Spring Aop 动态代理

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对比

动态代理cglibjdk
是否提供子类代理
是否提供接口代理
区别必须依赖于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配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)

保持饥饿

532 声望
143 粉丝
0 条评论
推荐阅读
国际化方案(1)- 多语言
国际化是指软件开发时,应该具备支持多种语言和地区的功能。 换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据。

KerryWu阅读 296

SpringBoot集成LibreOffice+jodconverter做文件预览(office转pdf)
LibreOffice 是一款开放源代码的自由免费全能办公软件,可运行于 Microsoft Windows, GNU/Linux 以及 macOS 等操作系统上。它包含了 Writer, Calc, Impress, Draw, Math 以及 Base 等组件,可分别用于文本文档、...

Zeran2阅读 6.3k

之前很火给女朋友推送微信服务号消息是怎么做的?
经过了几天的奋战,终于把微信服务号的模板消息给写完了。后端其实没花多少时间,因为之前已经有同学提过pull request了,我在这基础之上简单优化下就完事了,主要的时间都是花在前端上,对前端页面和参数的适配...

Java3y3阅读 1.2k

简单使用spring cloud 服务注册做一个请求转发中心
背景上篇文章 记录多项目共用一个公众号逻辑修改, 实现了多个项目共用一个公众号。 但是也存在几点问题,比如:中间服务器拦截了微信的请求,虽然方便了项目不再需要写微信授权的代码,但如果以后需要再拓展新的...

weiweiyi2阅读 779

消息推送平台终于要上线啦!
我的开源项目消息推送平台Austin终于要上线了,迎来在线演示的第一版!🔥项目在线演示地址:[链接]消息推送平台🔥推送下发【邮件】【短信】【微信服务号】【微信小程序】【企业微信】【钉钉】等消息类型。[链接][链...

Java3y3阅读 1.1k

Java项目是不是分布式,真有那么重要吗?
「微服务」「分布式」在我刚毕业的时候还是比较关注的,那时候还入门了一把SpringCloud,写了一篇很长的文章,还是很顶的,有不少的大号都给我转载了,在知乎又获得了很多的赞。

Java3y2阅读 594评论 1

聊聊那些年我们实现java AOP几种常见套路
前言有一定开发经验的同学对AOP应该很了解吧,如果不了解,可以先查看如下文章进行科普一下[链接],再来阅读本文。示例前置准备注: 本示例基于springboot进行演示1、在项目pom引入aop的GAV {代码...} 2、编写业...

linyb极客之路阅读 373评论 1

保持饥饿

532 声望
143 粉丝
宣传栏