上节记了Spring的动态代理开发,能注意到用到aop的地方,额外功能引用了aop的相关依赖;切入点则是用了aop标签。所以可以简单理解为 。AOP编程==Spring动态代理开发。这节就深入分析下AOP的原理、底层实现、编程方式等。

一、AOP的概念
AOP (Aspect Oriented Programing) ⾯向切⾯编程 = Spring动态代理开发
以切⾯为基本单位的程序开发,通过切⾯间的彼此协同,相互调⽤,完成程序的构建
切⾯ = 切⼊点 + 额外功能
OOP (Object Oritened Programing) ⾯向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调⽤,完成程序的构建
POP (Producer Oriented Programing) ⾯向过程(⽅法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调⽤,完成程序的构建

AOP的概念:
 本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。
 好处:利于原始类的维护

注意:AOP编程不可能取代OOP ! !

AOP编程的开发步骤:1、写原始对象 2、写额外功能 3、确定切入点 4、组装切面(额外功能+切入点)

切面 = 切入点 + 额外功能

简单理解就是有OrderServiceImpl 和 UserServiceImpl 两个原始对象作为两个块,类对象里的一个个方法视为一个个点,这时候在两个原始对象中都加入相同的额外功能,在额外功能这里找两个点,连起的一条线就可以被视为一个切面。(找准n个点,连成1个面,切开加入额外功能)

6.PNG

二、AOP底层原理的实现

开发步骤很清晰了,但是相对于静态代理,动态代理(AOP)的开发是基于动态字节码技术生成的动态代理类,是无文件落地的。而且在配置文件中通过beanid最终会取回代理类对象。上一节仅仅是记录了写额外功能用的是接口实现,底层的这两个问题没有深入研究,因为Spring应该已经给封装好了。所以接下来就分析这两个核心问题:

1. AOP如何创建动态代理类(动态字节码技术)
2. Spring⼯⼚如何加⼯创建代理对象
 通过原始对象的id值,获得的是代理对象
1、动态代理的创建

(1)、JDK的动态代理类

需要实现一个方法:Proxy.newProxyInstance()里面的三个参数分别对应类加载器、实现接口和额外功能

7.PNG

8.PNG

再简单解读一下: (1)因为动态代理类没有对应的.class文件,动态字节码技术不生成,所以这里的classloader只要找别的类借一个将字节码加载到JVM就行 (2)interfaces要和原始对象实现相同接口 (3)invocationhandler中书写额外功能并调用原始方法并返回结果,写法和MethodInterceptor很类似,参数比后者多,因为后者封装了吧,我们在这里就是细致的分析底层。

代码测试:

package com.jin.jdk;

import com.jin.proxy.User;
import com.jin.proxy.UserService;
import com.jin.proxy.UserServiceImpl;

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

/**
 * @author jinyunlong
 * @date 2021/12/7 9:18
 * @profession ICBC锅炉房保安
 */
public class TestJDKProxy {

    /*
        1. 借用类加载器  TestJDKProxy
                       UserServiceImpl
           随便借
        2.JDK8.x前 创建原始对象要声明final
     */

    public static void main(String[] args) {
        //1 创建原始对象
        UserService userService = new UserServiceImpl();
            //2 JDK创建动态代理

            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("-------proxy log 开始了--------");
                //原始方法运行
                Object ret = method.invoke(userService,args);
                System.out.println("-------proxy log 结束了-------");

                return ret;
            }
        };

        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
        userServiceProxy.login("Stringssss","dsdsdsds");
        userServiceProxy.register(new User());
    }
}

测试结果:

-------proxy log 开始了--------
UserServiceImpl.login
-------proxy log 结束了-------
-------proxy log 开始了--------
UserServiceImpl.register 业务运算 + DAO
-------proxy log 结束了-------

(2)、CGlib的动态代理

CGlib动态代理的应用场景: 适用于非接口实现的对象。父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证2者方法一致,同时在代理类中提供新的实现。(额外功能+原始方法)

9.PNG

代码测试:

package com.jin.cglib;

import com.jin.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author jinyunlong
 * @date 2021/12/7 13:57
 * @profession ICBC锅炉房保安
 */
public class TestCglib {
    public static void main(String[] args) {
        //1 创建原始对象
        UserService userService = new UserService();

        /*
        2 通过cglib方式创建动态代理对象
        Proxy.newProxyInstance(classloader,interface,invocationhandler)

        Enhancer.setClassLoader()
        Enhancer.setSuperClass()
        Enhancer.setCallback();  ---> MethodInterceptor(cglib)

        Enhancer.create() ---> 代理
         */

        Enhancer enhancer = new Enhancer();

        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("-------cglib log 开始了--------");
                Object ret = method.invoke(userService, objects);
                System.out.println("-------cglib log 结束了--------");
                return ret;
            }
        };
        enhancer.setCallback(interceptor);
        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.login("Stringssss","dsdsdsds");
        userServiceProxy.register(new User());
    }
}

注意:上面测试类代码的UserService是类不是接口。

返回结果:

-------cglib log 开始了--------
UserService.login
-------cglib log 结束了--------
-------cglib log 开始了--------
UserService.register
-------cglib log 结束了--------

JDK动态代理和CGlib动态代理的区别:

JDK动态代理 Proxy.newProxyInstance() 通过接⼝创建代理的实现类
Cglib动态代理 Enhancer 通过继承⽗类创建的代理类

这样第一个问题(AOP如何创建动态代理类)的底层原理就分析清楚了;无论是用JDK还是CGlib创建动态代理;类加载器、原始类(实现同一接口或者继承原始父类)、额外功能都是必不可少的。然后生成代理对象供程序调用。那么接下来就引出第二个问题,为什么在测试方法中getbean的是配置文件中原始对象的beanid获取的却是代理对象呢?

2、Spring工厂加工原始对象

Spring(3) 记录了后置处理Bean的操作,可以对对象进行再加工,当时是对已经进行过set注入的值进行了修改后再去返回bean对象;那么我们也可以修改原始对象,从而使他返回的是代理对象。这样的话上面的问题就可以解答了。

10.PNG

如上图,编码比较好写,就是把动态代理的创建(这里是JDK,实现接口方式)放在BeanPostProcessor了,生成代理对象并返回即可。

代码测试:

package com.jin.factory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

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

/**
 * @author jinyunlong
 * @date 2021/12/7 14:53
 * @profession ICBC锅炉房保安
 */
public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    /*
      Proxy.newProxyInstance();
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("-------new log 开始了--------");
                //原始方法运行
                Object ret = method.invoke(bean,args);
                System.out.println("-------new log 结束了-------");
                return ret;
            }
        };

        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
        return userServiceProxy;
    }
}

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop   http://www.springframework.org/schema/aop/spring-aop.xsd">


    <bean id="userService" class="com.jin.factory.UserServiceImpl" />

<!--    1. 实现BeanPostProcessor 进行加工-->
<!--    2. 配置文件中对BeanPostProcessor进行配置-->
    <bean id="proxyBeanPostProcessor" class="com.jin.factory.ProxyBeanPostProcessor" />


</beans>
    @Test
    public void test2(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext1.xml");
//        UserService userService = (UserService) ctx.getBean("userService");
        com.jin.factory.UserService userService = (com.jin.factory.UserService) ctx.getBean("userService");

        userService.login("yun","123456");
        userService.register(new User());
    }

返回结果:

-------new log 开始了--------
UserServiceImpl.login
-------new log 结束了-------
-------new log 开始了--------
UserServiceImpl.register
-------new log 结束了-------

这样,AOP底层原理最核心的两点就都实现了,细致的分析是这样,Spring把这些封装了,所以无感(有兴趣去看看这个MethodInvocation接口),直接调用就完了。

或者去看看这个:

【小家Spring】探索Spring AOP中aopalliance的Joinpoint、MethodInvocation、Interceptor、MethodInterceptor..

SpringAOP[5]-MethodInvocation(拦截器的调用)

三、基于注解的AOP编程
1、编码

之前是用编码+配置文件的方法做动态代理(AOP编程),经典4步都清晰可见,如果是用注解对AOP编程的话,开发步骤会简化一些:

测试代码:

package com.jin.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

/**
 * @author jinyunlong
 * @date 2021/12/7 16:28
 * @profession ICBC锅炉房保安
 */

/*
    1.额外功能
    public class Around implements MethodInterceptor {
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
                Object ret = methodInvocation.proceed();
                return ret;
        }
    }
    2.切入点
        <aop:config
            <aop:pointcut id="" expression="execution(* login(..)) />"
 */
@Aspect
public class MyAspect {

    @Pointcut("execution(* *.register(..))")
    public void myPointcut(){

    }

    @Around(value = "myPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("-------aspect log 开始了--------");
        Object ret = joinPoint.proceed();
        System.out.println("-------aspect log 结束了--------");
        return ret;
    }

//    @Around(value = "myPointcut()")
//    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
//        System.out.println("-------fuck log 开始了--------");
//        Object ret = joinPoint.proceed();
//        System.out.println("-------fuck log 结束了--------");
//        return ret;
//    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop   http://www.springframework.org/schema/aop/spring-aop.xsd">


    <bean id="userService" class="com.jin.aspect.UserServiceImpl" />

    <!--
    切面
      1.额外功能
      2.切入点
      3.组装切面
     -->
    <bean id="around" class="com.jin.aspect.MyAspect" />

<!--    告知Spring基于注解进行AOP编程-->
    <aop:aspectj-autoproxy />
<!--    JDK切cglib-->
<!--    <aop:aspectj-autoproxy proxy-target-class="true" />-->


</beans>

可见一个around的bean标签就把2、3、4步整合到一起了(你懂得),因为都在方法有写,挺直观的。注意标签里一定要写 那个aop:aspectj-autoproxy ,告知Spring基于注解进行AOP编程。

2、切入点复用

如上,看Aspect类,现在已经实现复用了,很简单,把切入点从@Around注解中抽出来放在@Pointcut注解中(写个空方法就行),然后供@Around注解以value方式写方法名就行了(解耦了)。

3、JDK和CGlib动态代理的切换

基于注解或者传统开发都一样,改标签就行

默认情况 AOP编程 底层应⽤JDK动态代理创建⽅式
  如果切换Cglib
     1. 基于注解AOP开发
       <aop:aspectj-autoproxy proxy-target-class="true" />
     2. 传统的AOP开发
       <aop:config proxy-target-class="true">
       </aop>

我在做切换代理测试的时候遇到了这种报错:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mathCalculator' defined in com.config.MianConfig3: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Unexpected AOP exception; nested exception is java.lang.IllegalStateException: Unable to load cache item

原因是spring-aop版本我用的是5.1.14.RELEASE ,根据文章 升级版本解决了问题,(我升到了5.2.11.RELEASE)

参考文章:spring 之aop 异常Unable to load cache item

4、AOP开发的一个坑

略,写出来没试成~

四、AOP的总结

一图流,非常清晰:

11.PNG


标题:Spring(5)
作者:jyl
地址:http://jinyunlong.xyz/articles/2021/12/08/1638934318429.html