通过xml的aspect实现
导入AOP的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
————————————————
版权声明:本文为CSDN博主「David宫洪深」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36020545/article/details/53000896
xml中导入命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
编写aop配置
切入点表达式入门简介:
例如:
execution(public void com.test.aop.aspectTest.service.AopTestService.add(int))
1.execution
:以此法编写的切入点表达式,将使用方法定位的模式匹配连接点。
用 execution 写出来的表达式,都是直接声明到类中的方法的。
2.public
:限定只切入 public 类型的方法。
3.void
:限定只切入返回值类型为 void 的方法。
4.com.test.aop.aspectTest.service.AopTestService
:限定只切入 AopTestService 这个类的方法。
5.add
:限定只切入方法名为 add 的方法。
6.(int)
:限定只切入方法的参数列表为一个参数,且类型为 int的方法。
通过<aop:config>
来在配置AOP。
<bean id="loggerAdvice" class="com.test.aop.aspectTest.component.Logger"/>
<aop:config>
<aop:aspect id="loggerAspect" ref="loggerAdvice">
<aop:before method="beforePrint"
pointcut="execution(public void com.test.aop.aspectTest.service.AopTestService.add(int)"/>
</aop:aspect>
</aop:config>
<aop:aspect>
定义切面。
<aop:before>
定义通知方法类型。before是前置通知,在目标方法执行前调用增强逻辑。这个标签中的method
为增强逻辑的方法名,pointcut
为切入点表达式(定义哪些方法逻辑需要增强)。
基于注解的AOP配置
1.对于自定义的切面类(aspect)
切面类标注注解`@Aspect`,`@Component`。
2.在切面类中。可以在方法上标注对应的通知注解。
@Before:前置通知
@After:后置通知
@AfterReturning:返回通知
@AfterThrowing:异常通知
@Around:环绕通知
3.通知注解中可以加切入点表达式
例如:
@Before("execution(public * com.test.aop.aspectTest.service.testService.*(..))")
public void beforePrint() {
System.out.println("Logger beforePrint run ......");
}
4.配置类中引入@EnableAspectJAutoProxy
注解含义:开启AOP。
@Configuration
@ComponentScan("com.test.aop.aspectText")
@EnableAspectJAutoProxy
public class AspectJAOPConfiguration {
}
注:在xml配置文件中配置<aop:aspectj-autoproxy/>
,效果一样。
注意点:同一个切面类中,环绕通知的执行时机比单个通知要早
。
5.@Pointcut
注解
切入点表达式注解。
可以在切面类(aspect)中,定义一个空方法。标注注解@Pointcut。里面配置通用的切入点表达式。
在其他通知注解上,传入这个空方法即可。
例如:
@Pointcut("execution(* com.test.aop.aspectTest.service.*.*(String)))")
public void defaultPointcut() {
}
@Before("defaultPointcut()")
public void afterPrint() {
System.out.println("Logger afterPrint run ......");
}
在xml配置文件,如何抽取通用的切入点表达式?
<aop:config>
<aop:aspect id = "apsectId" ref = "切面类id">
<aop:pointcut id="pointcutId" expression= "切入点表达式" >
<aop:before method = "beforeTest"
pointcut-ref="pointcutId"/>
<!--定义其他的通知 -->
</aop:aspect>
</aop:config>
6.@annotation的使用
除了 execution
之外,还有一种切入点表达式也比较常用:@annotation()
。
它的使用方式就非常简单了,只需要在括号中声明注解的全限定名即可。
1.声明一个注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
2.切入点表达式中添加声明
@annotation(com.test.aop.aspectTest.component.Log)
// 指定注解
@Pointcut("@annotation(com.test.aop.aspectTest.component.Log)")
public void withAnnotation() {}
含义:会搜索整个 IOC 容器中标注了 @Log 注解的所有 bean 全部增强。
通知方法中的特殊参数
JoinPoint
JoinPoint.getTarget可以返回目标对象。
JoinPoint.getThis可以返回代理对象。
JoinPoint.getArgs可以获取被拦截的方法的参数列表。
JoinPoint.getSignature()可以获取被拦截的签名。
@Before("execution(public * com..TestService.add(..))")
public void beforePrint(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法名称
Method method = signature.getMethod();
System.out.println(method.getName());
System.out.println("Logger beforePrint run ......");
}
ProceedingJoinPoint
ProceedingJoinPoint是对JoinPoint的扩展,扩展了proceed方法。
ProceedingJoinPoint
有一个 proceed
方法,执行了它,就相当于之前咱在动态代理中写的 method.invoke(target, args); 方法了。
返回通知和异常通知的特殊参数
通知如果要获取返回结果或者异常。
1.首先在方法的参数列表中声明一个 result 或者 e :
@AfterReturning("execution(* com..TestService.subtract(double))")
public void afterReturningPrint(Object retval) {
System.out.println("Logger afterReturningPrint run ......");
System.out.println("返回的数据:" + retval);
}
@AfterThrowing("defaultPointcut()")
public void afterThrowingPrint(Exception e) {
System.out.println("Logger afterThrowingPrint run ......");
}
2.通知注解中增加对应的属性。
返回通知增加属性returning:
@AfterReturning(value = "execution(* com..TestService.subtract(double))", returning = "retval")
public void afterReturningPrint(Object retval) {
System.out.println("Logger afterReturningPrint run ......");
System.out.println("返回的数据:" + retval);
}
异常通知增加属性throwiing:
@AfterThrowing(value = "defaultPointcut()", throwing = "e")
public void afterThrowingPrint(Exception e) {
System.out.println("Logger afterThrowingPrint run ......");
System.out.println("抛出的异常:" + e.getMessage());
}
AOP联盟指定的通知类型
在没有整合aspectj之前,spring框架有一套原生的实现。
AOP联盟就制定了规范,总结出了5中通知类型。
通知类型:
1.前置通知
2.返回通知
3.异常通知
4.环绕通知
5.引介通知
与AspectJ的区别:多了一个引介通知,少了一个后置通知。
spring框架中的通知接口如下
前置通知:org.springframework.aop.MethodBeforeAdvice
返回通知:org.springframework.aop.AfterReturningAdvice
异常通知:org.springframework.aop.ThrowsAdvice
环绕通知:org.aopalliance.intercept.MethodInterceptor
引介通知:org.springframework.aop.IntroductionAdvisor
注意:环绕通知的接口是 AOP 联盟原生定义的接口(不是 cglib 的那个 MethodInterceptor )。
多个切面执行顺序
默认的切面执行顺序,是根据切面类的unicode编码,按照十六进制排序得来的。按照字母表的顺序来的。
也可以声明执行顺序通过实现Ordered接口
。实现Ordered接口的默认排序是Integer的最大值。设置值越小,先执行。
也可以通过注解@Order
,注解属性中填写值,越小对应的切面类越先执行。
那么,同一个切面类中的多个相同通知的执行顺序呢?
答:都是根据unicode编码顺序(字典表顺序)来的。
注意
:同一切面类中相同通知**不能
通过@Order注解排序**。
代理对象调用自身方法
有一种场景:
我们产生的代理对象,调用拦截的目标对象方法中,调用了另外一个目标对象的方法。这种情况下,代理个性化逻辑只会执行一次。(调用了目标对象的两个方法,自定义代理逻辑执行性一次)
解决方案:AopContext
1.在配置类中开启AOP注解中开启exposeproxy属性
2.如果代理的方法中调用目标对象的另外一个方法,都需要执行代理逻辑,使用AopContext.currentProxy()
方法就取到代理对象的 this,然后再调用另外一个方法。
@Configuration
@ComponentScan("com.test.aop.testAop")
@EnableAspectJAutoProxy(exposeProxy = true) // 暴露代理对象
public class AopContextConfiguration {
}
@Service
public class TestTarget{
public void update(String id,String name) {
//这里调用另外一个get方法
get(id);
//进行修改操作
}
public String get(String id) {
//返回查询结果
return "";
}
}
@Aspect
@Component
public class AspectTest{
@Before("execution(* com.test.aop.testAop.service.TestTarget.*(..))")
public void beforePrint() {
System.out.println("LogAspect 前置通知 ......");
}
}
修改后的Service类:
@Service
public class TestTarget{
public void update(String id,String name) {
//这里调用另外一个get方法
//get(id);
((TestTarget)AopContext.currentProxy()).get(id);
//进行修改操作
}
public String get(String id) {
//返回查询结果
return "";
}
}