SpringBoot之Spring核心AOP详解

发布于:2022-11-09 ⋅ 阅读:(12) ⋅ 点赞:(0) ⋅ 评论:(0)

一,前言

众所周知,Spring 中最重要的两个功能,就是控制反转(IOC)和面向切面编程 (AOP)。

今天这里只对AOP做个基础总结。所谓AOP,即:Aspect Oriented Programming,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。其底层是基于动态代理实现。具体实现方法可参考:设计模式之代理模式

AOP 为我们提供了处理SpringBoot开发的全局化视角,使用得当可以极大提高我们的编程效率。

而在Spring Boot 中使用 AOP 与 Spring 中使用 AOP 几乎没有什么区别,只是使用 java配置和注解代替了原来的 XML 配置。

二,源码解析

AOP的源码在SpringBoot里面要追溯到spring-boot-autoconfigure-2.3.7.RELEASE.jar的jar包里,这里也是springboot的自动配置jar包。

由于在Spring里,AOP通过EnableAspectJAutoProxy注解开启。通过AopAutoConfiguration这个类的详细情况来看,默认情况下,Spring会通过AopAutoConfiguration自动引入这个注解。

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(
    prefix = "spring.aop",
    name = {"auto"},
    havingValue = "true",
    matchIfMissing = true
)
public class AopAutoConfiguration {
    public AopAutoConfiguration() {
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"proxy-target-class"},
        havingValue = "true",
        matchIfMissing = true
    )
    static class ClassProxyingConfiguration {
        ClassProxyingConfiguration(BeanFactory beanFactory) {
            if (beanFactory instanceof BeanDefinitionRegistry) {
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory;
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

        }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({Advice.class})
    static class AspectJAutoProxyingConfiguration {
        AspectJAutoProxyingConfiguration() {
        }

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "true",
        //默认情况下,是没有spring.aop.proxy-target-class这个配置项的。
            matchIfMissing = true
        )
        static class CglibAutoProxyConfiguration {
            CglibAutoProxyConfiguration() {
            }
        }

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "false",
            matchIfMissing = false
        )
        static class JdkDynamicAutoProxyConfiguration {
            JdkDynamicAutoProxyConfiguration() {
            }
        }
    }
}

这里也可以看到在SpringBoot2.3.7(确切点是2.x后),SpringBoot使用的代理方式默认是Cglib,因为配置的是:

matchIfMissing = true

此时,spring.aop.proxy-target-class默认是没有配置的,如果我们也没有手动配置它,那么在 SpringBoot 2.x 中会默认使用 Cglib 来实现。但是在spring5文档中默认还是jdk代理方式,如果对象没有实现接口,则使用 CGLIB 代理。但是由于jdk代理方式在代理对象的赋值对象不是接口的时候会报错这一问题,所以在SpringBoot2.x以后就更改了。这也就是为什么我们有时候在自动注入业务类实现类的方式的时候如果你是用的是jdk代理就会报错。列如这种形式:

 @Autowired
    private TestUserServiceImpl testUserService;

而如果是这种:

 @Autowired
    private TestUserService testUserService;

在jdk或Cglib下就都可以正常使用。因为JDK 动态代理是基于接口的,代理生成的对象只能赋值给接口变量。Cglib 是通过生成子类来实现的,代理对象无论是赋值给接口还是实现类这两者都是代理对象的父类。

如果需要默认使用 JDK 动态代理可以通过配置项spring.aop.proxy-target-class=false来进行修改。 

2.1,AOP初始化

2.1.1,初始化AspectJAutoProxyRegistrar

EnableAspectJAutoProxy通过Import注解引入了AspectJAutoProxyRegistrar

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrarSpring在初始化AopAutoConfiguration时把所有通过Import注解引入的ImportBeanDefinitionRegistrar实现类拿出来进行初始化,并调用其registerBeanDefinitions函数

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);
            }
        }

    }
}

2.1.2, 初始化AnnotationAwareAspectJAutoProxyCreator

AspectJAutoProxyRegistrar 则通AopConfigUtil中的registerAspectJAnnotationAutoProxyCreatorIfNecessary注册了一个

AnnotationAwareAspectJAutoProxyCreatorBeanDefinition

@Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

 通过类图中的关系链可以看到AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor接口,也就是所谓的后置处理器。用于在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。

public interface BeanPostProcessor {
//实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
//实例化、依赖注入、初始化完毕时执行
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

 后置处理器会在抽象类AbstractApplicationContext中的refresh()方法中被使用。refresh方法是Spring ApplicationContext的精髓,通过对refresh方法的源代码,可以看出这个方法使用synchronized加锁,调用类内不同的方法,完成以下工作:属性配置注入,BeanFactory准备,BeanFactory后置处理器调用,注册beanPostProcessor,初始化国际化及事件注册器,完成BeanFactory,完成refresh方法。

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
//截取部分代码
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            /*
             * Prepare this context for refreshing,setting its startup date and
             * active flag as well as performing any initialization of property sources.
             *容器刷新前的准备工作
             * 1.设置启动时间、激活/关闭标志位,
             *2.执行属性资源的初始化验证必须的属性
             *3.进行监听器和监听事件集合初始化
             */
            this.prepareRefresh();
            //获取BeanFactory对象,同时还进行了BeanDefinition信息的加载
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            //准备工厂
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
            //调用所有已注册的BeanFactoryPostProcessor对象的方法
                this.invokeBeanFactoryPostProcessors(beanFactory);
            /*
             *将BeanFactory中BeanPostProcessor类型的对象识别出来,并单独管理起来,便于在Bean完 
             *成初始化之后进行使用
             *Spring内部的一些特殊的Bean对象是早于自定义的Bean对象加入到容器工厂中的,我们通常说 
             *的Bean的实例化和初始化是针对自定义Bean对象而言的
            */
                this.registerBeanPostProcessors(beanFactory);
            //初始化用于国际化的资源
                this.initMessageSource();
            //初始化事件多播器
                this.initApplicationEventMulticaster();
            //这是一个留给子类扩展的钩子,语义上是留给子类做一些额外的容器刷新工作,
                this.onRefresh();
            //注册监听器,所谓注册,其实是把容器中已有的监听器类型的Bean对象单独管理起来,以便于 
            //进行后续的管理及发挥其特殊的功能性
                this.registerListeners();
            //初始化所有余下的非懒加载的单例Bean对象
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }
 }

这样Spring就会在会在初始化普通Bean之前初始化所有BeanPostProcessor。 

2.1.3 ,初始化切面方法跟切点

通过前面的类图可以看到AnnotationAwareAspectJAutoProxyCreator间接实现了InstantiationAwareBeanPostProcessor接口,而InstantiationAwareBeanPostProcessor实现了BeanPostProcessor接口,于是Spring 会在Bean创建时调用其postProcessBeforeInstantiation方法对Bean进行处理。

在第一次调用该方法时,AbstractAutoProxyCreator的子类AnnotationAwareAspectJAutoProxyCreator会初始化切面

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
 //截取部分代码
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        Object cacheKey = this.getCacheKey(beanClass, beanName);
        if (!StringUtils.hasLength(beanName) || 
           !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
            //判断当前BeanName对应的Bean是否应该被代理
            //并将判断结果保存下来,避免后续的后处理方法重复计算
            //在第一次判断时,会在shouldSkip里扫描所有Bean进行切面初始化
            if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }
        //如果为AbstractAutoProxyCreator注入了自定义的TargetSourceCreator
        //则通过TargetSourceCreator创建的Bean都被被AOP代理
        //TargetSourceCreator默认为空
        TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }

            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
            return null;
        }
    }
}

实现 切面初始化逻辑的类

public class BeanFactoryAspectJAdvisorsBuilder {
//截取部分代码
public List<Advisor> buildAspectJAdvisors() {
        List<String> aspectNames = this.aspectBeanNames;
        //如果还未进行初始化
        if (aspectNames == null) {
            synchronized(this) {
                aspectNames = this.aspectBeanNames;
                if (aspectNames == null) {
                    List<Advisor> advisors = new ArrayList();
                    List<String> aspectNames = new ArrayList();
                    //拿到容器里所有的beanName
                    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
                    String[] var18 = beanNames;
                    int var19 = beanNames.length;

                    for(int var7 = 0; var7 < var19; ++var7) {
                        String beanName = var18[var7];
                        if (this.isEligibleBean(beanName)) {
                            Class<?> beanType = this.beanFactory.getType(beanName, false);
              //判断类上是否标注Aspect,以及判断该class是否已经被代码式的Aspectj处理过
                            if (beanType != null && this.advisorFactory.isAspect(beanType)) {
                                aspectNames.add(beanName);
                                AspectMetadata amd = new AspectMetadata(beanType, beanName);
                                if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                                    MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//从类中拿到所有带有Before、Around等注解的方法,
//将这些方法包装成MethodInterceptor放入Advisor,MethodInterceptor#invoke为增强方法的调用入口
//将Advisor排好顺序组成List返回
                                    List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                                    if (this.beanFactory.isSingleton(beanName)) {
                                        this.advisorsCache.put(beanName, classAdvisors);
                                    } else {
                                        this.aspectFactoryCache.put(beanName, factory);
                                    }

                                    advisors.addAll(classAdvisors);
                                } else {
                                    if (this.beanFactory.isSingleton(beanName)) {
                                        throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton");
                                    }

                                    MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                                    this.aspectFactoryCache.put(beanName, factory);
                                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                                }
                            }
                        }
                    }

                    this.aspectBeanNames = aspectNames;
                    return advisors;
                }
            }
        }

        if (aspectNames.isEmpty()) {
            return Collections.emptyList();
        } else {
            List<Advisor> advisors = new ArrayList();
            Iterator var3 = aspectNames.iterator();

            while(var3.hasNext()) {
                String aspectName = (String)var3.next();
                List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);
                if (cachedAdvisors != null) {
                    advisors.addAll(cachedAdvisors);
                } else {
                    MetadataAwareAspectInstanceFactory factory = (MetadataAwareAspectInstanceFactory)this.aspectFactoryCache.get(aspectName);
                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                }
            }

            return advisors;
        }
    }


}

Advisor排序 

public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable {
private static final Comparator<Method> METHOD_COMPARATOR;

static {
        Comparator<Method> adviceKindComparator = new ConvertingComparator(
//按照注解顺序设置方法对应的advisor的顺序
//在AspectJAfterAdvice里,会先将请求继续向拦截器链后传播,
//对增强方法的调用是在后面的finnaly块里。所以这里的After顺序即使在AfterReturning前面也没关系
//另外,因为在finnly块里触发,所以即使后续的调用抛出了未捕获的异常,After指定的增强方法也会被执行                  
new InstanceComparator(new Class[]{Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class}), (method) -> {
//如果方法上没有标注上面的几个注解,则返回null,null会排在最后
            AbstractAspectJAdvisorFactory.AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
            return ann != null ? ann.getAnnotation() : null;
        });
        Comparator<Method> methodNameComparator = new ConvertingComparator(Method::getName);
        METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
    }

/*
 *判断method是否属于切面方法
 */
    @Nullable
    public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {
        this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
//获取切点信息,如果candidateAdviceMethod不是切面方法,则返回null
        AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
        return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
    }

@Nullable
    private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
//在方法上查找Aspectj的相关注解(Around、After等)
        AbstractAspectJAdvisorFactory.AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
        if (aspectJAnnotation == null) {
            return null;
        } else {
            AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
            ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
            if (this.beanFactory != null) {
                ajexp.setBeanFactory(this.beanFactory);
            }

            return ajexp;
        }
    }
}

2.2,生成代理对象过程解析 

由前面的类图关系情况可知AbstractAutoProxyCreator实现了BeanPostProcessor,在创建Bean时,Spring会调用AbstractAutoProxyCreator中的postProcessAfterInitialization方法Bean进行处理。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
//截取部分代码
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
//对bean进行包装,返回代理bean
                return this.wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;
    }
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//如果bean有TargetSourceCreator创建,说明已经被代理过了,直接返回
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } 
//拿出缓存的检测的结果进行判断
         else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        } 
//初步判断bean是否可以被代理
         else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
//根据切点Point的表达式获得符合当前bean的所有advisor
//如果当前bean不在切点的指向中,则返回DO_NOT_PROXY
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if (specificInterceptors != DO_NOT_PROXY) {
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
//创建代理对象,将所有advisor包装成DynamicAdvisedInterceptor,
//其intercept方法为所有增强方法的统一入口,这个类来自Spring
                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;
        }
    }
}

选择代理对象的创建方式 

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
//截取部分代码
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            Class<?> targetClass = config.getTargetClass();
//如果没有目标类去创建代理则抛出aop异常
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
//如果代理对象类是接口则用jdk代理方式,否则就创建ASM修改字节码的方式生产代理类-cglib
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }
}

在PeoxyConfig类中可以看到proxyTargetClass和optimize默认都是false,因此前面两个判断都为true,if语句的最终判断取决于hasNoUserSuppliedProxyInterfaces(config),hasNoUserSuppliedProxyInterfaces(config)就是在判断代理的对象是否有实现接口,有实现接口的话直接走new JdkDynamicAopProxy(config)分支,即使用JDK的动态代理。

 2.3,访问代理对象

DispatcherServlet 找到对应的实例跟方法后通过反射进行调用,此时会遍历代理对象上的所有MethodInterceptor

public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
//截取部分代码
 @Nullable
    public Object proceed() throws Throwable {
 //如果拦截器遍历完了,则调用目标方法
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return this.invokeJoinpoint();
        } else {
//遍历所有增强器MethodInterceptor,
//遍历方式是在MethodInterceptor里调用MethodInvocation#proceed
//每次进入该方法,都会使currentInterceptorIndex增加1,从而达成遍历
            Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
            if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
                InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
                Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();
                return dm.methodMatcher.matches(this.method, targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
            } else {
 //调用MethodInterceptor#invoke
                return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
            }
        }
    }
}

增强方法的执行顺序图如下:

  

三,AOP在Spring中的作用

提供声明式事务;允许用户自定义切面

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等....
  • 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target)︰被通知对象。
  • 代理(Proxy) :向目标对象应用通知之后创建的对象。
  • 切入点(PointCut) :切面通知执行的“地点""的定义。
  • 连接点(JointPoint) :与切入点匹配的执行点。

四,AOP在项目中的实际使用

AOP作为Spring框架核心特性之一,它不仅是一种编程思想更是实际项目中可以落地的技术实现技巧。通过自定义注解和AOP的组合使用,可以实现一些通用能力的抽象。比如很多接口都需要进行鉴权、日志记录或者执行时间统计等操作,但是如果在每个接口中都编写鉴权或者日志记录的代码那就很容易产生很多重复代码,在项目后期不好维护。针对这种场景 我们可以使用AOP同时结合自定义注解实现接口的切面编程,在需要进行通用逻辑处理的接口或者类中增加对应的注解即可。

在SpringBoot项目中我们可以引入AOP依赖来简化开发操作,SpringBootAOP整合的AOP中包含一些常用的注解方便我们开发。这里我们只构建基本使用方法,并不对整体项目进行展示。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

 4.1,用于日志处理

构建一个日志切面实现类用于记录日志

package com.yy.Aspect;

import cn.hutool.core.date.LocalDateTimeUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author young
 * @date 2022/11/4 20:35
 * @description: 日志切面
 */
@Component
@Aspect
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    // 切入点表达式,表示切入点为控制器包中的所有方法
    @Pointcut("within(com.yy.*.controller..*)")
    public void logAspect() {
        // TODO document why this method is empty
    }

    // 切入点之前执行
    @Before("logAspect()")
    public void doBefore(JoinPoint joinPoint) {
        logger.info("访问时间:{}--访问接口:{}", LocalDateTimeUtil.now(), joinPoint.getSignature());
    }
}

运行结果:

2022-11-09 14:05:39.088  INFO 14032 --- [nio-8090-exec-1] com.yy.Aspect.LogAspect                  : 访问时间:2022-11-09T14:05:39.087--访问接口:Object com.yy.admin.controller.AdminController.loginStatus(String,String)
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5233f602] was not registered for synchronization because synchronization is not active
2022-11-09 14:05:39.125  INFO 14032 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-11-09 14:05:39.918  INFO 14032 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@984058527 wrapping com.mysql.cj.jdbc.ConnectionImpl@78c509c9] will not be managed by Spring
==>  Preparing: select * from admin where username=? and pwd=?
==> Parameters: 张三(String), 123(String)
<==    Columns: id, username, pwd, avatar, token
<==        Row: 1, 张三, 123, https://i0.hdslb.com/bfs/article/366f4cf5edc6ceba38a4025e802a73818d84e38e.jpg, null
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5233f602]
2022-11-09 14:05:40.016  INFO 14032 --- [nio-8090-exec-1] c.y.admin.service.Impl.AdminServiceImpl  : token的值为:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiMSIsIjEyMyJdLCJzdWIiOiJ0b2tlbiIsImV4cCI6MTY2Nzk4MTEzOX0.bf3CQOMhfFvzmkr27Ik5sL5x7QvtbZY0Lk6pqCT8Hvs

这样就能记录每次调用方法的执行日志情况了。

4.2,计算业务接口耗时情况

在研发项目的性能测试阶段,或者项目部署后,我们会希望查看服务层方法执行的时间。以便精准的了解项目中哪些服务方法执行速度慢,后续可以针对性的进行性能优化。

此时我们就可以使用 AOP 的环绕通知,监控服务方法的执行时间,以便于后期的项目优化。

介于上面的日志处理有一些“一刀切”的情况,在实际使用中并不灵活,为了方便我们有需求的实现对业务的监控,我们可以通过自定义注解的方式来配合AOP切面使用,达到自主控制监控的功能。

创建自定义注解:

package com.yy.Aspect;

import java.lang.annotation.*;

/**
 * @author young
 * @date 2022/11/5 21:15
 * @description: 用于处理业务的夯耗时情况统计
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CostTime {
}

编写AOP切面类: 

package com.yy.Aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author young
 * @date 2022/11/4 20:46
 * @description:
 */
@Component
@Aspect
public class ServiceAspect {
  private  Logger logger = LoggerFactory.getLogger(ServiceAspect.class);
  //绑定自定义注解
    @Pointcut("@annotation(com.yy.Aspect.CostTime)")
    public void serviceAspect() {
        // TODO document why this method is empty
    }

//    @Around("serviceAspect()") // 环绕通知
    public Object deAround(ProceedingJoinPoint joinPoint){
        long startTime = System.currentTimeMillis();// 记录开始时间
        Object result = null;
        try {
            result = joinPoint.proceed();
            //获取方法名称
            String method = joinPoint.getSignature().getName();
            //获取类名
            String theClass = joinPoint.getSignature().getDeclaringTypeName();
            logger.info("类名为{}的服务层方法:{}--接口耗时:{}毫秒", theClass,method, System.currentTimeMillis() - startTime);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return result;
    }
}

在对应的业务接口上添加自定义注解 

    /**
     * 返回所有数据
     * @return List<TestUser>
     */
    @GetMapping("/getAll")
    @CostTime
    public R<List<TestUser>> getAll() {
        return R.ok(testUserService.list());
    }

运行后执行相关接口测试即可看到,耗时信息已经打印出来。

………………
<==        Row: 801157124, 刘亮, 1, 刘家墩号, 2022-08-27 11:40:41
<==        Row: 805400578, 牛马, 0, sad, 2022-09-24 03:01:32
<==        Row: 1216356353, 张三, 1, 阿达, 2022-09-01 03:13:57
<==        Row: 1887461377, bb, 1, sd, 2022-09-10 16:00:00
<==      Total: 102
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38d0cfbc]
2022-11-09 14:23:38.801  INFO 23976 --- [nio-8090-exec-2] com.yy.Aspect.AnnotationServiceAspect    : 类:[com.yy.testUser.controller.TestUserController],方法:[getAll]接口耗时为:[66046毫秒]

 其他功能,就不一一展示了,仅作参考即可。

如有理解不到之处,还望不惜赐教~

部分源码参考来源于简书