spring源码学习(一)--AOP初探
LZ以前一直觉得,学习spring源码,起码要把人家的代码整体上通读一遍,现在想想这是很愚蠢的,spring作为一个应用平台,不是那么好研究透彻的,而且也不太可能有人把spring的源码全部清楚的过上一遍,哪怕是spring的缔造者。我们确实没有必要把源码全部过一遍,如果在看spring源码的过程中,能学习到东西是最关键的,说实话,下面的很多东西也是我边看源码,边看大神的解读写出来的,基本不属于原创,但是改变自己的思想,尽量从源码出发,不要一出现问题就只能寄所有希望于百度,如果能做到这点,最好不过了。
AOP中文翻译是面向切面编程,与面向对象,面向接口,面向服务等概念是相似的,所谓面向切面,即是使用切面与其他事物产生关系。面向对象强调一切皆对象,也可以说面向接口是一切皆接口,面向服务是一切皆服务,而面向切面也是一样,一切皆切面。
下面我们具体说说AOP,目前由AOP联盟给出了AOP的标准,AOP联盟的规范只是提供了几个接口定义,为了统一AOP的标准,下面来看看主要的几个接口的uml类图。
Advice接口:这是一个空接口,里面没有任何方法,只是用来标识一个通知。LZ自己的理解,这个接口定义了要通知什么内容。
Interceptor接口:Advice的子接口,这个接口和advice都不直接使用,一般是要扩展以后去实现特殊的拦截。
Joinpoint接口:代表了一个运行时的连接点。
Invocation接口:代表了程序中的一个调用,可以被拦截器Interceptor拦截。
下面还有几个接口,分别是Interceptor和Invocation的扩展接口,从下一层继承开始,interpretor的继承体系已经开始依赖于invocation。这从某种意义上来说,advice是依赖于joinpoint的。
以上就是AOP联盟规范里的的几个主要接口。下面我们看一下spring里的AOP的核心接口,这里叨唠一下看别人框架的一个小技巧,用抽象构建框架,用细节实现扩展。所以看别人代码,注意接口的关系和含义很关键。
Advice体系:Spring采用AOP联盟的Advice作为超级接口,扩展了很多子接口,比如BeforeAdvice,AfterAdvice等等,稍后以AfterReturningAdvice为例,讨论下spring的通知体系。
Pointcut接口:spring采用Pointcut作为切点的抽象,其中有一个方法返回一个MethodMatcher,作用很明显,就是说切点决定了要切入哪些方法。这里其实是定义了一个匹配规则。比如正则匹配,可以只匹配前缀为save的方法等等。
Advisor:通知器或者说通知者,我们从现实角度来说,通知者当然需要知道要通知什么。所以Advisor依赖于Advice,而Advisor旗下的子接口PointAdvisor还依赖于Pointcut,也就是说这个接口更确切的定位应该是包含了要通知谁和要通知什么,也就是说要能获得Advice和Pointcut。
下面我们用一个例子说明spring的AOP是如何工作的,在spring的bean中有一种特殊的bean,叫FactoryBean。这并不是一个普通的bean,而是用来产生bean的一个bean。这样说起来有点绕口,但这个接口就是用来做这个事的,它是为了实现一系列特殊加工过的bean而产生的接口。比如AOP中,我们其实就是要对一个bean进行增强,进行加工,让它在运行的过程中可以做一些特殊的事情。ProxyFactoryBean就是一个为了AOP实现的特殊的FactoryBean,它的作用很明显就是产生proxy的bean,也就是被我们增强过的bean,在这里给出它的源码。
public class ProxyFactoryBean extends ProxyCreatorSupport implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware { /** * This suffix in a value in an interceptor list indicates to expand globals. */ public static final String GLOBAL_SUFFIX = "*"; protected final Log logger = LogFactory.getLog(getClass()); private String[] interceptorNames; private String targetName; private boolean autodetectInterfaces = true; private boolean singleton = true; private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); private boolean freezeProxy = false; private transient ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader(); private transient boolean classLoaderConfigured = false; private transient BeanFactory beanFactory; /** Whether the advisor chain has already been initialized */ private boolean advisorChainInitialized = false; /** If this is a singleton, the cached singleton proxy instance */ private Object singletonInstance;
}
源码太长,这里只关心两个属性,interpretorNames和targetName,interpretorNames代表需要加强哪些东西以及需要怎样加强,也就是pointcut和advice。targetName代表的则是我们针对谁来做这些加强,即我们的目标对象。
首先interpretorNames是必须赋予的属性,这个属性指定了通知器或者是通知的名字。如果传入的是通知Advice,则会被自动包装为通知器。
targetName是我们要增强的目标对象,这个对象如果有实现的接口,则会采用JDK的动态代理实现,否则将需要第三方的jar包cglib。
下面我们测试一下spring AOP是不是真的可以给目标对象加额外功能。我们首先在spring的容器里添加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-2.5.xsd"> <bean id="testAdvisor" class="com.springframework.aop.test.TestAdvisor"></bean> <bean id="testTarget" class="com.springframework.aop.test.TestTarget"></bean> <bean id="testAOP" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetName"> <value>testTarget</value> </property> <property name="interceptorNames"> <list> <value>testAdvisor</value> </list> </property> </bean> </beans>
下面是目标对象TestTarget。
public class TestTarget{ public void test() { System.out.println("target.test()"); } public void test2(){ System.out.println("target.test2()"); } }
下面是通知器。
public class TestAdvisor implements PointcutAdvisor{ public Advice getAdvice() { return new TestAfterAdvice(); } public boolean isPerInstance() { return false; } public Pointcut getPointcut() { return new TestPointcut(); } }
通知器里自定义的通知(advice)和切点(pointcut),分别代表通知什么和在什么地方通知。首先是通知。
public class TestAfterAdvice implements AfterReturningAdvice{ public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("after " + target.getClass().getSimpleName() + "." + method.getName() + "()"); } }
下面是切点。
public class TestPointcut implements Pointcut{ public ClassFilter getClassFilter() { return ClassFilter.TRUE; } public MethodMatcher getMethodMatcher() { return new MethodMatcher() { public boolean matches(Method method, Class<?> targetClass, Object[] args) { if (method.getName().equals("test")) { return true; } return false; } public boolean matches(Method method, Class<?> targetClass) { if (method.getName().equals("test")) { return true; } return false; } public boolean isRuntime() { return true; } }; } }
切点只在目标对象的test方法执行完后打印一下。代码就这些,我们来测试一下。
public class TestAOP { public static void main(String[] args) { ApplicationContext applicationContext = new FileSystemXmlApplicationContext("classpath:beans.xml"); TestTarget target = (TestTarget) applicationContext.getBean("testAOP"); target.test(); System.out.println("------无敌分割线-----"); target.test2(); } }
打印结果。
我们增强的afterReturningAdvice已经起作用了。这里只是演示一下spring aop的实现,实际开发中不要这么用。
我相信很多人都能看懂以上代码逻辑,下一篇文章,我们顺着spring源码,看一下spring到底是如何实现的以上逻辑。