上节记了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个面,切开加入额外功能)
二、AOP底层原理的实现
开发步骤很清晰了,但是相对于静态代理,动态代理(AOP)的开发是基于动态字节码技术生成的动态代理类,是无文件落地的。而且在配置文件中通过beanid最终会取回代理类对象。上一节仅仅是记录了写额外功能用的是接口实现,底层的这两个问题没有深入研究,因为Spring应该已经给封装好了。所以接下来就分析这两个核心问题:
1. AOP如何创建动态代理类(动态字节码技术)
2. Spring⼯⼚如何加⼯创建代理对象
通过原始对象的id值,获得的是代理对象
1、动态代理的创建
(1)、JDK的动态代理类
需要实现一个方法:Proxy.newProxyInstance()里面的三个参数分别对应类加载器、实现接口和额外功能
再简单解读一下: (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者方法一致,同时在代理类中提供新的实现。(额外功能+原始方法)
代码测试:
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对象;那么我们也可以修改原始对象,从而使他返回的是代理对象。这样的话上面的问题就可以解答了。
如上图,编码比较好写,就是把动态代理的创建(这里是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的总结
一图流,非常清晰: