如果你有幸能看到
tiny-spring
是为了学习Spring的而开发的,可以认为是一个Spring的精简版。Spring的代码很多,层次复杂,阅读起来费劲。我尝试从使用功能的角度出发,参考Spring的实现,一步一步构建,最终完成一个精简版的Spring。 有人把程序员与画家做比较,画家有门基本功叫临摹,tiny-spring可以算是一个程序的临摹版本-从自己的需求出发,进行程序设计,同时对著名项目进行参考。
AOP分为配置(Pointcut,Advice),织入(Weave)两部分工作,当然还有一部分是将AOP整合到整个容器的生命周期中。
7.step7-使用JDK动态代理实现AOP织入
织入(weave)相对简单,我们先从它开始。Spring AOP的织入点是AopProxy,它包含一个方法Object getProxy()来获取代理后的对象。
在Spring AOP中,我觉得最重要的两个角色,就是我们熟悉的MethodInterceptor和MethodInvocation(这两个角色都是AOP联盟的标准),它们分别对应AOP中两个基本角色:Advice和Joinpoint。Advice定义了在切点指定的逻辑,而Joinpoint则代表切点
切点通知器相关接口
public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; }
Spring的AOP只支持方法级别的调用,所以其实在AopProxy里,我们只需要将MethodInterceptor放入对象的方法调用即可。
使用jdk动态代理
public class JdkDynamicAopProxy extends AbstractAopProxy implements InvocationHandler { public JdkDynamicAopProxy(AdvisedSupport advised ) { super(advised); } @Override public Object getProxy() { return Proxy.newProxyInstance(getClass().getClassLoader(),advised.getTargetSource().getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提取拦截的方法 MethodInterceptor methodInterceptor = (MethodInterceptor) advised.getMethodInterceptor(); //比较传入的方法和对象的方法是否一致,如果一致则调用传入的方法, if (advised.getMethodMatcher() != null && advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { //这里应该是先调用拦截的方法,然后调用原始对象的方法。但是一般括号里的东西不是优先吗?括号里面好像就只有赋值操作而已。 return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method, args)); } else { return method.invoke(advised.getTargetSource().getTarget(), args); } }
我们称被代理对象为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(); }
git checkout step-8-invite-pointcut-and-aspectj
完成了织入之后,我们要考虑另外一个问题:对什么类以及什么方法进行AOP?对于“在哪切”这一问题的定义,我们又叫做“Pointcut”。Spring中关于Pointcut包含两个角色: ClassFilter
和 MethodMatcher
,分别是对类和方法做匹配。Pointcut有很多种定义方法,例如类名匹配、正则匹配等,但是应用比较广泛的应该是和 AspectJ
表达式的方式。
AspectJ
是一个“对Java的AOP增强”。它最早是其实是一门语言,我们跟写Java代码一样写它,然后静态编译之后,就有了AOP的功能。下面是一段AspectJ代码:
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借鉴了过来。于是我们实现了一个 AspectJExpressionPointcut
:
@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); }
public class AspectJExpressionPointcutTest { @Test public void testClassFilter() throws Exception { String expression = "execution(* us.codecraft.tinyioc.*.*(..))"; AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); aspectJExpressionPointcut.setExpression(expression); boolean matches = aspectJExpressionPointcut.getClassFilter().matches(HelloWorldService.class); Assert.assertTrue(matches); } @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); }
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基本完工。
git checkout step-10-invite-cglib-and-aopproxy-factory
前面的JDK动态代理只能对接口进行代理,对于类则无能为力。这里我们需要一些字节码操作技术。这方面大概有几种选择: ASM
, CGLib
和 javassist
,后两者是对 ASM
的封装。Spring中使用了CGLib。
在这一步,我们还要定义一个工厂类 ProxyFactory
,用于根据TargetSource类型自动创建代理,这样就需要在调用者代码中去进行判断。
另外我们实现了 Cglib2AopProxy
,使用方式和 JdkDynamicAopProxy
是完全相同的。
public class Cglib2AopProxy extends AbstractAopProxy { public Cglib2AopProxy(AdvisedSupport advised) { super(advised); } //通过cglib类库创建了一个代理类的实例 @Override public Object getProxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(advised.getTargetSource().getTargetClass()); enhancer.setInterfaces(advised.getTargetSource().getInterfaces()); //设置代理类的通知方法,相当于设置拦截器方法 enhancer.setCallback(new DynamicAdvisedInterceptor(advised)); Object enhanced = enhancer.create(); return enhanced; } //方法拦截器 private static class DynamicAdvisedInterceptor implements MethodInterceptor { private AdvisedSupport advised; private org.aopalliance.intercept.MethodInterceptor delegateMethodInterceptor; private DynamicAdvisedInterceptor(AdvisedSupport advised) { this.advised = advised; this.delegateMethodInterceptor = advised.getMethodInterceptor(); } //调用代理类的方法(代理类与原始类是父子关系,还有一种是兄弟关系,调用实质是调用原始类的方法) @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { if (advised.getMethodMatcher() == null || advised.getMethodMatcher().matches(method, advised.getTargetSource().getTargetClass())) { //这里也应该是先调用拦截方法,然后调用原始对象的方法 return delegateMethodInterceptor.invoke(new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy)); } return new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy).proceed(); } } private static class CglibMethodInvocation extends ReflectiveMethodInvocation { private final MethodProxy methodProxy; public CglibMethodInvocation(Object target, Method method, Object[] args, MethodProxy methodProxy) { super(target, method, args); this.methodProxy = methodProxy; } @Override public Object proceed() throws Throwable { return this.methodProxy.invoke(this.target, this.arguments); } }
有一个细节是CGLib创建的代理是没有注入属性的, Spring的解决方式是:CGLib仅作代理,任何属性都保存在TargetSource中,使用MethodInterceptor=>TargetSource的方式进行调用。
至此,AOP部分完工。
1、通过AspectJ表达式处理AOP的主要类和关系
2、使用cglib动态代理
3、两种动态代理