AOP是一种概念,springAOP、AspectJ都是AOP的实现,Spring AOP有自己的语法,但是语法复杂,所以SpringAOP借助了AspectJ的注解,但是底层实现还是自己的。
系统是由很多不同的组件完成,每一个组件负责一块特定的功能,除了实现自身的核心功能外,这些组件常常还需要承担额外的责任,例如将日志、事务管理、安全等这样的核心服务融入到自身业务逻辑中,这些就是核心服务就是横向切入,实际上就是对某一对象或者其功能进行增强,因为他会跨越系统的多个组件。
例如:
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
按照正常的逻辑,我们可以这么做。
这有个问题就是,有多少接口,就要多少次代码copy。这显然就造成了代码的冗余,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
但同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。
红框处,就是面向切面编程。
我们先来看看实际运用,这里只阐述使用注解的形式,XML的形式就不阐述了。
第一步:配置spring文件,开启aop注解
<!-- 开启aop的注解 -->
2 <aop:aspectj-autoproxy/>
3 <context:component-scan base-package="cn.ytk.*"></context:component-scan>
第二步:编写增强类(即需要配置切面的类-切面类)
package cn.ytk.strong;
2
3 import org.aspectj.lang.ProceedingJoinPoint;
4 import org.aspectj.lang.annotation.AfterReturning;
5 import org.aspectj.lang.annotation.Aspect;
6 import org.aspectj.lang.annotation.Before;
7 import org.aspectj.lang.annotation.Pointcut;
8 import org.springframework.stereotype.Component;
9
10 @Component
11 @Aspect
12 public class Project2 {
13 //方式1:
14 @Before(value="execution(* cn.ytk.dao.UserDao.*(..))")
15 public void before() {
16 System.out.println("前置通知。。。。。。");
17 }
18
19 //方式2:先编写切点在将切点加到加强上。
20 @Pointcut("execution(* cn.ytk.dao.*.*(..))")
21 public void after() {}
22
23 @AfterReturning("after()")
24 public void after1() {
25 System.out.println("....后置通知....");
26 }
27 }
从示例中看出,注解方式中使用AOP技术的需要做以下几步:
1)开启AOP注解,<aop:aspectj-autoproxy/>
2)定义切面类,使用@Aspect注解
3)在切面类上加设注解@Component
4)在切面类中定义切点方法,使用@PointCut注解
5)在切面类中定义通知方法,使用@Before、@After、@Around等注解
6)在通知方法的注解中使用切点方法
AOP的底层实现也是基于动态代理,Spring默认采取的动态代理机制实现AOP, 简单来说就是在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式。动态代理技术包括Java动态代理和CGLIB动态代理,前者基于接口实现,后者基于类实现。Spring默认采取的Java动态代理机制实现AOP。在介绍Spring AOP原理之前先介绍Java动态代理。
AOP的实现流程可以梳理为下图:
看过了上面的例子,我想大家脑中对AOP已经有了一个大致的雏形,但是又对上面提到的切面之类的术语有一些模糊的地方,接下来就来讲解一下AOP中的相关概念,了解了AOP中的概念,才能真正的掌握AOP的精髓。
Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强/通知):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。Interceptor(拦截器):在Advice的基础上扩展定义,定义了通知的增强方式,也就是通过对Joinpoint(连接点)的拦截。一个通用的拦截器可以拦截发生在基础程序中的运行时事件。
Advisor(顾问/增强器): Advisor是切面的另一种实现,绑定通知跟切点。没有指定切点的通知是没有意义的,Advisor可以说就是一个绑定在指定切点上的通知。它能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。
Target(目标对象):织入 Advice 的目标对象。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。AOP有三种织入方式:①编译期织入:需要特殊的Java编译期(例如AspectJ的ajc);②装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强;③运行时织入:在运行时为目标类生成代理实现增强。
下面结合源码分析:
一:声明注解
声明了自定义的注解,一定要在xml文件中配置。<aop:aspectj-autoproxy/>注解使用AspectJAutoProxyBeanDefinitionParser解析器进行解析。这个对应关系在AopNamespaceHandler(package org.springframework.aop.config)类中指定。
1 public void init() {
2 // In 2.0 XSD as well as in 2.1 XSD.
3 registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
4 registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
5 registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
6
7 // Only in 2.0 XSD: moved to context namespace as of 2.1
8 registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
9 }
接着来看下AspectJAutoProxyBeanDefinitionParser(package org.springframework.aop.config)解析器的代码。它是一个实现了BeanDefinitionParser接口的类,专门用于解析切面自动代理的Bean定义的解析工作,重点在其parse方法。
1 @Override
2 @Nullable
3 public BeanDefinition parse(Element element, ParserContext parserContext) {
4 //注册AnnotationAwareAspectJAutoProxyCreator
5 AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
6 //对注解中子类的处理
7 extendBeanDefinition(element, parserContext);
8 return null;
9 }
其中调用了AopNamespaceUtils类(处理Spring AOP命名空间的工具类,package org.springframework.aop.config)的registerAspectJAnnotationAutoProxyCreatorIfNecessary函数。我们进入此函数看一下代码逻辑:
1 public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
2 ParserContext parserContext, Element sourceElement) {
3 //注册或者升级AutoProxyCreator定义beanName为
4 // org.Springframework.aop.config.internalAutoProxyCreator的BeanDefinition
5 BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
6 parserContext.getRegistry(), parserContext.extractSource(sourceElement));
7 //对于proxy-target-class以及expose-proxy属性的处理
8 useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
9 //注册组件并通知,便于监听器进一步处理,其中BeanDefinition的className
10 // 为AnnotationAwareAspectJAutoProxyCreator
11 registerComponentIfNecessary(beanDefinition, parserContext);
12 }
上述代码一共三行代码,每行代码完成一件事情,都是一个完整的逻辑。
第一行代码:BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary( parserContext.getRegistry(), parserContext.extractSource(sourceElement));
注册或者升级AnnotatonAwareAspectJAutoProxyCreator。
我们进入AopConfigUtils类(package org.springframework.aop.config)的registerAspectJAnnotationAutoProxyCreatorIfNecessary函数看看:
1 @Nullable
2 public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
3 BeanDefinitionRegistry registry, @Nullable Object source) {
4
5 return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
6 }
上面这个函数又实际调用了本类中的registerOrEscalateApcAsRequired函数,我们继续查看该函数的实现逻辑:
1 @Nullable
2 private static BeanDefinition registerOrEscalateApcAsRequired(
3 Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
4
5 Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
6
7 //如果已经存在了自动代理创建器且存在的自动代理创建器与现在的不一致,
8 // 那么需要根据优先级判断到底使用哪个
9 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
10 BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
11 if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
12 int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
13 int requiredPriority = findPriorityForClass(cls);
14 if (currentPriority < requiredPriority) {
15 //改变bean最重要的就是改变bean所对应的className属性
16 apcDefinition.setBeanClassName(cls.getName());
17 }
18 }
19 //如果已经存在自动代理创建器且与将要创建的一致,那么无需再次创建
20 return null;
21 }
22
23 RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
24 beanDefinition.setSource(source);
25 beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
26 beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
27 registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
28 return beanDefinition;
29 }
对于Aop的实现,基本都是靠AnnotatonAwareAspectJAutoProxyCreator去完成的,它可以根据@Point注解定义的切点来自动代理相匹配的bean。但是为了配置简便,Spring使用了自定义的配置帮助我们自动注册AnnotatonAwareAspectJAutoProxyCreator。上述代码实现了自动注册AnnotatonAwareAspectJAutoProxyCreator类的功能,同时存在优先级问题,如果已经存在了自动代理创建器,而且存在的与现在的不一致,那么需要根据优先级判断到底使用哪个。
第二行代码:useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
处理proxy-target-class和expose-proxy属性,进入该函数:
1 private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
2 if (sourceElement != null) {
3 //对应proxy-target-class属性的处理
4 boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
5 if (proxyTargetClass) {
6 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
7 }
8 //对应expose-proxy属性的处理
9 boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
10 if (exposeProxy) {
11 AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
12 }
13 }
14 }
proxy-target-class和expose-proxy属性解释:
(1)proxy-target-class属性
Spring AOP使用JDK动态代理或者CGLIB来为目标对象创建代理。如果被代理的目标对象至少实现了一个接口,则使用JDK动态代理,所有该目标类型实现的接口都将被代理。如果该目标对象没有实现任何接口,则创建一个CGLIB代理。也可以强制使用CGLIB代理,强制使用CGLIB代理需要将<aop:config>的proxy-target-class属性设置为true:<aop:config proxy-target-class=”true”>…</aop:config>
当需要使用CGLIB代理和@AspectJ自动代理支持,可以按照下面的方式设置<aop:aspectj-autoproxy>的proxy-target-class属性:<aop:aspectj-autoproxy proxy-target-class=”true”>。
(2)expose-proxy属性
Spring AOP无法拦截内部方法调用,比如一个接口里面有两个方法:doSomething1()和doSomething2()。然后在方法1中调用了方法2:this.doSomething2()。此处this指向目标对象,因此调用this.doSomething2()将不会执行doSomething2的增强。(也就是切面只会对doSomething1方法进行增强,但是不会对doSomething2进行增强)。
解决方法:this. doSomething2 ()修改为 ((AService) AopContext.currentProxy()).doSomething2 ()。同时修改Spring AOP的配置:<aop:aspectj-autoproxy expose-proxy="true" />
第三行代码:registerComponentIfNecessary(beanDefinition, parserContext);
注册组件并通知,便于监听器进一步处理。
二:获取增强方法或者增强器
上面通过自定义配置完成了对AnnotatonAwareAspectJAutoProxyCreator(package org.springframework.aop.aspectj.annotation中)的自动注册。
AnnotatonAwareAspectJAutoProxyCreator类:SpringBoot框架中默认支持的方式。spring aop 开启注解方式之后,该类会扫描所有@Aspect()注释的类,生成对应的advisor。
AnnotatonAwareAspectJAutoProxyCreator类:SpringBoot框架中默认支持的方式。spring aop 开启注解方式之后,该类会扫描所有@Aspect()注释的类,生成对应的advisor。
其有一个非常核心的方法:wrapIfNecessary()。继续进入该类中的wrapIfNecessary方法实现逻辑:
1 /**
2 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
3 * @param bean the raw bean instance
4 * @param beanName the name of the bean
5 * @param cacheKey the cache key for metadata access
6 * @return a proxy wrapping the bean, or the raw bean instance as-is
7 */
8 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
9 //如果已经处理过
10 if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
11 return bean;
12 }
13 //这个bean无需增强
14 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
15 return bean;
16 }
17 //判断给定的bean是否是一个基础设施类,基础设施类不应代理,或者配置了指定bean不需要代理。
18 //所谓InfrastructureClass就是指Advice/PointCut/Advisor等接口的实现类。
19 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
20 this.advisedBeans.put(cacheKey, Boolean.FALSE);
21 return bean;
22 }
23
24 // 如果存在增强方法则创建代理
25 //获取这个bean的advice
26 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
27 //如果获取到了增强则需要针对增强创建代理
28 if (specificInterceptors != DO_NOT_PROXY) {
29 this.advisedBeans.put(cacheKey, Boolean.TRUE);
30 //创建代理
31 Object proxy = createProxy(
32 bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
33 this.proxyTypes.put(cacheKey, proxy.getClass());
34 return proxy;
35 }
36
37 this.advisedBeans.put(cacheKey, Boolean.FALSE);
38 return bean;
39 }
从上述代码中我们看到了代理创建的雏形。创建代理主要包含了两个步骤:1、获取增强方法或者增强器。2、根据获取的增强进行代理。
下面我们先来看下获取增强方法或者增强器:
AbstractAutoProxyCreator类的wrapIfNecessary方法中调用了getAdvicesAndAdvisorsForBean,AbstractAutoProxyCreator类只对该方法进行定义,真正实现在其子类AbstractAdvisorAutoProxyCreator(package org.springframework.aop.framework.autoproxy)中实现。
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
上面方法实现又调用了该类中的findEligibleAdvisors方法,进入其代码:
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
里面有两个函数findCandidateAdvisors(获取所有的增强)和findAdvisorsThatCanApply(寻找增强中适用于bean的增强并应用)
这两个方法就是来获取所有的增强以及寻找增强中适用于bean的增强并应用。
以上是通知器筛选的过程,筛选的工作主要由 ClassFilter 和 MethodMatcher 完成。
所以获取增强器的主要流程:(1)获取所有bean名称;(2)遍历所有bean名称找出其中标记Aspect的bean;(3)解析并构造获取bean的所有增强器;(4)将解析到的增强器添加到缓存中;(5)过滤匹配出当前bean的增强器。
三:根据获取的增强创建代理
回到AbstractAutoProxyCreator类的wrapIfNecessary方法。从这个方法代码中我们看到了代理创建的雏形。创建代理主要包含了两个步骤:1、获取增强方法或者增强器。2、根据获取的增强进行代理。上面我们介绍了获取增强方法或者增强器,下面我们看下根据获取的增强进行代理。
wrapIfNecessary函数中创建代理用了调用了Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
在AbstractAutoProxyCreator类中的createProxy方法中实现了以下步骤:
步骤1:获取当前类的属性。
步骤2:添加代理接口。
步骤3:拦截器封装转化为增强器
由buildAdvisors方法将拦截器封装转化为增强器。
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
步骤4:将Advisor加入到ProxyFactory中。
将拦截器封装转化为增强器后,再通过ProxyFactory提供的addAdvisor方法将增强器置入创建工厂中。
proxyFactory.addAdvisors(advisors);
步骤5:设置要代理的类。
步骤6:在Spring中还为子类提供了定制函数customizeProxyFactory,子类可以在此函数中对ProxyFactory进一步封装。
步骤7:设置是否需要冻结代理对象
步骤8:通过createAopProxy方法进行代理操作,使用return getAopProxyFactory.getProxy(getProxyClassLoader()),实际用的是DefaultAopProxyFactory类的createAopProxy方法,因为AopProxyFactory只是一个接口。DefaultAopProxyFactory是AopProxyFactory默认实现类,核心函数createAopProxy。所以其实是调用了DefaultAopProxyFactory类的createAopProxy方法。而createAopProxy方法中,则是真正实现了代理的创建,如果目标类是接口类型,则使用JDK代理,否则使用CGlib代理,如果都不是则还是由JDK代理。故Spring主要是由JDK代理。
至此完成了代理的创建。
四:织入
Spring主要是使用JDK动态代理,而Java动态代理机制中有两个重要的接口和类:
(1)接口InvocationHandler:动态代理类的调用处理程序。
(2)Proxy:动态代理类,Proxy类就是用来创建一个代理对象的类。
Spring的AOP实现是用Proxy和InvocationHandler这两个东西。JDK代理中InvocationHandler的创建是最为核心的。在自定义的InvocationHandler中需要重写3个方法。(1)构造函数,将代理的对象传入。(2)invoke方法,此方法中实现了AOP增强的所有逻辑。(3)getProxy方法。
Java动态代理的步骤:
1、创建目标类(委托类)的接口
2、创建目标类(委托类)
3、定义一个代理类的调用处理程序。该程序必须实现接口InvocationHandler,且必须 实现接口的invoke方法。
4、通过Proxy的静态方法newProxyInstance()创建一个代理对象。
5、通过代理对象调用委托类对象的方法。
在第三步创建了代理之后,然后就是第四步重写getProxy方法(获取到代理类需要实现的所有的接口)和invoke方法(调用执行方法)织入。
getProxy():
// JdkDynamicAopProxy类结构,由此可知,其实现了InvocationHandler,则必定有invoke方法,来被
// 调用,也就是用户调用bean相关方法时,此invoke()被真正调用
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable
// 重写getProxy()
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// JDK proxy 动态代理的标准用法
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
invoke():
//使用了JDK动态代理模式,真正的方法执行在invoke()方法里,看到这里在想一下上面动态代理的例
//子,是不是就完全明白Spring源码实现动态代理的原理了。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
try {
// 1.以下的几个判断,主要是为了判断method是否为equals、hashCode等Object的方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// 2.获取当前bean被拦截方法链表
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 3.如果为空,则直接调用target的method
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
// 4.不为空,则逐一调用chain中的每一个拦截方法的proceed,这里的一系列执行的原因以及proceed执行的内容,我 在这里就不详细讲了,大家感兴趣可以自己去研读哈
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
... // 中间省略无关代码
return retVal;
}
}
接下路实现一些业务逻辑,帮助我们更加掌握AOP技术。
第一个实例:所有的get请求被调用前在控制台输出一句"get请求的advice触发了"。
具体实现如下:
1.创建一个AOP切面类,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现advice:
package com.jingudi.framework.log.log.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAdvice {
// 定义一个切点:所有被GetMapping注解修饰的方法会织入advice
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
private void logAdvicePointcut(){}
@Before("logAdvicePointcut()")
public void logAdvice(){
// 这里只是一个示例,你可以写任何处理逻辑
System.out.println("get请求的advice触发了");
}
}
2.随便创建一个接口类,内部创建一个get请求
(必须要有@GetMapping):
@ApiOperation("查询用户列表")
@GetMapping
public PageResult<UserEntity> getUserList(QueryUserVo vo) {
return userService.getUserList(vo);
}
日志台:
第二个实例:
该例的场景是:
自定义一个注解PermissionsAnnotation
创建一个切面类,切点设置为拦截所有标注PermissionsAnnotation的方法,截取到接口的参数,进行简单的权限校验
将PermissionsAnnotation标注在测试接口类的测试接口test上。
具体实现步骤:
1.使用@Target、@Retention、@Documented自定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}
2.创建第一个AOP切面类,,只要在类上加个@Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现第一步权限校验逻辑:
package com.jingudi.advice;
import com.alibaba.fastjson.JSONObject;
import com.jingudi.modules.system.dto.DictDetailDto;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
@Order(1)
@Slf4j
public class PermissionFirstAdvice {
// 定义一个切面,括号内写入第1步中自定义注解的路径
@Pointcut("@annotation(com.jingudi.annotation.PermissionsAnnotation)")
private void permissionCheck() {
}
@Before("permissionCheck()")
public void beforeAdvice(JoinPoint joinPoint){
// 这里只是一个示例,你可以写任何处理逻辑
System.out.println("---------Before触发了----------");
// 获取签名
Signature signature = joinPoint.getSignature();
// 获取切入的包名
String declaringTypeName = signature.getDeclaringTypeName();
// 获取即将执行的方法名
String funcName = signature.getName();
log.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);
// 也可以用来记录一些信息,比如获取请求的 URL 和 IP
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取请求 URL
String url = request.getRequestURL().toString();
// 获取请求 IP
String ip = request.getRemoteAddr();
log.info("用户请求的url为:{},ip地址为:{}", url, ip);
}
@Around("permissionCheck()")
public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {
// 这里只是一个示例,你可以写任何处理逻辑
System.out.println("---------Around触发了----------");
//获取请求参数,详见接口类
Object[] objects = joinPoint.getArgs();
System.out.println(objects);
// Integer id = ((JSONObject) objects[0]).getInteger("id");
DictDetailDto object1 = (DictDetailDto)objects[0];
// 修改入参
JSONObject object = new JSONObject();
return joinPoint.proceed(objects);
}
@AfterReturning(pointcut = "permissionCheck()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result){
// 这里只是一个示例,你可以写任何处理逻辑
System.out.println("---------AfterReturning触发了----------");
Signature signature = joinPoint.getSignature();
String classMethod = signature.getName();
log.info("方法{}执行完毕,返回参数为:{}", classMethod, result);
// 实际项目中可以根据业务做具体的返回值增强
log.info("对返回参数进行业务上的增强:{}", result + "增强版");
}
@AfterThrowing(pointcut = "permissionCheck()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
Signature signature = joinPoint.getSignature();
String method = signature.getName();
// 处理异常的逻辑
log.info("执行方法{}出错,异常为:{}", method, ex);
}
@After("permissionCheck()")
public void afterAdvice(JoinPoint joinPoint){
// 这里只是一个示例,你可以写任何处理逻辑
System.out.println("---------After触发了----------");
Signature signature = joinPoint.getSignature();
String method = signature.getName();
log.info("方法{}已经执行完", method);
}
}
注意:@Order(0)表示AOP加载顺序(切面加载顺序)