阅读必备
了解spring的IOC容器原理 && 设计模式--代理模式
说明
作者是AOP初学者,文章内容是AOP学习复盘思路,是作者的思考思路,聊以记录,不免会有些漏误,提前说明以免误人时间,思想跑偏
背景
最近在学习spring源码,刚刚结束了IOC的学习,然后学习AOP,相比IOC,AOP对于作者来说更加的琐碎,需要更多的耐心,但通透之后顿觉十分畅快,趁兴而书
AOP
1.作用及思想
作为学习的对象,第一个要了解的是它的作用,之后便是很重要的设计思想,很多文章都有介绍,虽不赘述,但必须知道
2.代理
aop的实现需要借助代理模式,因此理解代理模式也是十分必要,代理模式的文章同样很多,这里同样不做具体介绍
开始 代码环节
找到关键点
// 1. 设置被代理对象(Joinpoint)
AdvisedSupport advisedSupport = new AdvisedSupport();
TargetSource targetSource = new TargetSource(helloService, HelloServiceImpl.class, new Class[]{HelloService.class});
advisedSupport.setTargetSource(targetSource);
// 2.设置拦截器(Advice)
TimerInterceptor timerInterceptor = new TimerInterceptor();
advisedSupport.setMethodInterceptor(timerInterceptor);
// 3.创建代理(Proxy)
JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
HelloService helloServiceProxy = (HelloService) jdkDynamicAopProxy.getProxy();
// 4.基于AOP的调用
helloServiceProxy.helloWorld();
上述代码为一次aop的普通Jdk动态代理实现,没有整合spring框架,可以看到十分清晰的实现思路
设置对象-->设置拦截器-->生成代理对象-->调用方法
实现aop有两个重要的对象
1.TargetSource
2.TimerInterceptor
TimerInterceptor是拦截方法,很容易理解,TargetSource是拦截对象,是我们要用拦截方法增强功能的对象(比如业务类)
所以看来实现aop需要做好两件事情,第一个是定义好拦截方法,第二个是找到拦截对象,接下来分析AOP整合spring是如何实现的
关键点深入
拆开来看拦截方法和拦截对象
拦截方法
定义好功能
public class TimerInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long time = System.nanoTime();
System.out.println("Invocation of Method " + invocation.getMethod().getName() + " start!");
Object proceed = invocation.proceed();
System.out.println("Invocation of Method " + invocation.getMethod().getName() + " end! takes " + (System.nanoTime() - time)
+ " nanoseconds.");
return proceed;
}
}
在spring-*.xml(自己定义)中配置好就ok
<bean id="timeInterceptor" class="com.xyz.aop.TimerInterceptor"></bean>
拦截对象
接下来就是如何找到拦截对象
这方面有很多的定义,具体可以参考彻底征服 Spring AOP 之 理论篇,非常感谢永顺的文章
拦截对象的识别获取可以通过AspectJ框架实现,用AspectJ表达式(AspectJExpression)来识别
如果只是使用,就很简单,只需要配置一下
<bean id="aspectjAspect" class="com.xyz.aop.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="timeInterceptor"></property>
<property name="expression" value="execution(* com.xyz.*.*(..))"></property>
</bean>
重点是“execution( com.xyz..*(..))” 及AspectJ表达式(AspectJExpression)
可以看到简单获取拦截对象的配置也是十分容易
织入
那么现在拦截方法和拦截对象都有了,如何将拦截方法增强到拦截对象呢?
这个就涉及到IOC中的BeanPostProcessor,所以需要了解IOC原理
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception;
Object postProcessAfterInitialization(Object bean, String beanName) throws Exception;
}
可以看到BeanPostProcessor在bean的初始化前后能够做些操作,拦截方法就可以通过这两个方法织入到拦截对象中。
织入实现在AspectJAwareAdvisorAutoProxyCreator.postProcessAfterInitialization()方法中实现
public class AspectJAwareAdvisorAutoProxyCreator implements BeanPostProcessor,BeanFactoryAware {
private AbstractBeanFactory beanFactory;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
return bean;
}
/**
* bean实例化后要进行初始化操作,会经过这个方法满足条件则生成相关的代理类并返回
* @param bean
* @param beanName
* @return
* @throws Exception
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
// 如果是切点通知器,则直接返回
if (bean instanceof AspectJExpressionPointcutAdvisor) {
return bean;
}
// 如果是方法拦截器,则直接返回
if(bean instanceof MethodInterceptor){
return bean;
}
// 通过getBeanForType方法加载BeanFactory中所有的PointcutAdvisor(保证了PointcutAdvisor的实例化顺序优于普通bean。)
// AspectJ方式实现织入,这里它会扫描所有的Pointcut,并对bean做织入。
List<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansForType(AspectJExpressionPointcutAdvisor.class);
for (AspectJExpressionPointcutAdvisor advisor : advisors) {
// 匹配要拦截的类
// 使用AspectJExpressionPointcut的matches匹配器,判断当前对象是不是要拦截的类的对象。
if (advisor.getPointCut().getClassFilter().matches(bean.getClass())) {
// ProxyFactory继承了AdvisedSupport,所以内部封装了TargetSource和MethodInterceptor的元数据对象
ProxyFactory advisedSupport = new ProxyFactory();
// 设置切点的方法拦截器
advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
// 设置切点的方法匹配器 利用AspectJ表达式进行方法匹配
// AspectJExpressionPointcutAdvisor里的AspectJExpressionPointcut的getMethodMatcher()方法
advisedSupport.setMethodMatcher(advisor.getPointCut().getMethodMatcher());
// 要拦截的类,生成一个TargetSource(要拦截的对象和器类型)(被代理对象)
TargetSource targetSource = new TargetSource(bean, bean.getClass(), bean.getClass().getInterfaces());
advisedSupport.setTargetSource(targetSource);
// 交给实现了AopProxy接口的getProxy方法的ProxyFatctory区生成代理对象
return advisedSupport.getProxy();
}
}
return bean;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws Exception {
this.beanFactory = (AbstractBeanFactory) beanFactory;
}
}
对比一下最开始的代码,看出逻辑大致相同,
是否为拦截对象(类级别)-->(是)-->设置方法拦截器-->设置方法匹配器-->设置拦截对象-->返回生成的代理对象
只是借助spring,实现了更好的解耦,可以通过xml进行配置。
通过xml配置方式实现aop
看下完整的配置
//业务对象
<bean id="outputService" class="com.xyz.OutputServiceImpl">
</bean>
<bean id="helloService" class="com.xyz.HelloServiceImpl">
<property name="text" value="Hello xmlLoader with bean"></property>
<property name="outputService" ref="outputService"></property>
</bean>
// 织入
<bean id="autoProxyCreator" class="com.xyz.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>
// 拦截方法
<bean id="timeInterceptor" class="com.xyz.aop.TimerInterceptor"></bean>
// 封装拦截方法和切点
<bean id="aspectjAspect" class="com.xyz.aop.AspectJExpressionPointcutAdvisor">
//拦截器注入
<property name="advice" ref="timeInterceptor"></property>
//切点注入 用于匹配拦截对象
<property name="expression" value="execution(* com.xyz.*.*(..))"></property>
</bean>
最后
大致思路就是如此,当然有很多涉及到的要点都没很好的介绍说明,就如同开篇所说,这只是作者的思考思路,是复盘思路,更多的相关源码需要读者自己去探索了,这里推荐一下简化的spring源码--git项目tiny-spring,作者学习该项目时把全部代码亲自敲了一遍,只有自己亲自去实践了才能理解的更加透彻,希望与大家共勉!