一、SpringAOP应用

  1. 先搭建一个SpringBoot项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mmc</groupId>
<artifactId>springboot-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-study</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
  1. 定义一个业务逻辑类,作为切面
public interface CalculationService {

    /**
* 加法运算
* @param x
* @param y
* @return
*/
public Integer add(Integer x,Integer y);
} /**
* @description:
* @author: mmc
* @create: 2020-06-01 14:22
**/
@Service
public class CalculationServiceImpl implements CalculationService { @Override
public Integer add(Integer x, Integer y) {
if(x==null||y==null){
throw new NullPointerException("参数不能为空");
}
return x+y;
}
}
  1. 定义一个切面类,添加通知方法
  • 前置通知(@Before):logStart:在目标方法(div)运行之前运行
  • 后置通知(@After):logEnd:在目标方法(add)运行结束之后运行(无论方法正常结束还是异常结束)
  • 返回通知(@AfterReturning):logReturn:在目标方法(add)正常返回之后运行
  • 异常通知(@AfterThrowing):logException:在目标方法(add)出现异常以后运行
  • 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())

/**
* @description: 切面类
* @author: mmc
* @create: 2020-06-01 14:24
**/
@Aspect
@Component
public class LogAspects { //抽取公共的切入点表达式
//1、本类引用
//2、其他的切面引用
@Pointcut("execution(public Integer com.mmc.springbootstudy.service.CalculationService.*(..))")
public void pointCut(){}; @Before("pointCut()")
public void logStart(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}");
} @After("pointCut()")
public void logEnd(JoinPoint joinPoint){
System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After");
} //JoinPoint一定要出现在参数表的第一位
@AfterReturning(value="pointCut()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result){
System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
} @AfterThrowing(value="pointCut()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception){
System.out.println(""+joinPoint.getSignature().getName()+"异常。。。异常信息:{"+exception+"}");
}
}
  1. 写一个controller测试
@RequestMapping("/testaop")
@ResponseBody
public Integer testaop(Integer x,Integer y){
Integer result = calculationService.add(x, y);
return result;
}
  1. 测试

add运行。。。@Before:参数列表是:{[2, 3]}

add结束。。。@After

add正常返回。。。@AfterReturning:运行结果:{5}

二、源码分析

主线流程图:


  1. spring.factories文件里引入了AopAutoConfiguration类
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration { @Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
//看配置文件,如果配置的spring.aop.proxy-target-class为false则引入JdkDynamicAutoProxyConfiguration
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration { } @Configuration
//开启AspectJAutoProxy
@EnableAspectJAutoProxy(proxyTargetClass = true)
//看配置文件,如果配置的spring.aop.proxy-target-class为true则引入CglibAutoProxyConfiguration
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
public static class CglibAutoProxyConfiguration { } }

在包目录下找到配置文件,并且发现他的值为true

在上面的方法上有EnableAspectJAutoProxy注解,并传入了proxyTargetClass=true

  1. 进入@EnableAspectJAutoProxy注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//引入了AspectJAutoProxyRegistrar
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false; boolean exposeProxy() default false;
}
  1. 进入AspectJAutoProxyRegistrar类
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
AspectJAutoProxyRegistrar() {
} public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//注册了自动自动代理类
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
} if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
} }
}
  1. 进入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法里面
 public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

可以看到返回了一个BeanDefinition,里面的BeanClass类型是AnnotationAwareAspectJAutoProxyCreator,这个类看名字是一个AOP的动态代理创建类,里面没有啥可疑的方法。在IDEA里按Ctrl+H看他的继承结构。有一个父类AbstractAutoProxyCreator,这个类实现了BeanPostProcessor接口。这个接口是Bean的扩展接口,在bean初始化完成后会调用到他的postProcessAfterInitialization(Object bean, String beanName)方法。


  1. 方法内容如下
 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
//如果有必要,进行包装
return this.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;
} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
//获取切面的方法,第9点那里展开讨论
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//创建动态代理
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}
  1. 可以看出这里已经在开始创建动态代理了
  protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
}
//动态代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (this.shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
} else {
this.evaluateProxyInterfaces(beanClass, proxyFactory);
}
} Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
//切面那里的方法
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
this.customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (this.advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//获取动态代理类
return proxyFactory.getProxy(this.getProxyClassLoader());
}
  1. 学过AOP的人都知道动态代理的方式有两种,一种JDK代理,一种CGLIB动态代理。那么Spring里面是怎么选择的呢?答案就在这里:
 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 1.config.isOptimize()是否使用优化的代理策略,目前使用与CGLIB
// config.isProxyTargetClass() 是否目标类本身被代理而不是目标类的接口
// hasNoUserSuppliedProxyInterfaces()是否存在代理接口 if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
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.");
} else {
//目标类不是接口或不是代理类就使用cglib代理
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
  1. Cglib的代理类是CglibAopProxy、ObjenesisCglibAopProxy,JDK的代理类是JdkDynamicAopProxy。在这些类里面对目标类进行了代理,在执行方法的时候就是执行的代理类的方法,而实现了切面编程的效果。
  2. 主线流程就是这些了,还有一个没说的就是我们如何获取的切面方法,@Before("pointCut()")这些注解又是如何生效的?再回到AbstractAutoProxyCreator的wrapIfNecessary()方法

    里面有这句代码:
 Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);

  @Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);
return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
} protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//查找候选的要切面附加的方法,这里加进去的
List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
this.extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);
} return eligibleAdvisors;
}
  1. 他会找到Aspect类,然后遍历里面的方法,并获取Pointcut,然后构造出Advisor,加入到集合List advisors里,供动态代理时使用

SpringAOP使用及源码分析(SpringBoot下)的更多相关文章

  1. SURF算法与源码分析、下

    上一篇文章 SURF算法与源码分析.上 中主要分析的是SURF特征点定位的算法原理与相关OpenCV中的源码分析,这篇文章接着上篇文章对已经定位到的SURF特征点进行特征描述.这一步至关重要,这是SU ...

  2. Hadoop之HDFS原理及文件上传下载源码分析(下)

    上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信.本篇将继续介绍hdfs文 ...

  3. 细说并发5:Java 阻塞队列源码分析(下)

    上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...

  4. Tomcat处理HTTP请求源码分析(下)

    转载:http://www.infoq.com/cn/articles/zh-tomcat-http-request-2 很多开源应用服务器都是集成tomcat作为web container的,而且对 ...

  5. 源码分析springboot自定义jackson序列化,默认null值个性化处理返回值

    最近项目要实现一种需求,对于后端返回给前端的json格式的一种规范,不允许缺少字段和字段值都为null,所以琢磨了一下如何进行将springboot的Jackson序列化自定义一下,先看看如何实现,再 ...

  6. 源码分析SpringBoot启动

    遇到一个问题,需要从yml文件中读取数据初始化到static的类中.搜索需要实现ApplicationRunner,并在其实现类中把值读出来再set进去.于是乎就想探究一下SpringBoot启动中都 ...

  7. 【Java入门提高篇】Day24 Java容器类详解(七)HashMap源码分析(下)

    前两篇对HashMap这家伙的主要方法,主要算法做了一个详细的介绍,本篇主要介绍HashMap中默默无闻地工作着的集合们,包括KeySet,values,EntrySet,以及对应的迭代器:HashI ...

  8. Node.js躬行记(20)——KOA源码分析(下)

    在上一篇中,主要分析了package.json和application.js文件,本文会分析剩下的几个文件. 一.context.js 在context.js中,会处理错误,cookie,JSON格式 ...

  9. 神奇的 BlocksKit(1):源码分析(下)

    私有类 _BKObserver _BKObserver 是用来观测属性的对象,它在接口中定义了 4 个属性: @property (nonatomic,readonly,unsafe_unretain ...

随机推荐

  1. dp cf 20190614

    C. Hard problem 这个题目一开始看还感觉比较复杂,但是还是可以写,因为这个决策很简单就是对于这个字符串倒置还是不倒置. 然后我不会一维去转移,直接用二维,第二维用01来表示转移和不转移, ...

  2. 前端自适应样式reset.css

    @charset "utf-8"; /* CSS Document */ html, body, ul, li, ol, dl, dd, dt, p, h1, h2, h3, h4 ...

  3. Appium+Python-项目实践一

    一.前言                            前面讲了环境搭建和常用的元素定位,后续会持续以项目实践的方式去慢慢学习以及整理各方面的知识点,具体不会详细阐述,但会贴上完整代码,想要了 ...

  4. Excel开始,Excel结束,R语言居中

    入职.离职,总公司调往分公司,分公司调往总公司,每月社保.公积金和上月比较有增减.税局导出的为Excel文件,需要和记录对照一番. 用Excel处理,那就是姓名粘贴为两列,条件格式-重复值,没变色的为 ...

  5. Awareness Kit让你的音乐APP脱颖而出,更懂用户,也更动人心

    让你的音乐APP脱颖而出,更懂用户,也更动人心. 场景 情景感知服务能带来什么?   作为音乐发烧友,闲下来的时候总想打开App,享受沉浸在音乐中的放松.然而,App推荐的歌单经常没法满足我的需要,如 ...

  6. FPGA学习心得汇总(手中写代码,心中有电路)

    http://bbs.ednchina.com/BLOG_ARTICLE_2111172.HTM 任何的时序逻辑都可以转换为组合逻辑+D触发器来完成. FPGA内部主要三块:可编程的逻辑单元.可编程的 ...

  7. python语法学习第五天--函数(2)

    命名空间: 命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的. 命名空间提供了在项目中避免名字冲突的一种方法.各个命名空间是独立的,没有任何关系 ...

  8. python循环中对一个列表的赋值问题

    参考:https://www.cnblogs.com/zf-blog/p/10613981.html https://www.cnblogs.com/andywenzhi/p/7453374.html ...

  9. Elasticsearchdump 数据导入/导出

    一.安装过程 Elasticsearchdump 仓库地址,详细使用情况 当前工具主要是用来对ES中的数据进行数据导入/导出,以及对数据迁移相关,使用elasticdump工具需要使用到npm,所以需 ...

  10. mybatis开发,你用 xml 还是注解?我 pick ...

    最近在看公司项目时发现有的项目mybatis是基于注解开发的,而我个人的习惯是基于xml文件开发. 对于mybatis注解开发的原理理解不够,于是翻阅了部分源码,写下此文.主要介绍了mybatis开发 ...