Spring AOP使用简介

Spring的两大核心功能是IOC和AOP。当我们使用Spring的AOP功能时是很方便的。只需要进行下面的配置即可。

@Component
@Aspect
public class MyAspect { //PointCut匹配的方法必须是Spring中bean的方法
//Pointcut可以有下列方式来定义或者通过&& || 和!的方式进行组合.
//下面定义的这些切入点就可以通过&& ||组合 private static Logger logger = LoggerFactory.getLogger(MyAspect.class); //*:代表方法的返回值可以是任何类型
//整个表达式匹配controller包下面任何的的echo方法,方法入参乐意是任意
@Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(..))")
public void pointCut1(){} @Before("pointCut1()")
public void befor(){
logger.info("前置通知vvvv...");
logger.info("我要做些事情...");
}
}

然后再开启注解

//自动选择合适的AOP代理
//传统xml这样配置:<aop:aspectj-autoproxy/> //exposeProxy = true属性设置成true,意思是将动态生成的代理类expose到AopContext的ThreadLocal线程
//可以通过AopContext.currentProxy();获取到生成的动态代理类。 //proxyTargetClass属性设置动态代理使用JDK动态代理还是使用CGlib代理,设置成true是使用CGlib代理,false的话是使用JDK动态代理 //注意:如果使用Spring Boot的话,下面的配置可以不需要。AopAutoConfiguration这个自动配置类中已经自动开启了AOP
//默认使用CGLIB动态代理,Spring Boot配置的优先级高于下面的配置 @Configuration
@EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = false)
public class AopConfig { }

通上面的配置,当我们调用controller包下面的任何类的echo方法时就会触发前置通知。其实这个说法不是很准确。因为我们调用的类已经不是我们自己写的类了。而是Spring框架通过动态代理生成的类。

稍微了解一点Spring AOP的同学都会知道Spring的AOP是通过动态代理实现的。那Spring是怎么生成动态代理类,并将Advice织入代理类的呢?整个流程是怎样的呢?下面就分析下Spring生成动态代理类的过程。

需要说明下的是,本博客旨在梳理整个AOP动态代理的过程,细节方面需要大家自己去看。

@EnableAspectJAutoProxy干了些啥

如果让你从头开始研究下AOP的原理,你是不是一头雾水,根本不知道从何入手。但其实看Spring的代码有个小技巧:如果你要研究一个功能,可以从开启这个功能的Enable注解开始看。Spring的很多功能都是通过Enable注解开启的,所以这些注解肯定和这些功能相关。

那么这边我们可以从@EnableAspectJAutoProxy这个注解开始着手,看下这个注解做了些什么操作。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
//设置为true的话就一直使用cglib动态代理
//设置为false的话,对于接口使用jdk动态代理,对于类使用cglib代理
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}

看到上面的@Impoer注解,我们很自然就会想到去看AspectJAutoProxyRegistrar这个类。

//AspectJAutoProxyRegistrar源代码
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { /**
* 主要作用也就是注册AnnotationAwareAspectJAutoProxyCreator
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//注册AnnotationAwareAspectJAutoProxyCreator的BeanDefinition
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
//给上面注册的BeanDefinition中添加两个属相proxyTargetClass和exposeProxy
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
} }

我们可以看到上面的类中也没干什么特别的事情,就注册了一个BeanDefinition。如果我们点进去看下AnnotationAwareAspectJAutoProxyCreator这个类的源代码会发现这个类竟然实现了InstantiationAwareBeanPostProcessor这个接口。熟悉Spring尿性的朋友会敏锐的感觉到Spring可能是在postProcessBeforeInstantiation或者postProcessAfterInstantiation这些方法中对Bean进行动态代理的。

“大胆假设,小心求证”,让我们带着这个猜想去看看AnnotationAwareAspectJAutoProxyCreator到底干了些什么?

AnnotationAwareAspectJAutoProxyCreator生成动态代理类

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName); if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// 一般不指定CustomTargetSource,所以不会进入这段代码,所以关键代码在
// postProcessAfterInitialization中
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}

下面是创建动态代理类的关键代码。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
//这边是创建代码类的关键代码
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
} // Create proxy if we have advice.
//获取当前Bean配置的advice,这步是关键
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//创建代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

层层dedug进去我们能看到下面这段代码,我们口中常说的JDK动态代理和Cglib动态代理就是在这边生成的。

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
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() || Proxy.isProxyClass(targetClass)) {
//生成JDK动态代理
return new JdkDynamicAopProxy(config);
}
//生成Cglib动态代理
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
/**
* Determine whether the supplied {@link AdvisedSupport} has only the
* {@link org.springframework.aop.SpringProxy} interface specified
* (or no proxy interfaces specified at all).
*/
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}

到此,我们已经简单分析了Spring动态代理类的生成流程。

PS:关于InstantiationAwareBeanPostProcessor接口和BeanPostProcessor接口大家可以自行了解下,这两个接口是Spring中非常重要的接口。看懂了这两个接口,Spring很多“神秘”的功能你就能理解了。

简单总结

通过上面分析,其实我们发现如果不去看AOP动态代理类生成的细节的话,整个Spring AOP的流程还是挺简单的:

  • @EnableAspectJAutoProxy注解通过AopConfigUtils这个工具类注册AnnotationAwareAspectJAutoProxyCreator这个类,这个类实现了InstantiationAwareBeanPostProcessor接口,所以会在Bean实例化前后对Bean做一系列额外的操作;
  • AnnotationAwareAspectJAutoProxyCreator的postProcessAfterInitialization中会找出所有和当前Bean相关的Advice,如果找到就创建相应的动态代理类,如果找不到就不生成,返回原始类。

所以整个大流程就这么简单。

一些重要类:

  • @EnableAspectJAutoProxy;
  • AspectJAutoProxyRegistrar:注册AnnotationAwareAspectJAutoProxyCreator
  • AnnotationAwareAspectJAutoProxyCreator:AOP动态代理自动生成的处理类,其他类似的类有AspectJAwareAdvisorAutoProxyCreator和InfrastructureAdvisorAutoProxyCreator等;
  • AopConfigUtils:AOP配置工具类
  • ProxyFactory:代理工厂
  • AopProxy接口:常见实现类ObjenesisCglibAopProxy、JdkDynamicAopProxy

参考

Spring系列.AOP原理简析的更多相关文章

  1. Spring系列.@EnableRedisHttpSession原理简析

    在集群系统中,经常会需要将Session进行共享.不然会出现这样一个问题:用户在系统A上登陆以后,假如后续的一些操作被负载均衡到系统B上面,系统B发现本机上没有这个用户的Session,会强制让用户重 ...

  2. Spring 核心组件工作原理简析

    Spring Framework 的核心组件有三个: Spring Core,Spring Context 和 Spring Beans,它们奠定了 Spring 的基础并撑起了 Spring 的框架 ...

  3. Java Android 注解(Annotation) 及几个常用开源项目注解原理简析

    不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...

  4. PHP的错误报错级别设置原理简析

    原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...

  5. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

  6. spring ioc aop 原理

    spring ioc aop 原理 spring ioc aop 的原理 spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分. 在传统的程序设计中,当调 ...

  7. [转载] Thrift原理简析(JAVA)

    转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...

  8. Spring系列.事务管理原理简析

    Spring的事务管理功能能让我们非常简单地进行事务管理.只需要进行简单的两步配置即可: step1:开启事务管理功能 @Configuration //@EnableTransactionManag ...

  9. 基于IdentityServer4的OIDC实现单点登录(SSO)原理简析

    写着前面 IdentityServer4的学习断断续续,兜兜转转,走了不少弯路,也花了不少时间.可能是因为没有阅读源码,也没有特别系统的学习资料,相关文章很多园子里的大佬都有涉及,有系列文章,比如: ...

随机推荐

  1. Yii2.0 URL美化功能Nginx与Apache配置文件

    NGinx: location / { index index.html index.htm index.php; try_files $uri $uri/ /index.php$is_args$ar ...

  2. 《机器学习Python实现_09_02_决策树_CART》

    简介 CART树即分类回归树(classification and regression tree),顾名思义,它即能用作分类任务又能用作回归任务,它的应用比较广泛,通常会用作集成学习的基分类器,总得 ...

  3. vue中使用vue-qrcode生成二维码

    要使用二维码,引入一个包就可以了,使用非常简单,话不多说,看代码吧 //1,引入, import VueQrcode from '@xkeshi/vue-qrcode'; Vue.component( ...

  4. [COCOS2DX-LUA]0-005.cocos2dx中关于全面屏和折叠屏的适配的一些见解

    1.随着科技的发展,我们可以看到从iphoneX的刘海屏开始,引发了各种全面屏和异形屏的出现.这是科技的进步,但是对于各大的应用厂商来说,苦不堪言. 2.当然 ,吐槽归吐槽,我们还是要理智的去对待这个 ...

  5. Spring/SpringBoot常用注解总结

    转自:[Guide哥] 0.前言 可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景.对于每一个注解我都说了具体用法,掌握搞懂,使 ...

  6. Netty源码学习系列之2-ServerBootstrap的初始化

    前言 根据前文我们知道,NioEventLoopGroup和NioEventLoop是netty对Reactor线程模型的实现,而本文要说的ServerBootstrap是对上面二者的整合与调用,是一 ...

  7. Spring ( 三 ) Spring的Bean的装配与生命周期、专用测试

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.对象的生命周期 1.IOC之Bean的生命周期 创建带有生命周期方法的bean public cla ...

  8. 结合 AOP 轻松处理事件发布处理日志

    结合 AOP 轻松处理事件发布处理日志 Intro 前段时间,实现了 EventBus 以及 EventQueue 基于 Event 的事件处理,但是没有做日志(EventLog)相关的部分,原本想增 ...

  9. Java实现 LeetCode 599 两个列表的最小索引总和(使用hash提高效率)

    599. 两个列表的最小索引总和 假设Andy和Doris想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示. 你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅 ...

  10. Java实现 LeetCode 87 扰乱字符串

    87. 扰乱字符串 给定一个字符串 s1,我们可以把它递归地分割成两个非空子字符串,从而将其表示为二叉树. 下图是字符串 s1 = "great" 的一种可能的表示形式. grea ...