spring源码分析(二)Aop
创建日期:2016.08.19
修改日期:2016.08.20-2016.08.21
交流QQ:992591601
参考资料:《spring源码深度解析》、《spring技术内幕》、传值播客spring教学视频
http://www.cnblogs.com/xing901022/p/4264334.html
http://www.cnblogs.com/digdeep/p/4528353.html
一,动态代理、java InvocationHandler实现、Cglib实现
要了解动态代理,可阅读设计模式相关书籍,不难理解。这篇博文我简单解释,动态代理就是与静态代理相对应的,在程序运行过程中动态创建的代理类。代理类可以简单理解为一种对目标类的增强,之后我们要使用目标类的话,只需用代理类就可以了。
实现动态代理有两种方式,其一是java自带的动态代理功能,另外就是使用Cglib库实现。前者的使用有一个必须的条件,那就是目标类必须实现接口。而后者的使用则是对前者的一种补充。
假设我们有这样两个bean,其一为AlienFishServiceBean,不实现任何接口。
其一为FishServiceImpl,实现FishService接口。
对于前者,我们需要使用Cglib来实现动态代理功能(示例代码,实现功能:当bean的fishName字段不为空时,才能调用该bean的方法):
- package cn;
- import java.lang.reflect.Method;
- import cn.aop.service.AlienFishServiceBean;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
- /**
- * @ClassName: CGlibProxyFactory
- * @Description: CGlibProxyFactory
- * @author 无名
- * @date 2016-8-14 17:31:48
- * @version 1.0
- */
- public class CGlibProxyFactory implements MethodInterceptor {
- private Object targetObject;
- public Object createProxyIntance(Object targetObject) {
- this.targetObject = targetObject;
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(this.targetObject.getClass());
- enhancer.setCallback(this);
- return enhancer.create();
- }
- public Object intercept(Object proxy, Method method, Object[] args,
- MethodProxy methodProxy) throws Throwable {
- AlienFishServiceBean bean = (AlienFishServiceBean) this.targetObject;
- Object result = null;
- if(bean.getFishName()!=null) {
- result = methodProxy.invoke(targetObject, args);
- }
- return result;
- }
- }
对于后者,我们可以使用java自带的动态代理功能(示例代码,实现功能:当bean的fishName字段不为空时,才能调用该bean的方法):
- package cn;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import cn.aop.service.FishService;
- import cn.aop.service.FishServiceImpl;
- /**
- * @ClassName: JDKProxyFactory
- * @Description: JDKProxyFactory
- * @author 无名
- * @date 2016-8-13 下午11:55:31
- * @version 1.0
- */
- public class JDKProxyFactory implements InvocationHandler {
- private Object targetObject;
- public Object createInstance(Object targetObject) {
- this.targetObject = targetObject;
- return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
- this.targetObject.getClass().getInterfaces(), this);
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- FishService fs = (FishServiceImpl)targetObject;
- Object result = null;
- if(fs.getFishName() != null) {
- result = method.invoke(targetObject, args);
- }
- return result;
- }
- }
上述两个代理方式使用时,先用代理类工厂创建对应代理类,然后使用代理类即可(此代理类是对目标类的代理,‘增强了’目标类的一些方面)。
二,Spring Aop
spring aop需要的jar包:
org.springframework.aop-xxx.jar(spring),aopalliance-1.0.jar,aspectjrt.jar, aspectjweaver.jar,cglib.jar
spring aop是在动态代理这种设计模式的基础之上的。
这里我说下aop的几个基本概念,都是基于我自己的理解,简单粗暴:
aspect:面,可以理解为一个事务,对该事务做对应处理。
pointcut:切入点,对应于面具体的切入的地方。
advice:spring定义了四个advice, BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice。在触及面和点的时候,根据配置,执行对应通知。
spring aop的实现由两种,其一是配置文件方式,另外是注解方式。
我们首先,用配置文件方式:
目标类,接口和实现类:
- package cn.service;
- public interface FishService {
- public void say00();
- public void say01();
- }
- package cn.service.impl;
- import cn.service.FishService;
- public class FishServiceBean implements FishService {
- public void say00() {
- System.out.println("I am fish 00");
- }
- public void say01() {
- System.out.println("I am fish 01");
- }
- }
下面的类提供对应的advice
- package cn.service;
- import org.aspectj.lang.ProceedingJoinPoint;
- /**
- * 切面
- *
- */
- public class MyInterceptor {
- public void doBefore() {
- System.out.println("before");
- }
- public void doAfter() {
- System.out.println("after");
- }
- public void doFinal() {
- System.out.println("final");
- }
- public void doThrowing() {
- System.out.println("throwing");
- }
- public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
- System.out.println("进入方法");
- Object result = pjp.proceed();
- System.out.println("退出方法");
- return result;
- }
- }
配置文件(aop:config内设置aop,切面里设置切入点,及几种advice):
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
- <aop:aspectj-autoproxy/>
- <bean id="fishService" class="cn.service.impl.FishServiceBean"></bean>
- <bean id="aspetbean" class="cn.service.MyInterceptor"/>
- <aop:config>
- <aop:aspect id="asp" ref="aspetbean">
- <aop:pointcut id="mycut" expression="execution(* cn.service..*.*(..))"/>
- <aop:before pointcut-ref="mycut" method="doBefore"/>
- <aop:after-returning pointcut-ref="mycut" method="doFinal"/>
- <aop:after-throwing pointcut-ref="mycut" method="doThrowing"/>
- <aop:after pointcut-ref="mycut" method="doAfter"/>
- <aop:around pointcut-ref="mycut" method="doBasicProfiling"/>
- </aop:aspect>
- </aop:config>
- </beans>
测试类:
- package junit.test;
- import org.junit.BeforeClass;
- import org.junit.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import cn.service.FishService;
- public class SpringAOPTest {
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- }
- @Test public void interceptorTest(){
- ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");
- FishService fishService = (FishService)cxt.getBean("fishService");
- fishService.say00();
- fishService.say01();
- }
- }
运行结果:
与配置文件方式相对应的便是注解的方式
注解方式只需在spring的xml文件里保留 <aop:aspectj-autoproxy/> 就可以了。
而上面对应的MyInterceptor类需要多写相对的注解(著名切入点、advice等)。
三,Spring源码分析
首先自己设想一下spring实现aop的思路,大概是:1,获取、寻找所有bean,如果为AspectJ注解的类,则进行对应处理。
2,对标记为AspectJ注解的类进行增强器的提取(前文提到动态代理是对目标类的一种增强)
3,创建动态代理。
首先看一个类AopNamespaceHandler,这里是aop注解对应的解析器:
- public class AopNamespaceHandler extends NamespaceHandlerSupport {
- public void init() {
- // In 2.0 XSD as well as in 2.1 XSD.
- registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
- registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
- registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
- // Only in 2.0 XSD: moved to context namespace as of 2.1
- registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
- }
- }
我们看registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());这一句,可知其内涵是向NamespaceHandlerSupport这一类的
private final Map<String, BeanDefinitionParser> parsers =
new HashMap<String, BeanDefinitionParser>(); 这一map数据结构中注册对应解析器。
此后 一旦遇到aspectj-autoproxy注解,便自然会从map中取到对应解析器,并使用解析器AspectJAutoProxyBeanDefinitionParser解析。AspectJAutoProxyBeanDefinitionParser是继承了BeanDefinitionParser接口的。
- public BeanDefinition parse(Element element, ParserContext parserContext) {
- AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
- extendBeanDefinition(element, parserContext);
- return null;
- }
- public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
- ParserContext parserContext, Element sourceElement) {
- BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
- parserContext.getRegistry(), parserContext.extractSource(sourceElement));
- useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
- registerComponentIfNecessary(beanDefinition, parserContext);
- }
首先看下进入该函数时参数的状态:
sourceElement的name为aop:aspectj-autoproxy。这个函数就是专门用于注册aop:aspectj注解的。
这个方法三句代码,各有其作用,分别是:1注册AnnotationAwareAspectJAutoProxyCreator(AOP的实现都靠这个),返回的BeanDefinition设置对应的AnnotationAwareAspectJAutoProxyCreator
2处理proxy-target-class(CgLib或jdk)以及expose-proxy属性
3注册组件并通知
从spring的 DefaultAopProxyFactory类入手,该类继承自AopProxyFactory接口。
- package org.springframework.aop.framework;
- /**
- * Interface to be implemented by factories that are able to create
- * AOP proxies based on {@link AdvisedSupport} configuration objects.
- *
- * <p>Proxies should observe the following contract:
- * <ul>
- * <li>They should implement all interfaces that the configuration
- * indicates should be proxied.
- * <li>They should implement the {@link Advised} interface.
- * <li>They should implement the equals method to compare proxied
- * interfaces, advice, and target.
- * <li>They should be serializable if all advisors and target
- * are serializable.
- * <li>They should be thread-safe if advisors and target
- * are thread-safe.
- * </ul>
- *
- * <p>Proxies may or may not allow advice changes to be made.
- * If they do not permit advice changes (for example, because
- * the configuration was frozen) a proxy should throw an
- * {@link AopConfigException} on an attempted advice change.
- *
- * @author Rod Johnson
- * @author Juergen Hoeller
- */
- public interface AopProxyFactory {
- /**
- * Create an {@link AopProxy} for the given AOP configuration.
- * @param config the AOP configuration in the form of an
- * AdvisedSupport object
- * @return the corresponding AOP proxy
- * @throws AopConfigException if the configuration is invalid
- */
- AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
- }
我们看最核心的createAopProxy方法:
- public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
- if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
- Class targetClass = config.getTargetClass();
- if (targetClass == null) {
- throw new AopConfigException("TargetSource cannot determine target class: " +
- "Either an interface or a target is required for proxy creation.");
- }
- if (targetClass.isInterface()) {
- return new JdkDynamicAopProxy(config);
- }
- if (!cglibAvailable) {
- throw new AopConfigException(
- "Cannot proxy target class because CGLIB2 is not available. " +
- "Add CGLIB to the class path or specify proxy interfaces.");
- }
- return CglibProxyFactory.createCglibProxy(config);
- }
- else {
- return new JdkDynamicAopProxy(config);
- }
- }
这段代码逻辑很清晰,判断如果目标类实现接口则使用jdk创建代理,否则使用cglib。
再仔细看这段代码,发现无论是jdk还是cglib,都要使用AdvisedSupport config这个参数。
设置断点,查看该变量,发现果然信息量很大,对应的AdvisedSupport类代码我就不贴了,看下面截图也可以想象个大概了。
具体看下advice的内容,我们配置文件里配置的内容都在里面了:
至于这个config是怎样生成的,其实很好想象,就是前面讲的,对应注解的解析,注册。
new JdkDynamicAopProxy(config)
- /**
- * Construct a new JdkDynamicAopProxy for the given AOP configuration.
- * @param config the AOP configuration as AdvisedSupport object
- * @throws AopConfigException if the config is invalid. We try to throw an informative
- * exception in this case, rather than let a mysterious failure happen later.
- */
- public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
- Assert.notNull(config, "AdvisedSupport must not be null");
- if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
- throw new AopConfigException("No advisors and no TargetSource specified");
- }
- this.advised = config;
- }
CglibProxyFactory.createCglibProxy(config)
- /**
- * Create a new Cglib2AopProxy for the given AOP configuration.
- * @param config the AOP configuration as AdvisedSupport object
- * @throws AopConfigException if the config is invalid. We try to throw an informative
- * exception in this case, rather than let a mysterious failure happen later.
- */
- public Cglib2AopProxy(AdvisedSupport config) throws AopConfigException {
- Assert.notNull(config, "AdvisedSupport must not be null");
- if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
- throw new AopConfigException("No advisors and no TargetSource specified");
- }
- this.advised = config;
- this.advisedDispatcher = new AdvisedDispatcher(this.advised);
- }
都是用config设置对应的advise属性。
小小总结:最后我又单步调试一通,结合上一章节内容《spring源码分析(一)IoC、DI》,发现AOP代理类的创建时CreateBean这一阶段开始的,其实这时候就是对应目标类用代理类取代了。
这是断点运行时,观察createBean返回的结果,可见创建的fishService bean实质上是JdkDynamicAopProxy代理类。在那之后我们使用fishService实际上就是使用对应的JdkDynamicAopProxy代理类了,之前注册的那些advice也就生效了:
(在这一过程中值得一提的还有AbstractAutoProxyCreator的postProcessAfterInitialization方法,正如注释所说,作用是Create a proxy with the configured interceptors if the bean is identified as one to proxy by the subclass.
AnnotationAwareAspectJAutoProxyCreator的findCandidateAdvisors方法,作用是获取增强器)
spring源码分析(二)Aop的更多相关文章
- Spring源码分析之AOP从解析到调用
正文: 在上一篇,我们对IOC核心部分流程已经分析完毕,相信小伙伴们有所收获,从这一篇开始,我们将会踏上新的旅程,即Spring的另一核心:AOP! 首先,为了让大家能更有效的理解AOP,先带大家过一 ...
- 【Spring源码分析】AOP源码解析(上篇)
前言 前面写了六篇文章详细地分析了Spring Bean加载流程,这部分完了之后就要进入一个比较困难的部分了,就是AOP的实现原理分析.为了探究AOP实现原理,首先定义几个类,一个Dao接口: pub ...
- Spring源码分析之AOP
1.AOP简介 AOP即面向切面编程(Aspect Oriented Programming),通过预编译方式及运行期动态代理实现程序功能的统一维护的一种技术.使用aop对业务逻辑的各个部分进行隔离, ...
- 【Spring源码分析】AOP源码解析(下篇)
AspectJAwareAdvisorAutoProxyCreator及为Bean生成代理时机分析 上篇文章说了,org.springframework.aop.aspectj.autoproxy.A ...
- Spring源码分析笔记--AOP
核心类&方法 BeanDefinition Bean的定义信息,封装bean的基本信息,从中可以获取类名.是否是单例.是否被注入到其他bean中.是否懒加载.bean依赖的bean的名称等. ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- Spring源码分析之`BeanFactoryPostProcessor`调用过程
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...
- Spring源码分析之循环依赖及解决方案
Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostPro ...
- Spring源码分析——BeanFactory体系之抽象类、类分析(二)
上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...
随机推荐
- saas简介
SaaS是Software-as-a-Service(软件即服务)的简称,随着互联网技术的发展和应用软件的成熟, 在21世纪开始兴起的一种完全创新的软件应用模式.它与“on-demand softwa ...
- 框架介绍thinkphp
ThinkPHP是一个免费开源的,快速.简单的面向对象的 轻量级PHP开发框架 ,创立于2006年初,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业应用开发而诞生的.ThinkPH ...
- DWG2SHP DXF2SHP 如何把AutoCAD的DWG,DXF文件转换为Esri ArcGIS的Shape文件
dwg是AutoCAD创立的一种图纸保存格式,已经成为二维CAD的标准格式,很多其他CAD为了兼容AutoCAD,也直接使用dwg作为默认工作文件. 地图shape文件由ESRI开发,一个ESRI的s ...
- KIWI Syslog配置
日志服务器Kiwi+Syslogd+8.3.7破解版 Window收集服务器日志evtsys_exe_32 默认地,kiwi使用UDP 514端口接收日志数据,安装成功后即可接收日志 使用命令nets ...
- html form 提交表单的一些问题
1. 如果在一个form里有summit按钮,则只能提交本form的内容
- 使用CSS中的meta实现web定时刷新或跳转的方法
这篇文章主要介绍了使用CSS中的meta实现web定时刷新或跳转的方法,比使用JavaScript脚本实现起来更加简单一些,需要的朋友可以参考下 meta源信息功能之页面定时跳转与刷新 几乎所有的网页 ...
- Android PullToZoomListView实现放大回弹效果
另外一个相同项目的地址https://github.com/Frank-Zhu/PullZoomView 转自http://blog.csdn.net/wangjinyu501/article/det ...
- 攒机I7
CPU : I7 4790K +Z97 = 3200 散热器 :九州风神玄冰400 = 99 硬盘 :希捷 1TB 64M = 310 机箱: 金河田超越白 = 200 内存 DDR3金士顿8G = ...
- c#编程指南(十) 平台调用P-INVOKE完全掌握, 字符串和指针
可以说新手使用P-INVOKE最开始的头疼就是C#和C++的字符串传递,因为这里涉及到两个问题. 第一:C#的string和C++的字符串首指针如何对应. 第二:字符串还有ANSI和UNICODE(宽 ...
- iOS 7 UI Transition – Porting View Controller Layouts from iOS 6
http://www.mobinett.com/2013/08/19/ios7-ui-transition-porting-view-controller-layouts-ios6/