dcddc

西米大人的博客

0%

系统学习JAVA代理

动态代理和静态代理

代理是一种设计模式,又简单的分为两种。

  • 静态代理:代理类和委托类在代码运行前关系就确定了
  • 动态代理:动态代理类的字节码在程序运行时的时候生成

静态代理的问题

静态代理最直接的实现方法,就是代理类里注入委托类,实现增强逻辑以及回调委托类的方法,可以理解成一个简单的装饰模式。静态代理的缺点就是既然要代理委托类,就要实现委托类的所有方法,工作量太大

动态代理来了

动态代理在程序运行阶段动态生成代理类,且不需要实现委托类的所有方法,而是统一对委托类的方法做增强处理。回调委托类方法使用反射或预先建立的方法索引机制,不同动态代理的方式不同

JDK动态代理

JDK动态代理的原理简述如下:

  • InvocationHandler注入被代理类
  • 运行时(动态)创建实现了委托类接口的Proxy代理类,在构造方法里注入InvocationHandler
    • 代理类的静态代码块里通过反射获取了委托类实现接口的所有方法Method类,实现接口时传入了对应的Method类方便后面反射调用
  • Proxy代理委托类的方法时,回调InvocationHandler的invoke方法,传入原委托方法Method对象、参数列表等
  • 在InvocationHandler的invoke方法里实现增强逻辑,并通过反射回调委托类的原方法

jdk动态代理的Demo如下

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
public class ProxyFactory implements InvocationHandler {
private Class<?> target;
private Object real;
//委托类class
public ProxyFactory(Class<?> target){
this.target=target;
}
//实际执行类bind
public Object bind(Object real){
this.real=real;
//利用JDK提供的Proxy实现动态代理
return Proxy.newProxyInstance(target.getClassLoader(),new Class[]{target},this);
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
//代理环绕
System.out.println("begin");
//执行实际的方法
Object invoke = method.invoke(real, args);
System.out.println("end");
return invoke;
}

public static void main(String[] args) {
Calculator proxy =(Calculator) new ProxyFactory(Calculator.class).bind(new Calculator.CalculatorImpl());
proxy.add(1,2);
}
}

JDK动态代理的特点总结如下:

  • JDK动态代理只能代理实现了接口的委托类,且只能代理接口方法
  • DK的动态代理还可以代理Object类的equals、hashCode、toString这三个方法

CGLIB动态代理

如果委托类没实现接口,CGLIB动态创建的代理类是委托类的子类,否则和JDK动态代理一样,代理类也实现了委托类的接口

CGLIB生成的代理类对委托类所有可代理的方法(可覆写或实现了接口的方法)做了索引,执行代理时可以根据委托类方法签名的哈希值快速路由到委托类对应方法,直接用注入的委托类对象实例调用方法即可,避免了反射调用带来的性能损耗

CGLIB动态代理的特点总结如下:

  • CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类
  • 由于是继承方式,如果是static方法、private方法、final方法等描述的方法是不能被代理的
  • 做了方法访问优化,使用建立方法索引的方式避免了传统Method的方法反射调用
  • CGLIB会默认代理Object中finalize、equals、toString、hashCode、clone等方法。比JDK代理多了finalize和clone

AspectJ静态代理

静态代理唯一的缺点就是我们需要对每一个方法编写我们的代理逻辑,造成了工作的繁琐和复杂。AspectJ就是为了解决这个问题,在编译成class字节码的时候在方法周围加上业务逻辑。复杂的工作由特定的编译器帮我们做。

通过编写满足AspectJ语法的切面类,再配合mvn插件,编译委托类就可以得到静态织入的class文件,加载到JVM的委托类已经被增强了

AspectJ静态代理的特点总结如下:

  • Aspectj并不是动态的在运行时生成代理类,而是在编译的时候就植入代码到class文件,所以性能相对来说比较好
  • Aspectj不受类的特殊限制,不管方法是private、或者static、或者final的,都可以代理
  • Aspectj不会代理除了限定方法之外任何其他诸如toString()、clone()等方法

SpringAOP

SpringAOP实际上是对JDK代理和CGLIB代理做了一层封装,并引入了AspectJ的一些概念注解,例如@pointCut,@after,@before。需要注意,SpringAOP没有用AspectJ静态代理

我们在使用SpringAOP的时候通常会在XML配置文件中设置<aop:aspectj-autoproxy proxy-target-class="true">来使用CGLIB代理。但是是否一定会使用还是得看传入的class到底是个怎样的类。如果是接口,就算开启了这几个开关,最后还是会自动选择JDK代理

如果使用JDK动态代理,重点关注JdkDynamicAopProxy这个类,它实现了InvokeHandler接口,增强逻辑在invoke方法里。为了实现前置增强、后置增强等多个切面类赋予的增强逻辑,