Skip to content

SpringAOP的朴素解释

分类:

Spring Framework是构建与IOC与AOP之上的。这篇文章与另一篇文章SpringIOC的朴素解释是互补的。AOP意为面向切面编程,默认Spring只提供了运行时的方法切面。

1.面向切面编程 面向切面编程(AOP)是面向对象编程(OOP)的补充,它将处理问题的粒度细化到了方法层,在面向对象编程中,最核心的单元是类。它包含以下几个标准,所谓AOP框架就是实现这些标准: JoinPoint:这是一个运行时概念,JoinPoint包括了运行时该切点的一切信息,可以认为是一个PointCut的具体实现。 Pointcut:就是一个切点定义,代表一类可以织入的规则。 Advice(通知/增强):就是具体的增强逻辑。 Aspect(切面):由Pointcut与Advice组成,它代表一个切面组合。 AopProxy:代理类,这是结果,Aop增强后的结果就是产生一个链接了切点、切面、通知的代理类。 注:还有一些其他的定义这里就不扯了。

在我的很多篇文档都提到了这么一个理念:标准先行。这里的AOP标准就是一个非常非常好的例子。AOP的概念说简单可以简单,说复杂也可以复杂,现在有了标准就可以统一思想实现标准就行了。 代码有三个基本时态:编译时,加载时,运行时。这三个时间都可以人为干预然后增强你的类,比如Lombok就是在编译时增强的,它会在编译时插入增强代码。编译时是不灵活的,并且会让类膨胀。 纯java类库基本上都是在运行时对对象进行操作以达到增强的目的。基本过程是:通过目标对象得到一个增强后的代理对象,使用的时候直接使用代理对象,这个代理对象是与通知列表(Advice)链接在一起的,当调用代理对象的方法时,会调用与该方法(切点,PointCut)相关联的通知,从而达到AOP的目的。所以关键的一步是如何产生合适的代理对象。

2.动态代理与SpringAOP SpringAOP是通过动态代理实现的,分为两种:JDK动态代理与Cglib代理。实现这一切的基础是在Java中Everything is a object(一切皆对象)。Method是一个对象所以在调用过程中可以被hook插入其他逻辑。Jdk只能对接口进行增强,Cglib则不限接口与类,以下是两个简单的例子:

  • jdk:
java
public class JdkProxyTest implements Runnable{
	public static void main(String[] args) {
		JdkProxyTest o = new JdkProxyTest();
		InvocationHandler handler = new TestInvocationHandler(o);
		//Class[]是你要代理的接口,这里也用来提供代理类的类型,同时它也提供需要代理的方法。
		Runnable proxy = (Runnable)Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class<?>[] {Runnable.class}, handler);
		proxy.run();
	}
	
	public static class TestInvocationHandler implements InvocationHandler{
		
		private Object o;
		
		public TestInvocationHandler(Object o) {
			this.o = o;
		}
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			System.out.println("-----------------pre invoke------------------");
            //调用原来的方法,必须这样搞,不然一直调用代理类的代理方法会死循环。
			Object result = method.invoke(o, args);
			System.out.println("-----------------post invoke--------------------");
			return result;
		}
		
	}
	
	@Override
	public void run() {
		System.out.println("=========do something===============");
	}
}

注:Class<?>[]提供类型的同时也提供代理方法,这意味着不仅仅是接口中定义的方法,还有接口继承自Object的方法也会被代理。

  • Cglib:
java
public class CglibProxyTest {
	
	public static void main(String[] args) {
		 Enhancer e = new Enhancer();
            //父类
		 e.setSuperclass(CglibProxyTest.class);
		 TestMethodInterceptor mInterceptor = new TestMethodInterceptor();
            //实现接口
		 e.setInterfaces(new Class<?>[]{Runnable.class});
//		 e.setCallbackFilter(filter);
            //这是关键一步,是一个Callback回调
		 e.setCallback(mInterceptor);
		 CglibProxyTest proxy = (CglibProxyTest)e.create();
		 proxy.doSome();
	}
	public void doSome() {
		System.out.println("=========do something===============");
	}
	
	public static class TestMethodInterceptor implements MethodInterceptor{

		@Override
		public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
			System.out.println("-------------before intercept-----------------");
			Object result = arg3.invokeSuper(arg0, arg2);
			System.out.println("-------------after intercept-----------------");
			return result;
		}
	}
}

注:你的Class是不一定要继承某个类,实现某个接口的,他们是用来提供类型与方法的,这一点是与jdk代理不一样的。Cglib依赖ASM库。

Spring对代理类进行了抽象:

java
AopProxy
├─JdkDynamicAopProxy
└─CglibAopProxy
    └─ObjenesisCglibAopProxy

AopProxy接口只有两个方法:getProxy()和getProxy(ClassLoader)。也就是关键的一步,得到代理对象。而在Spring中能够优雅做到这一点的就是:BeanPostProcessor。BeanPostProcessor在Spring框架的扩展点中非常非常重要,它可以对bean创建进行hook,另外还有一个BeanFactoryPostProcessor也很重要,是对bean注册进行Hook。

使用:

java
//定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestTarget {}

//应用注解
public class TestClass {
	@TestTarget
	public void doSomething() {
		System.out.println("------------------------------doSomething-----------------------");
	}
}

//切点、切面、通知声明
@Aspect
public class TestAspact {
	@Pointcut("@annotation(com.example.demo.test.TestTarget)")
	public void pointCut() {}
	@Before("pointCut()")
	public void advice(JoinPoint joinPoint) {
		System.out.println("-------------advice----------------");
	}
}

//配置
@Configuration
public class TestConfiguration {	
	@Bean
	public TestClass testClass() {
		return new TestClass();
	}	
	@Bean
	public TestAspact testAspact() {
		return new TestAspact();
	}	
}

上面的所有被注解@TestTarget标识的bean的method都被增强了,增强内容是在控制台输出"-------------advice----------------"。

过程: 这里的过程是Spring使用BeanPostProcessor为bean产生代理对象的过程;也就是SpringAOP过程。这里以上面为的例子: 1.getBean(),这是梦开始的地方就是它调用的BeanPostProcessor方法。 2.AbstractAutoProxyCreator.postProcessAfterInitialization(Object,String),这里的具体实现类是AnnotationAwareAspectJAutoProxyCreator。就是这个BeanPostProcessor对Bean进行了AOP。

java
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        //得到cacheKey,普通bean就是beanName,FactoryBean则是BeanFactory.FACTORY_BEAN_PREFIX(&)+beanName;
        //如果beanName为空(理论上不可能)则返回Class
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            //如果必要则生成代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

3.wrapIfNecessary(),判断是否符合AOP条件,如果符合则产生代理类。

java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    //已经是缓存过的不需要增强的bean
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    //Advice,Pointcut,Advisor,AopInfrastructureBean不必增强;
    //beanName是.ORIGINAL结尾(这里需要自己看代码,条件必要但是不充分)的不必增强。
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // 关键一步,找到合适的Advice,内部调用findEligibleAdvisors()
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        //缓存,这里可以看出代理类可以再次被增强
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //关键一步,找到Advice后产生代理类
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

注:大概就是俩个关键步骤,a.根据bean信息找到合适的Advice,b.然后为这个bean应用这些advice产生代理对象并返回。

4.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(),这个方法会在所有可用的Advice中找到合适这个Class的advice。

java
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    //这一步在下面会讲到,它会处理切面
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    //筛选出合适的advisor,
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

注:findAdvisorsThatCanApply()首先设置当前线程正在被代理的beanName,然后内部委托给AopUtils.findAdvisorsThatCanApply(List<Advisor>, Class)找到合适的advice。AopUtils.findAdvisorsThatCanApply()会遍历所有的可用Advisor(candidateAdvisors参数),找到可以应用(AopUtils.canApply(Advisor, Class, boolean))的Advisor存入list并返回。AopUtils.canApply()首先会判断Advisor是IntroductionAdvisor类型还是PointcutAdvisor以应用不同的匹配规则(Introduction可以引入一个目标类中不存在的接口,目前我们都使用Pointcut这个概念,它代表一个切点,可以在切点前后增加逻辑达到增强。);知晓类型后,PointcutAdvisor会取出Pointcut封装信息(这里的实现是AspectJExpressionPointcut),通过这个封装信息匹配是否符合织入条件:a.Pointcut.getClassFilter().matches(Class)会取出PointcutExpression(AspectJExpressionPointcut的属性,具体类型是PointcutExpressionImpl)切点表达式封装,然后这个封装会分析目标类(这里是TestClass)以判断是否合适织入,b.PointCut取出MethodMatcher(这里是它自身this),使用这个methodMatcher匹配目标类(TestClass)的每个方法,如果有一个匹配成功则返回true。

注:Introduction用法:使用Introduction是一种非常非常不好的实践,在别人使用的时候会感觉有些方法是凭空产生的,如果你需要一个接口,那你直接实现就行了,如果这个接口有默认实现你提供接口方法默认实现就行了。当然也许是我没get到它的要点。

java
//需要引入的接口
public interface TestInterface {
	void doRun();
}
//目标
@Component
public class TestClass {}

//切面
@Component
@Aspect
public class TestAspact {
	//定义切点与默认实现
	@DeclareParents(value = "com.example.demo.test.TestClass" , defaultImpl = DefaultTestInterface.class )
	private TestInterface testInterface;
	//接口默认实现
	public static class DefaultTestInterface implements TestInterface{
		@Override
		public void doRun() {
			System.out.println("-------doRun---------");
		}	
	}
}

//使用
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ConfigurableApplicationContext app = SpringApplication.run(DemoApplication.class, args);
		//使用
                TestInterface i = (TestInterface)app.getBean("testClass");
		i.doRun();
	}

}

5.AbstractAutoProxyCreator.createProxy()创建代理类。

java
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }

    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);

    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    //找到所有适合这个bean的Advisor,这里最重要的是会找到通用Advisor,
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    // 如果Bean类没有在本地加载,则使用原始的ClassLoader来覆盖类加载器。
    ClassLoader classLoader = getProxyClassLoader();
    if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
        classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
    }
    //这里内部会构造AopProxy对象,具体是那个实现会根据proxyFactory设置选择
    //然后通过这个AopProxy对象的getProxy(classLoader)方法得到代理对象
    return proxyFactory.getProxy(classLoader);
}

注:如果目标类是接口或者目标类本身就是代理类型,则产生JdkDynamicAopProxy对象,否则产生ObjenesisCglibAopProxy对象。其还有其它情况(目标类为空或者只设置了接口)会根据ProxyFactory配置选择产生JdkDynamicAopProxy对象。得到AopProxy对象(这里类型是ObjenesisCglibAopProxy)后调用getProxy()得到代理对象,getProxy()如下:

java
@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
		}

		try {
			Class<?> rootClass = this.advised.getTargetClass();
			Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

			Class<?> proxySuperClass = rootClass;
			//如果已是代理对象,则拿其父类作为代理父类,其接口添加到接口列表中。
			if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
				proxySuperClass = rootClass.getSuperclass();
				Class<?>[] additionalInterfaces = rootClass.getInterfaces();
				for (Class<?> additionalInterface : additionalInterfaces) {
					this.advised.addInterface(additionalInterface);
				}
			}

			// 对Class及父类的每个方法的限定符进行校验,只有public或者protected的非静态非final方法能通过校验。
			// 未通过校验的方法会在debug日志级别环境下打印日志。
			validateClassIfNecessary(proxySuperClass, classLoader);

			// 终于到了这一步,构建Enhancer然后配置然后创建代理对象。
			Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
			//这里会找到全部的CallBack回调(实际上是临时new的,它需要根据AopProxy属性的不同而不同)。
			//我们的advisor会放到DynamicAdvisedInterceptor的advisors里面。
			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// 生成代理对象
			return createProxyClassAndInstance(enhancer, callbacks);
		}
		。。。。。。。。。。。。
	}

大概过程就是上面这几个步骤。 这还只是如果生成代理对象的过程,还有一个处理切面的过程: 处理切面也是由AnnotationAwareAspectJAutoProxyCreator完成的,上文中提到的findCandidateAdvisors()会找到所有@Aspect bean用与自动代理。

java
	protected List<Advisor> findCandidateAdvisors() {
		// Add all the Spring advisors found according to superclass rules.
		List<Advisor> advisors = super.findCandidateAdvisors();
		// Build Advisors for all AspectJ aspects in the bean factory.
		if (this.aspectJAdvisorsBuilder != null) {
			//关键一步buildAspectJAdvisors会处理@Aspect以及其他和AOP相关的注解,找出所有advisor
			advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
		}
		return advisors;
	}

注:buildAspectJAdvisors()第一次调用的时候的时候会将查找容器内所有的Aspect bean并将其处理成Advisor对象然后缓存起来。 注:从这里可以知道,一个Aspect必须要作为bean注入到容器才会生效。 注:具体实现请自行查看BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors()源码。

到这一步SpringAOP的过程就完成了。总的来说它包括两个部分:处理切面和生成代理对象。

3.加载时织入(LoadTime-Weaving) 这部分文档在Spring加载时织入,详细的定义属于Aspectj的知识。这里不打算往下深究了,没多大意义,等我遇到需要加载时织入的时候再来补充,希望你不要这么倒霉有这样的需求。但是可以猜到的是,这里的ClassLoader会使用自定义的ClassLoader。 注:最开始我们提到Spring提供的时运行时的方法AOP,而如果启用了LoadTime-Weaving则可以实现更加精细的AOP。

4.API Spring使用Advice、Pointcut和Advisor接口分别对通知(advice)、切点(pointcut)和切面(advice)进行抽象。使用如下:

java
//pointcut切点实现
public class TestPointCut implements Pointcut {

	@Override
	public ClassFilter getClassFilter() {
		return new TestClassFilter();
	}

	@Override
	public MethodMatcher getMethodMatcher() {
		return new TestMethodMatcher();
	}

	public static class TestMethodMatcher extends DynamicMethodMatcher{
		@Override
		public boolean matches(Method method, Class<?> targetClass, Object... args) {
			return method.getAnnotation(TestTarget.class) != null;
		}
	}
	
	public static class TestClassFilter implements ClassFilter {

		@Override
		public boolean matches(Class<?> clazz) {
			return TestClass.class.equals(clazz);
		}

	}

}
//advice通知实现
public class TestMethodBeforeAdvice implements MethodBeforeAdvice {

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("----------------before-------------------");
	}
	
}
//Advisor切面实现(也就是Aspect)
public class TestAdvisor extends AbstractPointcutAdvisor {
	private static final long serialVersionUID = -5818866045520237089L;
	@Autowired
	private TestPointCut testPointCut;
	@Autowired
	private TestMethodBeforeAdvice testMethodBeforeAdvice;
	@Override
	public Pointcut getPointcut() {
		return testPointCut;
	}
	@Override
	public Advice getAdvice() {
		return testMethodBeforeAdvice;
	}
}
//目标
public class TestClass {
	@TestTarget
	public void doSomething() {
		System.out.println("------------------------------doSomething-----------------------");
	}

}
//配置
@Configuration
public class TestConfiguration {
	@Bean
	public TestClass testClass() {
		return new TestClass();
	}	
	@Bean
	public TestMethodBeforeAdvice testMethodBeforeAdvice() {
		return new TestMethodBeforeAdvice();
	}
	@Bean
	public TestPointCut testPointCut() {
		return new TestPointCut();
	}
	@Bean
	public TestAdvisor testAdvisor() {
		return new TestAdvisor();
	}	
}
//使用
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ConfigurableApplicationContext app = SpringApplication.run(DemoApplication.class, args);
		TestClass c = app.getBean(TestClass.class);
		c.doSomething();
	}
}

在绝大多数时候我们使用AspectJ提供的表达式就可以实现我们的需求,在不济你可以扩展JdkRegexpMethodPointcut、AspectJExpressionPointcut(AspectJ表达式切点实现)、StaticMethodMatcherPointcut(静态方法匹配切点)、DefaultPointcutAdvisor(默认的切面实现)、MethodXXXXAdvice(方法前置/后置/异常等等等通知,是个接口)等等去满足你要的功能;如果还不能满足再去实现接口。Spring提供了一些工具类比如Pointcuts来辅助完成功能。

还有一些偏门的情况是比如你想自己实现getObject()获取代理类的过程或者所想控制Advisor切面的顺序等等等等一大堆,你可以自己实现ProxyFactoryBean等等,回头遇到再来研究。不过理论上上面的得到代理对象的过程以及涵盖了这部分逻辑。

注:FactoryBean在Spring中是用来获取bean对象(可用的Object)的工厂,他是一个泛型接口。

5.总结 和IOC的文章一样,这篇文章的绝大多是知识是在实际生产中用不上的,也就是无用知识;我依然保持我的观点:最大的问题是如何高效合作,而不是技术过不过关。AOP是有其规范的,有规范就有其实现,会用就行。总的来说,SpringAOP默认提供运行时方法增强,支持AspactJ语法定义,可以实现加载时增强,但是我们确实不到万不得已不应该这么做;SpringAOP包括了俩个过程:切面处理与代理对象生成,这两个步骤都是通过BeanPostProcessor完成的,处理切面是在第一次寻找Advisor的时候完成的,而代理对象生成是在找到合适的Advisor后通过AopProxy得到的;Spring对AOP的概念都抽象了接口并且开放了相关的API。

注:转载请标明出处与作者。如果意见或建议请评论区留言。

实践、认识、再实践、再认识,这种形式,循环往复以至无穷,而实践和认识之每一循环的内容,都比较地进到了高一级的程度。