1.代理模式
代理是设计模式的一种,代理类为委托类提供消息预处理,消息转发,事后消息处理等功能。Java中的代理分为三种角色: 代理类、委托类、接口
。
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。
Java中的代理按照代理类生成时机不同又分为静态代理
和动态代理
:
- 静态代理:静态代理的特点是, 为每一个业务增强都提供一个代理类, 由代理类来创建代理对象. 下面我们通过静态代理来实现对转账业务进行身份验证.
- 动态代理:静态代理会为每一个业务增强都提供一个代理类, 由代理类来创建代理对象, 而动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成.
1.2.静态代理
Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject)。如下示例:
接口 Subject.java
public interface Subject {
public void sayHello();
}
委托类 RealSubject.java
public class RealSubject implements Subject {
@Override
public void sayHello() {
System.out.println("hello!");
}
}
代理类 ProxySubject.java
class ProxySubject implements Subject {
private Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public void sayHello() {
System.out.println("Before say hello...");
subject.sayHello();
System.out.println("After say hello...");
}
}
测试方法 main
public static void main(String[] args) {
Subject subject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(subject);
proxySubject.sayHello();
}
按照示例中,代理模式实现的功能,可以理解成一个简易版的Spring AOP 实现,那我们就拿代理模式和Spring AOP做对比。
代理模式的组成包括:接口、委托类和代理类。我们在Spring中使用AOP,通常针对的“切面”,也就是委托类会有很多。接口和委托类是业务代码,必不可少,但代理类这是为了代理模式而创建的。如果每个委托类对应代理类的逻辑都不一样还好,可 如果多个委托类复用同一个代理类方法,就显得很冗余了
。
1.2. jdk动态代理
为了解决这类问题,jdk有提供动态代理的实现,即提供可复用的代理类。动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。
JDK动态代理是使用 java.lang.reflect 包下的代理类来实现. JDK动态代理动态代理必须要有接口.
由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。
上面的代码实现,可以修改成下面这种方式,同样能实现功能。
接口 Subject.java
public interface Subject {
public void sayHello();
}
委托类 RealSubject.java
public class RealSubject implements Subject {
@Override
public void sayHello() {
System.out.println("hello!");
}
}
代理类 InvocationHandlerImpl.java
public class InvocationHandlerImpl implements InvocationHandler {
private Object object;
public InvocationHandlerImpl(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before say hello...");
Object returnValue = method.invoke(subject, args);
System.out.println("After say hello...");
return returnValue;
}
}
测试方法 main
public static void main(String[] args) {
Subject realSubject = new RealSubject();
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
subject.sayHello();
}
1.3. cglib动态代理
JDK动态代理必须要有接口, 但如果要代理一个没有接口的类该怎么办呢? 这时我们可以使用CGLIB动态代理. CGLIB动态代理的原理是生成目标类的子类, 这个子类对象就是代理对象, 代理对象是被增强过的.
注意: 不管有没有接口都可以使用CGLIB动态代理, 而不是只有在无接口的情况下才能使用.
委托类 RealSubject.java
public class RealSubject {
public void sayHello() {
System.out.println("hello!");
}
}
代理类 MethodInterceptorImpl.java
public class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Before say hello...");
Object returnValue= methodProxy.invokeSuper(obj, args);
System.out.println("After say hello...");
return returnValue;
}
}
测试方法 main
public static void main(String[] args) {
RealSubject target = new RealSubject();
RealSubject proxy = (RealSubject) Enhancer.create(target.getClass(),
new MethodInterceptorImpl());
proxy.sayHello();
}
2. Spring AOP实现原理
关于Spring AOP的概念我就不多说了,大家都知道是基于动态代理实现的,就是上面我们说的这些,那么具体是怎么实现的呢?
在代理模式中有三种核心的角色:委托类、代理类、接口
,而 cglib 动态代理中“接口”是非必须的,因此我们关注Spring AOP中 委托类
和代理类
的实现。
委托类
回顾一下Aop的实现代码:需要在实现类上加上@Aspect
的注解,还需要通过@Pointcut
注解来申明“切点”,即委托类和委托方法的路径。
有了这些信息就足够获取委托类了。这里充分用到Java反射,先找到包含@Aspect
注解的类,然后找到该类下的@Pointcut
注解,读取所定义的委托类和委托方法路径,就完全能拿到委托类对象。
代理类
因为我们使用的是动态代理,这里的代理类可以被替换成代理方法
。同样,我们在@Aspect
注解的类中,用@Around、@Before、@After
修饰的方法,就是我们想要的代理方法。
总结
我们可以通过BeanFactoryPostProcessor
的实现类,完成对所有BeanDefinition的扫描,找出我们定义的所有的切面类,然后循环里面的方法,找到切点、以及所有的通知方法,然后根据注解判断通知类型(也就是前置,后置还是环绕),最后解析切点的内容,扫描出所有的目标类。这样就获取了委托类
和 代理方法
。
现在委托类
和 代理方法
都有了,我们知道在动态代理模式中,最终的目的是将委托类的方法执行,替换成代理类的方法执行。但是在Spring中,我们是感知不到代理类
的,我们在代码中还是调用原委托类
的方法,那么Spring框架是如何神不知鬼不觉地将委托类
替换成代理类
的呢?
这就涉及到我们之前有关Ioc文章的内容了,在Bean的生命周期中,Bean在初始化前后会执行BeanPostProcessor
的方法。可以把它理解成一个增强方法,可以将原始的Bean经过“增强”处理后加载到Ioc容器中。这就是一个天然的代理方法,原始的Bean就是委托类
,在此处实现代理方法生成代理类,再将代理类加载进Ioc容器。
3. jdk动态代理和cglib对比
动态代理 | cglib | jdk |
---|---|---|
是否提供子类代理 | 是 | 否 |
是否提供接口代理 | 是 | 是 |
区别 | 必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法 | 实现InvocationHandler,使用Proxy.newProxyInstance产生代理对象,被代理的对象必须要实现接口 |
Cglib和jdk动态代理的区别?
1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
2、 Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理
什么时候用cglib什么时候用jdk动态代理?
1、目标对象生成了接口 默认用JDK动态代理
2、如果目标对象使用了接口,可以强制使用cglib
3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换
JDK动态代理和cglib字节码生成的区别?
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的
Cglib比JDK快?
1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.6之前比使用java反射的效率要高
2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率
3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib
4、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改
总结来说:
- 在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢。
在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了。
Spring如何选择是用JDK还是cglib?
1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,用cglib实现
3、可以强制使用cglib(在spring配置中加入