一、SpringAOP應用
- 先搭建一個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>
- 定義一個業務邏輯類,作為切面
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;
}
}
- 定義一個切面類,新增通知方法
- 前置通知(@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+"}");
}
}
- 寫一個controller測試
@RequestMapping("/testaop")
@ResponseBody
public Integer testaop(Integer x,Integer y){
Integer result = calculationService.add(x, y);
return result;
}
- 測試
add執行。。。@Before:引數列表是:{[2, 3]}
add結束。。。@After
add正常返回。。。@AfterReturning:執行結果:{5}
二、原始碼分析
主線流程圖:
- 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
- 進入@EnableAspectJAutoProxy註解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//引入了AspectJAutoProxyRegistrar
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
- 進入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);
}
}
}
}
- 進入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)方法。
- 方法內容如下
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;
}
}
- 可以看出這裡已經在開始建立動態代理了
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());
}
- 學過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));
}
}
}
- Cglib的代理類是CglibAopProxy、ObjenesisCglibAopProxy,JDK的代理類是JdkDynamicAopProxy。在這些類裡面對目標類進行了代理,在執行方法的時候就是執行的代理類的方法,而實現了切面程式設計的效果。
- 主線流程就是這些了,還有一個沒說的就是我們如何獲取的切面方法,@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;
}
- 他會找到Aspect類,然後遍歷裡面的方法,並獲取Pointcut,然後構造出Advisor,加入到集合List
advisors裡,供動態代理時使用