代理

反射和动态代理使用的地方非常多。

反射

在运行状态中,对于任意的一个类,都能够知道这个类的所有属性和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法。

应用

  • 开发通用框架反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
  • 动态代理   在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了。
  • 注解   注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。
  • 可扩展 - 应用程序可以通过使用完全限定名称创建可扩展性对象实例来使用外部的用户定义类。

缺点

  • 性能开销 由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中避免。
  • 破坏封装性反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
  • 内部曝光由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

使用

获取 Class

  1. 使用Class.forName()静态方法
  2. 直接获取
  3. 调用Object.getClass()
  4. Type 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestClass{
public static void main(String[] args) throws ClassNotFoundException {
Class mysql = Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(mysql.getCanonicalName());

Class b = boolean.class;
System.out.println(b.getCanonicalName());

Set<String> set = new HashSet<>();
Class s = set.getClass();
System.out.println(s.getCanonicalName());

Class f = Float.TYPE;
System.out.println(f.getCanonicalName());
}
}

判断实例

判断是否为某个类的实例有两种方式:

  1. instanceof 关键字
  2. Class.isInstance() 方法(它是一个 Native 方法)
1
2
3
4
5
6
7
8
9
10
11
public class InstanceofDemo {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
if (arrayList instanceof List) {
System.out.println("ArrayList is List");
}
if (List.class.isInstance(arrayList)) {
System.out.println("ArrayList is List");
}
}
}//Output://ArrayList is List//ArrayList is List

创建实例

  • Class 对象的 newInstance 方法。
  • Constructor 对象的 newInstance 方法。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class NewInstanceDemo {
public static void main(String[] args)throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c1 = StringBuilder.class;
StringBuilder sb = (StringBuilder) c1.newInstance();
sb.append("aaa");
System.out.println(sb.toString());

//获取String类带一个String参数的构造器
Class<?> c2 = String.class;
Constructor constructor = c2.getConstructor(String.class);
//根据构造器创建实例
String str2 = (String) constructor.newInstance("bbb");
System.out.println(str2);
}
}

动态代理

  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法增强

JDK 自带的 proxy

基于接口的动态代理
涉及的类:Proxy  JDK 官方提供
如何创建:使用Proxy类中的newProxyInstance方法
要求:被代理类最少实现一个接口,如果没有使用则不能用
newProxyInstance 方法参数:
ClassLoader:类加载器
用于加载代理对象字节码的,和被代理对象使用相同的类加载器。固定写法
Class[]:字节码数组
用于让代理对象和被代理对象有相同方法。固定写法
InvocationHandler:提供增强代码
让我们写如何处理。一般写一个该接口的实现类,通常情况都是匿名内部类,但不必须
此接口的实现类都是谁用谁写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.itheima.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* @author lqs
* @date 2020/4/2 - 18:57
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();

IProducer proxyProducer = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
/**
* 执行被代理对象的任何接口方法都会经过该方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});

//test
proxyProducer.saleProduct(1000f);
}
}

第三方 cglib

基于子类的动态代理
涉及的类:Enhancer   第三方 cglib
提供
如何创建:使用Enhancer类中的create方法
create 方法参数:
Class;用于指定被代理对象的字节码
Callback:提供增强代码
我们写如何处理。一般写一个该接口的实现类,通常情况都是匿名内部类,但不必须

此接口的实现类都是谁用谁写
一般写子接口实现类:MethodInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.itheima.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* @author lqs
* @date 2020/4/2 - 18:57
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();

Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(),
new MethodInterceptor() {
/**
* 执行该对象的任何方法都会经过该方法
* @param obj
* @param method
* @param args
* 以上三个参数和基于接口的动态代理invoke方法参数是一样的
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});

//test
cglibProducer.saleProduct(120000f);
}
}

代理
https://polarisink.github.io/20220813/yuque/代理/
作者
Areis
发布于
2022年8月13日
许可协议