1000行代码读懂Spring(二)- 在Spring中实现AOP

关于AOP

AOP是Spring核心功能之一。今天就用tiny-spring来实现一个AOP。具体功能会包括:java

  1. 读取AspectJ格式的Pointcut描述。
  2. 使用JDK动态代理以及CGLib两种方式进行AOP织入。

AOP分为配置(Pointcut,Advice),织入(Weave)两部分工做,固然还有一部分是将AOP整合到整个容器的生命周期中。git

AOP相关概念较多,我不会一一列举,可是会在每一步对概念作一点解释。github

7.step7-使用JDK动态代理实现AOP织入

git checkout step-7-method-interceptor-by-jdk-dynamic-proxy

织入(weave)相对简单,咱们先从它开始。Spring AOP的织入点是AopProxy,它包含一个方法Object getProxy()来获取代理后的对象。spring

在Spring AOP中,我以为最重要的两个角色,就是咱们熟悉的MethodInterceptorMethodInvocation(这两个角色都是AOP联盟的标准),它们分别对应AOP中两个基本角色:AdviceJoinpoint。Advice定义了在切点指定的逻辑,而Joinpoint则表明切点。express

public interface MethodInterceptor extends Interceptor {
	
    Object invoke(MethodInvocation invocation) throws Throwable;
}

Spring的AOP只支持方法级别的调用,因此其实在AopProxy里,咱们只须要将MethodInterceptor放入对象的方法调用便可。app

咱们称被代理对象为TargetSource,而AdvisedSupport就是保存TargetSource和MethodInterceptor的元数据对象。这一步咱们先实现一个基于JDK动态代理的JdkDynamicAopProxy,它能够对接口进行代理。因而咱们就有了基本的织入功能。代理

@Test
	public void testInterceptor() throws Exception {
		// --------- helloWorldService without AOP
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
		HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
		helloWorldService.helloWorld();

		// --------- helloWorldService with AOP
		// 1. 设置被代理对象(Joinpoint)
		AdvisedSupport advisedSupport = new AdvisedSupport();
		TargetSource targetSource = new TargetSource(helloWorldService, HelloWorldServiceImpl.class,
				HelloWorldService.class);
		advisedSupport.setTargetSource(targetSource);

		// 2. 设置拦截器(Advice)
		TimerInterceptor timerInterceptor = new TimerInterceptor();
		advisedSupport.setMethodInterceptor(timerInterceptor);

		// 3. 建立代理(Proxy)
		JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
		HelloWorldService helloWorldServiceProxy = (HelloWorldService) jdkDynamicAopProxy.getProxy();

		// 4. 基于AOP的调用
		helloWorldServiceProxy.helloWorld();

	}

8.step8-使用AspectJ管理切面

git checkout step-8-invite-pointcut-and-aspectj

完成了织入以后,咱们要考虑另一个问题:对什么类以及什么方法进行AOP?对于“在哪切”这一问题的定义,咱们又叫作“Pointcut”。Spring中关于Pointcut包含两个角色:ClassFilterMethodMatcher,分别是对类和方法作匹配。Pointcut有不少种定义方法,例如类名匹配、正则匹配等,可是应用比较普遍的应该是和AspectJ表达式的方式。code

AspectJ是一个“对Java的AOP加强”。它最先是实际上是一门语言,咱们跟写Java代码同样写它,而后静态编译以后,就有了AOP的功能。下面是一段AspectJ代码:server

aspect PointObserving {
    private Vector Point.observers = new Vector();

    public static void addObserver(Point p, Screen s) {
        p.observers.add(s);
    }
    public static void removeObserver(Point p, Screen s) {
        p.observers.remove(s);
    }
    ...
}

这种方式无疑过重了,为了AOP,还要适应一种语言?因此如今使用也很少,可是它的Pointcut表达式被Spring借鉴了过来。因而咱们实现了一个AspectJExpressionPointcutxml

@Test
    public void testMethodInterceptor() throws Exception {
        String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression(expression);
        boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
        Assert.assertTrue(matches);
    }

9.step9-将AOP融入Bean的建立过程

git checkout step-9-auto-create-aop-proxy

万事俱备,只欠东风!如今咱们有了Pointcut和Weave技术,一个AOP已经算是完成了,可是它尚未结合到Spring中去。怎么进行结合呢?Spring给了一个巧妙的答案:使用BeanPostProcessor

BeanPostProcessor是BeanFactory提供的,在Bean初始化过程当中进行扩展的接口。只要你的Bean实现了BeanPostProcessor接口,那么Spring在初始化时,会优先找到它们,而且在Bean的初始化过程当中,调用这个接口,从而实现对BeanFactory核心无侵入的扩展。

那么咱们的AOP是怎么实现的呢?咱们知道,在AOP的xml配置中,咱们会写这样一句话:

<aop:aspectj-autoproxy/>

它其实至关于:

<bean id="autoProxyCreator" class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"></bean>

AspectJAwareAdvisorAutoProxyCreator就是AspectJ方式实现织入的核心。它实际上是一个BeanPostProcessor。在这里它会扫描全部Pointcut,并对bean作织入。

为了简化xml配置,我在tiny-spring中直接使用Bean的方式,而不是用aop前缀进行配置:

<bean id="autoProxyCreator" class="us.codecraft.tinyioc.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>

    <bean id="timeInterceptor" class="us.codecraft.tinyioc.aop.TimerInterceptor"></bean>

    <bean id="aspectjAspect" class="us.codecraft.tinyioc.aop.AspectJExpressionPointcutAdvisor">
        <property name="advice" ref="timeInterceptor"></property>
        <property name="expression" value="execution(* us.codecraft.tinyioc.*.*(..))"></property>
    </bean>

TimerInterceptor实现了MethodInterceptor(实际上Spring中还有Advice这样一个角色,为了简单,就直接用MethodInterceptor了)。

至此,一个AOP基本完工。

10.step10-使用CGLib进行类的织入

git checkout step-10-invite-cglib-and-aopproxy-factory

前面的JDK动态代理只能对接口进行代理,对于类则无能为力。这里咱们须要一些字节码操做技术。这方面大概有几种选择:ASMCGLibjavassist,后二者是对ASM的封装。Spring中使用了CGLib。

在这一步,咱们还要定义一个工厂类ProxyFactory,用于根据TargetSource类型自动建立代理,这样就须要在调用者代码中去进行判断。

另外咱们实现了Cglib2AopProxy,使用方式和JdkDynamicAopProxy是彻底相同的。

有一个细节是CGLib建立的代理是没有注入属性的, Spring的解决方式是:CGLib仅做代理,任何属性都保存在TargetSource中,使用MethodInterceptor=>TargetSource的方式进行调用。

至此,AOP部分完工,Spring的核心也基本成型。除去import语句,main下面一共是1026行。下篇博文会对Spring进行一个总体的分析。

项目地址

依然附上项目地址:https://github.com/code4craft/tiny-spring

相关文章
相关标签/搜索