纯程序的springAOP
现在,我们都是用AspectJ来实现面向AOP,因为它的语法很简单。而且,spring非常支持AspectJ。可是我们,还是要从程序化的AOP开始,因为AOP是基于代理的,纯程序能够让我们理清其中的代理模式。
前置通知的应用
AOP能够对方法进行增强,当然,还可以保证方法执行的安全性。我们通过一个类似于Spring Security一样的功能,来了解前置通知的纯程序化的代码。
比如我们有一个bean,我们要对其进行保护:
package com.ocean.testaop.aopfinal;
public class SecureBean {
public void writeMessage(){
System.out.println("今天下午3点三号门见");
}
}
这是机密消息,必须要通过身份验证才能够看到。
我们用一个类来存储用户信息:
package com.ocean.testaop.aopfinal;
public class UserInfo {
private String userName;
private String password;
public UserInfo(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
}
然后我们需要一个验证用户登录的类:
package com.ocean.testaop.aopfinal;
public class SecurityManager {
private static ThreadLocal<UserInfo> threadLocal
= new ThreadLocal<>();
//用户登录
public void login(String userName, String password){
threadLocal.set(new UserInfo(userName,password));
}
//用户不登录
public void logout(){
threadLocal.set(null);
}
//拿到登录的用户
public UserInfo getLoggedOnUser(){
return threadLocal.get();
}
}
现在,我们要来创建一个advice,用来对SecureBean进行保护,一个前置通知就要实现MethodBeforeAdvice接口,注意
MethodBeforeAdvice---->BeforeAdvice------>Advice,
Advice是通知的顶级接口。
package com.ocean.testaop.aopfinal;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class SecurityAdvice implements MethodBeforeAdvice {
private SecurityManager securityManager;
public SecurityAdvice(SecurityManager securityManager) {
this.securityManager = securityManager;
}
//如果用户是Jack,那就算通过验证;密码不管了
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
UserInfo user = securityManager.getLoggedOnUser();
if(user == null){
System.out.println("没有用户被授权");
throw new SecurityException("你要先登录才能调用方法:"+method.getName());
}else if("Jack".equals(user.getUserName())){
System.out.println("Jack,验证通过!");
}else{
System.out.println("登录用户是:"+user.getUserName()+" ,不允许调用方法: " + method.getName());
throw new SecurityException("错误的登录用户!");
}
}
}
我们测试一下:
package com.ocean.testaop.aopfinal;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ProxyFactoryBean;
public class SecurityDemo {
public static void main(String[] args) {
SecurityManager securityManager = new SecurityManager();
SecureBean proxy = getSecureBean();
securityManager.login("Jack","123456");
proxy.writeMessage();
securityManager.logout();
System.out.println("---------------------------------------------------------------------");
try{
securityManager.login("Ocean","123456");
proxy.writeMessage();
}catch (SecurityException e){
System.out.println("捕获到的异常信息: " + e.getMessage());
}finally {
securityManager.logout();
}
System.out.println("---------------------------------------------------------------------");
try{
proxy.writeMessage();
}catch (SecurityException e){
System.out.println("捕获到的异常: " + e.getMessage());
}
}
//产生代理
private static SecureBean getSecureBean(){
//new出目标对象
SecureBean target = new SecureBean();
//ProxyFactory控制aop的织入与代理的创建
ProxyFactory proxyFactory = new ProxyFactory();
//指定目标对象
proxyFactory.setTarget(target);
//传入通知
proxyFactory.addAdvice(new SecurityAdvice(new SecurityManager()));
//拿到代理
SecureBean proxy = (SecureBean) proxyFactory.getProxy();
return proxy;
}
}
结果:
首先我们关注代理的产生。
我们new出了一个代理工厂,并且把目标对象和通知都传给了工厂,由此产生出一个代理。
addAdvice()将通知封装到DefaultPointcutAdvisor中进行处理。DefaultPointcutAdvisor----->PointcutAdvisor------>Advisor
SpringAOP里面,切面需要由实现了advisor接口的类表示。
我们接着观察拿到代理的过程:
//拿到代理
SecureBean proxy = (SecureBean) proxyFactory.getProxy();
最终还是会委托给cglib或者是jdk动态代理。
关于这两个代理,后面叙述。
代理产生后,我们调用代理的方法时:
proxy.writeMessage();
都会经过SecurityAdvice的检查。
注意main方法中的new SecurityManager()
与private static SecureBean getSecureBean()
方法中的new SecurityManager()
并非同一个对象,但因为我们用ThreadLocal把数据都存在了当前线程中,所以这并没有关系。
通知的类型与引入通知
Spring有6种通知:
- 前置通知
- 后置通知
- 后置通知返回
- 环绕通知
- 异常通知
- 引入通知
环绕通知类似于前置通知和后置通知的组合,但是它有返回值。
我们稍稍看看引入通知。
为什么要使用引用?因为我们可以动态地为一个类添加功能。对,是类,而不是方法,不是方法的前后置增强,所以,引用是作用于类的。
比如现在我有一个联系人的类:
package com.ocean.testaop.aopfinal;
public class Contact {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我想要知道,该类的实例是否有修改,即,是否调用了setName(String name)
方法。
为了实现这个额外功能,我们定义一个接口:
package com.ocean.testaop.aopfinal;
public interface IsModified {
boolean isModified();
}
下面,我们就要把这个接口引入,这样的类被称为mixin(将advice与扩展接口混合):
package com.ocean.testaop.aopfinal;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
//创建mixin
public class IsModifiedMixin extends DelegatingIntroductionInterceptor implements IsModified {
private boolean isModified = false;
private Map<Method,Method> methodCache = new HashMap<>();
@Override
public boolean isModified() {
return isModified;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable{
if(!isModified){
//是否是setter方法
if(invocation.getMethod().getName().startsWith("set") && invocation.getArguments().length == 1){
//取出对应的getter方法
Method getter = getGetter(invocation.getMethod());
if(getter != null){
//传递给setter方法的参数
Object newValue = invocation.getArguments()[0];
//getter方法的返回值
Object oldValue = getter.invoke(invocation.getThis(),null);
//将两者进行比较以确定是否调用了setter方法,以此监测对象是否被改变
if(newValue == null && oldValue == null){
isModified = false;
}else if(newValue == null && oldValue != null){
isModified = true;
}else if(newValue != null && oldValue == null){
isModified = true;
}else {
//都不是null的话,就比较值
isModified = !newValue.equals(oldValue);
}
}
}
}
//调用父类的invoke方法,将调用分派到被通知对象
return super.invoke(invocation);
}
private Method getGetter(Method setter){
//从缓存当中根据setter去拿getter方法
Method getter = methodCache.get(setter);
//拿到就返回
if(getter != null){
return getter;
}
//拿不到就找到getter方法并put进map中
String getterName = setter.getName().replaceFirst("set","get");
try{
getter = setter.getDeclaringClass().getMethod(getterName,null);
synchronized (methodCache){
methodCache.put(setter,getter);
}
return getter;
}catch (NoSuchMethodException e){
return null;
}
}
}
DelegatingIntroductionInterceptor 是个什么类呢?
它最终还是个advice。
advice需要由advisor来封装,我们再建一个advisor:
package com.ocean.testaop.aopfinal;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
//创建advisor
public class IsModifiedAdvisor extends DefaultIntroductionAdvisor {
public IsModifiedAdvisor(){
super(new IsModifiedMixin());
}
}
每次创建一个advisor实例,就会有一个mixin实例。
现在我们测试:
package com.ocean.testaop.aopfinal;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.framework.ProxyFactory;
public class IntroductionDemo {
public static void main(String[] args) {
Contact target = new Contact();
target.setName("Ocean");
IntroductionAdvisor advisor = new IsModifiedAdvisor();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setOptimize(true);
Contact proxy = (Contact) proxyFactory.getProxy();
IsModified proxyInterface = (IsModified) proxy;
System.out.println("--------------------check the original information---------------------------------------");
System.out.println("Is this name an instance of Contact? ---->" + (proxy instanceof Contact));
System.out.println("Is this name an instance of IsModified?---->" + (proxy instanceof IsModified));
System.out.println("Has the name 'Ocean' been modified yet?---->" + proxyInterface.isModified());
System.out.println("--------------------set the same name----------------------------------------");
proxy.setName("Ocean");
System.out.println("Has the name 'Ocean' been modified yet?---->" + proxyInterface.isModified());
System.out.println("--------------------modify the name----------------------------------------");
proxy.setName("Caliente");
System.out.println("Has the name 'Ocean' been modified yet?---->" + proxyInterface.isModified());
}
}
代码proxyFactory.setOptimize(true);
要求强制使用cglib代理。
使用AspectJ
所以,Spring纯程序的AOP还是挺麻烦的。因为AspectJ语法的简介,所以spring在官方文档中也有详细的解说。
我们用注解的方式来了解AspectJ。
我们有一个目标类:
package com.ocean.testaop.aopfinal;
import org.springframework.stereotype.Component;
@Component
public class Math {
public int divide(int i, int j){
System.out.println("Math's divide running");
System.out.println("----------------------------------------");
return i/j;
}
}
里面做一个除法。
然后我们有一个切面(aspect):
package com.ocean.testaop.aopfinal;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MathAspect {
@Pointcut("execution(* com.ocean.testaop.aopfinal..*.divide*(..))")
public void myPointcut(){}
@Before("myPointcut()")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("before the target method---->" + joinPoint.getSignature().getName());
System.out.println("---------------------------------------");
}
@After("myPointcut()")
public void afterMethod(JoinPoint joinPoint){
System.out.println("after the target method------>" + joinPoint.getSignature().getName() + " ,and the arguments are"
+ Arrays.asList(joinPoint.getArgs()));
System.out.println("-----------------------------------------");
}
@AfterReturning(value = "myPointcut()",returning = "value")
public void afterReturningMethod(JoinPoint joinPoint,int value){
System.out.println("method------->" + joinPoint.getSignature().getName() + " completes, and the return value is "
+ value);
System.out.println("-------------------------------------------");
}
@AfterThrowing(value = "myPointcut()",throwing = "ex")
public void afterThrowingMethod(Exception ex){
System.out.println("Exception caught: " + ex.getMessage());
System.out.println("-------------------------------------------");
}
}
前面说过,spring有6种通知,以上切面展现了4种。
注意这里有个切点(pointcut)的概念,切点就是目标方法和通知的连接点。
"execution(* com.ocean.testaop.aopfinal..*.divide*(..))"
就是aspectJ的表达式。第一个*表示任意返回值,那是什么方法的任意返回值呢?是com.ocean.testaop.aopfinal包下面的任意类的以divide开头的方法,方法的参数同样是任意个。
然后写一个配置类来扫描包:
package com.ocean.testaop.aopfinal;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "com.ocean.testaop.aopfinal")
@EnableAspectJAutoProxy
public class AOPConfig {
}
对于@EnableAspectJAutoProxy
我们等下会详细说。
最后进行测试:
package com.ocean.testaop.aopfinal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationAspectJTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AOPConfig.class);
Math math = applicationContext.getBean(Math.class);
math.divide(1,1);
}
}
当然,这属于正常返回的情况:
如果我们把除数换成0,就会回调afterThrowingMethod方法:
package com.ocean.testaop.aopfinal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationAspectJTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AOPConfig.class);
Math math = applicationContext.getBean(Math.class);
math.divide(1,0);
}
}
这样很简单吧。
我们将这个代码逻辑还原成spring程序代码看看:
那就写一个环绕通知,这样就可以看到通知的全貌了。
我们创建一个拦截器:
package com.ocean.testaop.aopfinal;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class DivisionInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("look at the problem");
Object retVal = methodInvocation.proceed();
System.out.println("problem is solved");
return retVal;
}
}
这里其实就完成了前后置增强。
然后就是使用ApsectJ切点:
package com.ocean.testaop.aopfinal;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class AspectJPointcut {
public static void main(String[] args) {
Math target = new Math();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.ocean.testaop.aopfinal..*.divide*(..)))");
Advisor advisor = new DefaultPointcutAdvisor(pointcut,new DivisionInterceptor());
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
Math proxy = (Math) proxyFactory.getProxy();
proxy.divide(1,1);
}
}
我们也完成了around advice的功能,不过代码量还是很大。
springAOP原理
原对象何时变为代理对象?
我们都知道,aop使用了代理模式,那么,目标对象究竟是怎样被代理的呢?
还是使用Math类的例子。
我们打个断点:
getBean之后,拿到的Math是这样的:
这已经不是原生对象了,它在容器中就是一个代理对象。
如果我们不在切面类的上面加上注解@Aspect
呢?
最后拿到的Math还是原来的对象。
好,现在我们要走一边bean的生命周期,以期知道原生对象是在哪里被代理的,以及关键的组件是什么?
此时,原生对象还未产生:
当我们跑完final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
Math对象就从instanceWrapper
中拿出来了。
好,那么它是在哪个地方被代理的呢?
我们发现执行beanInstance = this.doCreateBean(beanName, mbdToUse, args);
时,原对象就变成代理对象了,所以代理的过程还是发生在doCreateBean这个方法中。
在原生对象产生后,原对象中途给了exposedObject:Object exposedObject = bean;
所以我们需要关注exposedObject什么时候改变。
它是在初始化bean方法调用的时候改变的:exposedObject = this.initializeBean(beanName, exposedObject, mbd);
因此我们走进初始化代码。
初始化代码里也是把bean给了另一个变量保存:Object wrappedBean = bean;
这时我们就要关注wrappedBean。
当调用后置处理器后:wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
原生对象就变成代理对象了。
所以,是后置处理器的问题。
我们知道,在bean被放入单例池之前,后置处理器可以对bean进行修改。
谁主导了代理的工作?
现在我们暂停一下,来关注@EnableAspectJAutoProxy
这个注解。因为加了它之后,AspectJ才起作用。
这个注解上方有Import注解,它的作用是给容器中自定义注入组件。
举个例子,比如我现在随便写一个类:
package com.ocean.testaop.aopfinal;
import org.springframework.beans.factory.annotation.Value;
public class NobodyClass {
@Value(value = "Ocean")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
它上面我没有加@Component
注解,我们要手动给它加到容器:
package com.ocean.testaop.aopfinal;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
@Component
public class SelfDefiniedImportClass implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(NobodyClass.class);
registry.registerBeanDefinition("nobody",beanDefinition);
}
}
现在我们使用@Import
注解:
package com.ocean.testaop.aopfinal;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan(basePackages = "com.ocean.testaop.aopfinal")
@EnableAspectJAutoProxy
@Import(SelfDefiniedImportClass.class)
public class AOPConfig {
}
测试:
package com.ocean.testaop.aopfinal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationAspectJTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AOPConfig.class);
// Math math = applicationContext.getBean(Math.class);
//
// math.divide(1,1);
NobodyClass nobody = (NobodyClass) applicationContext.getBean("nobody");
System.out.println(nobody.getName());
}
}
最后得到:
所以,想容器中注册组件是成功的。
现在的问题是,为什么要向容器中注册AspectJAutoProxyRegistrar
呢?
我们在这个类里面的一个方法中打个断点:
首先他问有没有这样一个名字的组件:
org.springframework.aop.config.internalAutoProxyCreator
(我用的是spring4.3.18版本,你用其他版本和我看到的源码可能不太一样,你可能看到一个常量,不过意思是一样的。)
当然是没有的:
接下类他就把rootBeanDefinition给new出来:
参数cls是传进来的:
它的类型是AnnotationAwareAspectJCreator。
所以,最后注册的组件就是AnnotationAwareAspectJCreator,名称是internalAutoProxyCreator。
接下来的问题是:AnnotationAwareAspectJCreator是谁?
实线表示extends,虚线表示implements。
最后,我们知道,AnnotationAwareAspectJCreator既是一个PostProcessor,又是一个Aware。
我们给继承树上所有与PostProcessor和Aware有关的逻辑打上断点,然后了解AnnotationAwareAspectJCreator是如何创建的:
refresh的时候,停在了这里,注释说:注册拦截bean创建的后置处理器。
调用后置处理器注册代理的registerBeanPostProcessors方法。
他说要注册实现了Ordered接口的后置处理器。
当然,我们的AnnotationAwareAspectJCreator是实现Ordered接口的,所以进来了。
现在要去getBean。
走下去。
调doGetBean。
getSingleton方法:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)
在getSingleton方法中,调用singletonFactory的getObject方法。
此时传进来的的singletonFactory是一个匿名内部类。
AbstractBeanFactory是一个抽象类,实现了ObjectFactory接口。
createBean。
doCreateBean。
创建bean。
初始化bean。
初始化逻辑里面,有一个invokeAwareMethods(beanName, bean)
我们的AnnotationAwareAspectJCreator既是一个BeanClassLoaderAware,又是一个BeanFactoryAware。
到了AbstractAdvisorAutoProxyCreator类的setBeanFactory方法了。
先调用父类AbstractAutoProxyCreator的setBeanFactory方法。
最后来到了AnnotationAwareAspectJCreator的initBeanFactory方法。
如此一来,invokeAwareMethods(beanName, bean)
的逻辑就走完了。
接下来就给bean包装一下,调用初始化方法前的后置处理器,调用初始化方法,然后调用初始化方法后的后置处理器。
最后返回出去。
把BeanPostProcessor排个序注册好:
加进beanFactory中。
至此,后置处理器注册完了。
那么他是怎么工作的呢?
我们debug接着跑。
判断“math”是否在advsedBeans中。并不在,advisedBeans是个空集合。
判断它是不是基础类型?即是不是Advice或Pointcut或Advisor或AopInfrastructureBean类型的。当然不是。
走进这个方法:
它又会问不是不个Aspect:
即有没有Aspect注解。
然后判断是否要跳过:
它列出了我们写的所有通知。
最终调用父类的shouldSkip方法返回false。
好,postProcessBeforeInstantiation结束了,这是Math实例创建之前做的工作。
下面是重要的 postProcessAfterInitialization。这是初始化后的工作。
我们进入wrapIfNecessary(bean, beanName, cacheKey);
方法。
里面有一个拿到所有通知和顾问的方法,进去:
他要拿到所有可用的advisor。然后给advisor排个序。
因为specificInterceptors != DO_NOT_PROXY
,所以进入if,把当前bean即“math”保存到advisedBean中。
重点来了,他这里就要创建代理了:
最后的重任就落在了我们的ObjenesisCglibAopProxy或者是JdkDynamicAopProxy身上了。
这里的Math对象就被改变了。这样我们和之前的逻辑就接上了。
在applyBeanPostProcessorsAfterInitialization中,他会遍历所有的后置处理器,当用到AnnotationAwareAspectJCreator时math对象改变了:
拦截器
我们在目标方法上打一个断点并进去:
直接来到了CglibAopProxy的intercept方法。
他这里要获取所有的interceptors:
再进去:
他要遍历所有的advisor,如果是 PointcutAdvisor类型的advisor,就进if,如果是IntroductionAdvisor类型的advisor,就进else。
我们没有引入,所以就进if。
那就遍历吧,走一次:
当前的advisor:
然后给出这个advisor,我们要拿一个interceptor。
进去:
他先通过advisor拿到advice,我们的advice是AspectJAfterThrowingAdvice,它是MethodInterceptor类型的,所以就加入interceptors数组当中。
后面他又搞了三个adapter:
利用这些adpter来拿interceptor,当然,我们已经拿到了,所以就不必辛苦他了。
最后返回出去:
走第二个advisor,它是作用在afterReturningMethod上的。
但它就不是MethodInterceptor类型的了,这时候他就要借助于适配器来拿interceptor了。
拿到拦截器链之后,要new一个CglibMethodInvocation。
调用该对象的proceed方法:
currentInterceptorIndex默认为-1。
也就是说,当this.interceptorsAndDynamicMethodMatchers.size()
为0的时候,会进第一个if。
这是在调用目标方法。
我们先进else:
拿到第一个interceptor:
这个是默认的interceptor,我们自己写了另外四个。
调用invoke方法。这里的this就是CiglibMethodInvocation:
他的invocation是个ThreadLocal:
他要把我们的MethodInvocation即CglibMethodInvocation加入当前线程共享。
然后再调用proceed方法,进去:
又来这里了(我把源码换了一下,原来的源码proceed方法进不去):
currentInterceptorIndex变成了1,于是我们拿出第二个拦截器:
又调invoke,进去:
又是proceed。
接着取拦截器。
一直调过去,最后来到了MethodBeforeAdviceInterceptor:
控制台有输出信息了:
再走proceed:
有趣的事情发生了,currentInterceptorIndex此时为4,正好是拦截器list长度5减1。
我们要进if,即调用目标方法,然后return。
一路return,直到执行后置通知。
然后是返回通知。
然后是异常通知,没异常就不catch了。
最后到了默认的ExposedInvocationInterceptor:
这就结束了。
CGLIB与JDK代理的性能
JDK只能生成接口的代理,而CGLIB代理既可以代理接口,也可以代理类。JDK生成的代理与目标对象实现了同一接口。
CGLIB会为目标类生成一个新的代理类,这个代理类继承了目标类。
CGLIB自己有固定的通知链,我们在上面已经看到了。
可以使用setInterfaces()方法强制使用JDK代理。
可以使用setOptimize(true)强制使用CGLIB代理。
我们简单测试一下CGLIB标准代理,CBLIG冻结通知链的情况下的代理,以及JDK动态代理之间的性能差异。
接口:
package com.ocean.testaop.aopfinal;
public interface SimpleBean
{
void advised();
void unadvised();
}
一个被通知方法,一个未被通知方法。
实现类:
package com.ocean.testaop.aopfinal;
public class DefaultSimpleBean implements SimpleBean {
private long dummy = 0L;
@Override
public void advised() {
dummy = System.currentTimeMillis();
}
@Override
public void unadvised() {
dummy = System.currentTimeMillis();
}
}
不管是被通知方法,还是未被通知方法,都简单地算个时间戳。
beforeadvice:
package com.ocean.testaop.aopfinal;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class NoOperationBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
}
}
连接点:
package com.ocean.testaop.aopfinal;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import java.lang.reflect.Method;
public class TestPointCut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "advised".equals(method.getName());
}
}
拦截所有的被通知方法。
测试:
package com.ocean.testaop.aopfinal;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class TestProxy {
public static void main(String[] args) {
SimpleBean target = new DefaultSimpleBean();
Advisor advisor = new DefaultPointcutAdvisor(new TestPointCut(),new NoOperationBeforeAdvice());
runCGLIBTest(advisor, target);
runCGLIBFrozenTest(advisor,target);
runJDKTest(advisor,target);
}
private static void runCGLIBTest(Advisor advisor,SimpleBean target){
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setProxyTargetClass(true);
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
SimpleBean proxy = (SimpleBean) proxyFactory.getProxy();
System.out.println("--------------------------RUN CGLIB TEST(STANDARD)------------------------------------------");
test(proxy);
}
private static void runCGLIBFrozenTest(Advisor advisor,SimpleBean target){
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setProxyTargetClass(true);
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setFrozen(true);
SimpleBean proxy = (SimpleBean) proxyFactory.getProxy();
System.out.println("--------------------------RUN CGLIB TEST(FROZEN)------------------------------------------");
test(proxy);
}
private static void runJDKTest(Advisor advisor,SimpleBean target){
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setInterfaces(new Class[]{SimpleBean.class});
SimpleBean proxy = (SimpleBean) proxyFactory.getProxy();
System.out.println("--------------------------RUN JDK TEST------------------------------------------");
test(proxy);
}
private static void test(SimpleBean proxy){
long before = 0;
long after = 0;
System.out.println("***********test advised method*************");
before = System.currentTimeMillis();
for(int i = 0; i < 500000; i++){
proxy.advised();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + "ms");
System.out.println();
System.out.println("***********test unadvised method*************");
before = System.currentTimeMillis();
for(int i = 0; i < 500000; i++){
proxy.unadvised();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + "ms");
System.out.println();
System.out.println("***********test equal() method*************");
before = System.currentTimeMillis();
for(int i = 0; i < 500000; i++){
proxy.equals(proxy);
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + "ms");
System.out.println();
System.out.println("***********test hashcode() method*************");
before = System.currentTimeMillis();
for(int i = 0; i < 500000; i++){
proxy.hashCode();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + "ms");
System.out.println();
System.out.println("***********test Advised.getProxyTargetClass() method*************");
Advised advised = (Advised) proxy;
before = System.currentTimeMillis();
for(int i = 0; i < 500000; i++){
advised.getTargetClass();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + "ms");
}
}
--------------------------RUN CGLIB TEST(STANDARD)------------------------------------------
***********test advised method*************
Took 596ms
***********test unadvised method*************
Took 246ms
***********test equal() method*************
Took 137ms
***********test hashcode() method*************
Took 56ms
***********test Advised.getProxyTargetClass() method*************
Took 18ms
--------------------------RUN CGLIB TEST(FROZEN)------------------------------------------
***********test advised method*************
Took 275ms
***********test unadvised method*************
Took 27ms
***********test equal() method*************
Took 23ms
***********test hashcode() method*************
Took 264ms
***********test Advised.getProxyTargetClass() method*************
Took 31ms
--------------------------RUN JDK TEST------------------------------------------
***********test advised method*************
Took 491ms
***********test unadvised method*************
Took 208ms
***********test equal() method*************
Took 238ms
***********test hashcode() method*************
Took 106ms
***********test Advised.getProxyTargetClass() method*************
Took 94ms
Process finished with exit code 0
CGLIB的性能要好于JDK。调用被通知方法时,冻结通知链的CBLIG代理显然更好。
来源:CSDN
作者:weixin_43810802
链接:https://blog.csdn.net/weixin_43810802/article/details/103737593