Spring的AOP的底层实现原理
aop是ioc的一个扩展功,先有的ioc,再有的aop,只是ioc的整个流程中新增的一个扩展点而已:BeanPostProcessor。
bean的创建过程中,有一个步骤可以对bean进行扩展实现,aop本身就是一个扩展功能,所以在BeanPostProcessor的后置处理方法中来进行实现。
如何扩展实现?
1、代理对象的创建过程(advice,切面,切点)
2、通过jdk或cglib的方式生成代理对象
3、在执行方法调用的时候,会调用到生成的字节码文件中,直接会找到DynamicAdvisoredinterce类中的intercept方法,从此方法开始执行。
4、根据之前定义好的通知来生成拦截器链
5、从拦截器链中依次获取第一个通知开始进行执行,在执行过程中,为了方便找到下一个通知是哪一个,会有CgibMethodInvocation的对象,找的时候是从-1的位置依次开始查找的并且执行的。
JDK 代理
如果代理类实现了接口,Spring默认会使用JDK动态代理。JDK的动态代理是基于反射实现。JDK通过反射,生成一个代理类,这个代理类实现了原来那个类的全部接口,并对接口中定义的所有方法进行了代理。当我们通过代理对象执行原来那个类的方法时,代理类底层会通过反射机制,调用我们实现的InvocationHandler接口的invoke方法
public class TargetProxy {
public static <T> Object getTarget(T t) {
return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
//比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
System.out.println("执行方法前...");
Object invoke = method.invoke(t, args);
System.out.println("执行方法后...");
return invoke;
}
});
}
}
优点:
- JDK动态代理是JDK原生的,不需要任何依赖即可使用
- 通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快
缺点: - 如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理(InvocationHandler)
- JDK动态代理无法为没有在接口中定义的方法实现代理
- JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低
cglib 代理
若需要代理的类没有实现接口,JDK的动态代理就无法使用,Spring会使用CGLiB动态代理来生成代理对象。CGLiB直接操作字节码,生成类的子类,重写类的方法完成代理。
public class TargetProxy {
public static <T> Object getProxy(T t) {
Enhancer en = new Enhancer(); //帮我们生成代理对象
en.setSuperclass(t.getClass());//设置要代理的目标类
en.setCallback(new MethodInterceptor() {//代理要做什么
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("执行方法前。。。");
//调用原有方法
Object invoke = methodProxy.invokeSuper(object, args);
// Object invoke = method.invoke(t, args);// 作用等同与上面。
System.out.println("执行方法后。。。");
return invoke;
}
});
return en.create();
}
}
优点:
- 使用CGLiB代理的类,不需要实现接口,因为CGLib生成的代理类是直接继承自需要被代理的类
- 因为CGLiB实现方式是重写父类的方法,所以对final方法,或者private方法是没有办法代理的
- CGLiB是通过修改字节码生成的代理类,所以CGLib执行代理方法的效率要高于JDK的动态代理
缺点: - 因为CGLiB实现方式是重写父类的方法,所以对final方法,或者private方法是没有办法代理的
- 因为CGLiB生成代理类的方式是通过操作字节码(asm工具包),这种生成的代理类的方式比JDK通过反射生成代理类的方式的效率低
AOP的简单实现
此处使用的CGLIB代理的方式,配置文件中分别给被代理类和增强方法(拦截器) 创建bean,然后创建ProxyFactoryBean对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="target" class="com.heaboy.aopdemo.aop.Target"/>
<bean id="targetAdvice" class="com.heaboy.aopdemo.aop.TargetAdvice"/>
<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="target"/> <!--被代理的类-->
<property name="interceptorNames" value="targetAdvice"/> <!--如果用多种增强方式,value的值使用逗号(,)分割-->
<property name="proxyTargetClass" value="true"/> <!--强制使用CGLIB代理方式-->
<property name="interfaces" value="com.heaboy.aopdemo.aop.TargetInteface"/> <!--target实现的接口-->
</bean>
</beans>
增强类(拦截器)
public class TargetAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice {
/*
* 通知/增强
**/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("前置环绕通知");
Object proceed = methodInvocation.proceed();
System.out.println("后置环绕通知");
return proceed;
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置返回通知");
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知");
}
}
/**
* 测试
*/
public class AopTest {
public static void main(String[] args) {
ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
Target targetProxy = (Target) appCtx.getBean("targetProxy");
targetProxy.method1();
}
}
利用ClassPathXmlApplicationContext实现类加载类路径下的配置文件创建ApplicationContext 对象appCtx
再从appCtx获取生成的targetProxy代理类强转为Target类对象,执行其方法