• Spring中AOP详解


    目录

    一、AOP的概念

    二、AOP的底层实现原理

    2.1 JDK的动态代理

    2.1.1 invocationhandler接口

    2.1.2 代理对象和原始类实现相同的接口 interfaces

    2.1.3 类加载器ClassLoador

    2.1.4 编码实现

    2.2 Cglib动态代理

    2.2.1 Cglib动态代理编码实现

    三、AOP如何通过原始对象的id获取到代理对象

    3.1 BeanPostProcessor

    3.2 编码实现


    一、AOP的概念

    AOP(Aspect Oriented Programing)即面向切面编程,以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。这里的 切面 = 切入点 + 额外功能,所以我们常说的AOP也就等同于Spring中的动态代理开发!那么什么是切面呢?当在不同的ServiceImpl中,需要添加同一个额外功能的时候,这几个类的方法中所添加的相同额外功能就会由点构成面,所以就将这个称为是切面

    二、AOP的底层实现原理

    2.1 JDK的动态代理

    由于这里是探索AOP底层的实现原理,所以我们这里先摒弃Spring框架。首先我们需要了解代理创建的三个要素(1.原始对象 2.额外功能 3.代理对象和原始对象实现相同的接口),有了这三个要素之后就能创建出一个代理对象,接下来画图分析

    首先将这个原始对象创建出来,在添加额外功能和实现相同的接口的时,使用JDK的Proxy类中的newProxyInstance(动态字节码技术)方法来完成。要了解一个类中方法的具体使用,就需要了解这个类中参数的具体含义

    2.1.1 invocationhandler接口

    这里的invocationhandler接口就是完成额外功能的,实现这个接口时要实现这个invoke方法,提到这个invoke方法是不是就联想到了Spring中的拦截器MethodInterceptor中的invoke方法?其实MethodInterceptor中的invoke方法就是对这一系列的操作进行了封装

    invocationHandler接口中的invoke方法有三个参数,其中proxy忽略掉

    method:额外功能所增加给的原始方法

    args:原始方法的参数

    method调用其invoke方法使得原始方法运行起来,那么我们想要添加额外功的就只需要添加在method.invoke的前后即可。这样额外功能的添加就完成了

    2.1.2 代理对象和原始类实现相同的接口 interfaces

    这里的参数interfaces是获取到原始对象实现的那个接口。通过获取类文件在获取接口来实现

    2.1.3 类加载器ClassLoador

    在一般创建对象的过程都是通过类加载器将对应的字节码文件加载到JVM,同时类加载器创建类的class对象,进而创建出这个类的对象。其中CL表示类加载器,同时获取这个类加载器也不需要我们担心,每一个类的.class文件都会自动分配一个

    但是在创建动态代理类的时候是没有源文件的,它是通过动态字节码技术(Proxy.newProxyInstance)去创建字节码的。由于动态代理技术是直接将字节码文件写入JVM中的,并没有这个类加载器,但是我们又需要使用类加载器去帮我们创建代理类对象,那这个时候怎么办呢?借一个嘛!所以这就是参数中需要一个类加载器的原因

    2.1.4 编码实现

    创建接口

    1. public interface UserService {
    2. void register();
    3. boolean login();
    4. }

    原始方法实现这个类 

    1. public class UserServiceImpl implements UserService{
    2. @Override
    3. public void register() {
    4. System.out.println("register核心功能正在执行");
    5. }
    6. @Override
    7. public boolean login() {
    8. System.out.println("login核心功能正在执行");
    9. return false;
    10. }
    11. }

    添加额外功能 

    1. import java.lang.reflect.InvocationHandler;
    2. import java.lang.reflect.Method;
    3. import java.lang.reflect.Proxy;
    4. public class TestJDKProxy {
    5. public static void main(String[] args) {
    6. // 创建原始对象
    7. UserService userService = new UserServiceImpl();
    8. // 以下是JDK动态代理创建
    9. // 实现InvocationHandler接口,为了方便演示采取内部类的方式
    10. InvocationHandler handler = new InvocationHandler() {
    11. @Override
    12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    13. // 原始方法运行
    14. Object ret = method.invoke(userService,args);
    15. // 在原始方法后面添加额外功能
    16. System.out.println("aop底层实现----额外功能添加在原始功能后面----log");
    17. return ret;
    18. }
    19. };
    20. // TestJDKProxy.class.getClassLoader()借用一个类加载器,借谁的无所谓
    21. // userService.getClass().getInterfaces() 拿到原始类的接口
    22. // 使用相同的接口接收代理类
    23. UserService userServiceProxy =
    24. (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
    25. // 调用核心方法 观察额外功能是否添加完成
    26. userServiceProxy.login();
    27. userServiceProxy.register();
    28. }
    29. }

    至此,JDK的动态代理原理就已经全部分析完了

    2.2 Cglib动态代理

    首先在开始Cglib动态代理之前,我们在回顾以下JDK动态代理的过程。JDK动态代理类通过与原始类实现同一个接口从而完成额外功能的添加。但是在现实开发的过程中有没有一种可能这个原始类没有实现任何的接口,那这个时候该怎么办呢?这个时候就需要使用Cglib动态代理来完成了

    Cglib是怎么完成这个代理类的实现的呢?Cglib是采取了继承的方式来完成代理类的实现的

    由于这里的Cglib动态代理的实现与JDK动态代理的实现是高度一致的,这里就只介绍二者的区别了,而不再介绍相同点了

    2.2.1 Cglib动态代理编码实现

    Cglib动态代理的实现中是通过Enhancer类中的一系列方法来完成的,通过setClassLoder方法去设置类加载器,通过setSuperClass方法设置父类对象,通过setCallback方法设置额外功能,当然这个额外功能也要去实现接口,这里的接口是MethodInterceptor(这个并不是Spring提供的那个接口,而是Cglib包中的接口),最后通过create方法创建动态代理对象

    1. public class UserServiceImpl{
    2. public void register() {
    3. System.out.println("register核心功能正在执行");
    4. }
    5. public boolean login() {
    6. System.out.println("login核心功能正在执行");
    7. return false;
    8. }
    9. }
    1. import org.springframework.cglib.proxy.Enhancer;
    2. import org.springframework.cglib.proxy.MethodInterceptor;
    3. import org.springframework.cglib.proxy.MethodProxy;
    4. import java.lang.reflect.Method;
    5. public class TestCglib {
    6. public static void main(String[] args) {
    7. // 创建原始对象
    8. UserServiceImpl userService = new UserServiceImpl();
    9. // 以下是Cglib创建动态代理对象
    10. Enhancer enhancer = new Enhancer();
    11. // TestCglib.class.getClassLoader() 借用的类加载器
    12. enhancer.setClassLoader(TestCglib.class.getClassLoader());
    13. // userService.getClass() 获取到的父类对象
    14. enhancer.setSuperclass(userService.getClass());
    15. MethodInterceptor interceptor = new MethodInterceptor() {
    16. @Override
    17. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    18. // 添加额外功能
    19. System.out.println("Cglib底层实现------额外功能添加在方法执行前---log");
    20. // 原始方法执行
    21. Object ret = method.invoke(userService, args);
    22. return ret;
    23. }
    24. };
    25. // 设置额外功能
    26. enhancer.setCallback(interceptor);
    27. // 创建动态代理对象
    28. UserServiceImpl userServiceCglib = (UserServiceImpl) enhancer.create();
    29. userServiceCglib.register();
    30. userServiceCglib.login();
    31. }
    32. }

     

    三、AOP如何通过原始对象的id获取到代理对象

    3.1 BeanPostProcessor

    在Spring中提供了一个接口BeanPostProcessor,这个接口是用来加工Spring通过配置文件创建的对象的。通过实现接口中的postProcessorAfterInitialization方法,就可以实现通过原始对象的id值获取到代理对象了(也就是通过这个接口对原始类进行再加工)

    3.2 编码实现

    首先在Spring的配置文件中创建UserServiceImpl的对象,这里是实现的接口,所以动态代理应该使用JDK动态代理的方式

    1. public class UserServiceImpl implements UserService{
    2. @Override
    3. public void register() {
    4. System.out.println("register核心功能正在执行");
    5. }
    6. @Override
    7. public boolean login() {
    8. System.out.println("login核心功能正在执行");
    9. return false;
    10. }
    11. }
    <bean id="userService" class="com.gl.demo.proxy.UserServiceImpl"/>

    创建好对象以后,创建一个类实现BeanPostProcessor接口为原始类进行加工,进而将代理类返回给用户

    1. import org.springframework.beans.BeansException;
    2. import org.springframework.beans.factory.config.BeanPostProcessor;
    3. import java.lang.reflect.InvocationHandler;
    4. import java.lang.reflect.Method;
    5. import java.lang.reflect.Proxy;
    6. public class BeanPostProcessorTest implements BeanPostProcessor {
    7. @Override
    8. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    9. return bean;
    10. }
    11. @Override
    12. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    13. // 在这个方法中对需要添加额外功能的类进行加工
    14. // 这里采取JDK动态代理的方式进行加工
    15. // BeanPostProcessorTest.class.getClassLoader() 借用的类加载器
    16. // bean.getClass().getInterfaces()获取原始类的接口
    17. // 实现InvocationHandler接口添加额外功能
    18. InvocationHandler handler = new InvocationHandler() {
    19. @Override
    20. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    21. Object ret = method.invoke(bean, args);
    22. System.out.println("spring底层实现动态代理----额外功能添加在原始方法后---log");
    23. return ret;
    24. }
    25. };
    26. // 将代理类返回给用户而不是原始类
    27. return Proxy.newProxyInstance(BeanPostProcessorTest.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
    28. }
    29. }

    最后将加工的好的代理对象配置在Spring的配置文件中

    <bean id="proxyBeanProcessor" class="com.gl.demo.proxy.BeanPostProcessorTest"/>

    这时候,用户通过原始类的id值拿到的是代理类而不是原始类了,进而完成了动态代理的过程

    1. public void test4() {
    2. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config2.xml");
    3. UserService userService = (UserService) ctx.getBean("userService");
    4. userService.register();
    5. userService.login();
    6. }

    至此,AOP底层原理就已经全部分析完毕了!以上的工作Spring其实都给我们封装好了,在日后的开发过程中直接使用就可以了,不用这么麻烦!

  • 相关阅读:
    前端自动化require.context的使用
    爱尔兰博士后招聘|利默里克大学-广告学
    应用多元统计分析--多元数据的直观表示(R语言)
    Ubuntu(WSL) 安装最新版本的 GCC
    贪心算法-以学籍管理系统为例
    软件复杂性的膨胀与测试
    【云原生之Docker实战】使用Docker部署calibre-web个人图书管理平台
    基于Java+SpringBoot+Vue前后端分离华府便利店信息管理系统设计和实现
    【附源码】Python计算机毕业设计设计与实现大学常规信息管理系统
    宝塔面板使用Supervisor进程守护插件,配置守护Mysql的操作教程。
  • 原文地址:https://blog.csdn.net/x2656271356/article/details/133992161