AOP
一.引入
1.应用场景
AOP的功能主要是为了拦截或者实现日志安全检查等一些检查的工作,aop也常用于事务管理,防止垃圾数据进入数据库.
2.横向切割和纵向切割
- AOP采用的是横向切割技术—>在不修改源代码的情况下完成添加功能的技术
- 纵向切割技术—>在修改源代码的情况下完成功能的操作
AOP名词的基础概念
-
通知—>实现横向切割技术的方式(通知定义了何时,做什么.)
@Before 前置通知--->通知方法会在目标方法调用之前执行 @AfterReturning 后置通知--->通知方法会在目标方法返回后调用 @AfterThrowing 异常通知--->通知方法会将目标方法封装起来 @Around 环绕通知--->通知方法会将目标方法封装起来 @After 最终通知--->通知方法会在目标方法返回或抛出异常后调用
-
连接点(JoinPoint)
连接点是应用执行过程中能够插入切面(Aspect)的一个点.这些点可以是调用方法时,甚至修改一个字段时. 它是一个虚拟的概念,例如坐地铁的时候,每一个站都可以下车,那么这里每一个站都是一个接入点. 假如一个对象中有多个方法,那么这个每一个方法就是一个连接点.
-
切入点(Pointcut)
切入点是一些特殊的连接点,是具体附加通知的地方. 例如,坐地铁的时候,具体在某个站下车,那这个站就是切入点
-
切面(Aspect)
切面是通知和切入点的结合,通知规定了在什么时机干什么事,切入点规定了在什么地方. 例如,"在8点钟在西站下车"就是一个切面. 那么时间8点,动作下车就是一个通知.西站就是一个切入点.
二.AOP中的Maven依赖—>aspectj
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.20.1</version>
<!-- 这一行要去掉,不然写代码的时候不起作用 runtime的意思是运行的时候起作用,但是会导致写代码的时候不起作用,导包导不进去-->
<!-- <scope>runtime</scope>-->
</dependency>
三.AOP使用(xml配置)
-
applicationContext.xml文件的配置
<aop:config> <aop:ponitcut id="pointcut的id名" expression="切入点表达式"/> <aop:aspect ref="引入AOP代理对象"> <aop:通知方法 method="AOP代理对象中的方法" pointcut-ref="pointcut的id名"/> </aop:aspect> </aop:config>
-
代理对象
public class 类名{ ... //代理对象的方法体 }
代码实例
- 业务类
package com.neuedu.service;
import org.springframework.stereotype.Service;
@Service
public class TargetService {
// 目标方法1:查询余额
public void selectAccount(){
System.out.println("模拟查询到用户余额为100");
// 为了测试异常通知-->手动设置一个异常
// throw new RuntimeException("运行时异常");
}
// 目标方法2
public Integer add(Integer add1,Integer add2){
return add1+add2;
}
}
- AOP处理类
package com.neuedu.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Service;
//AOP代理对象
@Service
public class AOPHandler {
public void handlerBefore(){
System.out.println("前置通知");
}
public void handlerBefore1(Integer add1,Integer add2){
System.out.println("前置通知");
System.out.println(add1);
System.out.println(add2);
}
public void handlerAfter(){
System.out.println("返回通知");
}
public void handlerAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前通知");
System.out.println("环绕通知");
// 设置为执行代码--->用proceed()方法
Object obj = pjp.proceed();
System.out.println(obj);
System.out.println("环绕后通知");
}
public void handlerExcep(){
System.out.println("异常通知");
}
public void handlerFinally(){
System.out.println("最终通知");
}
}
- applicationContext.xml文件配置
<aop:config>
<!-- 设置拦截(切入点)====>用切入点标识符 -->
<aop:pointcut id="point" expression="execution(* com.neuedu.service.*.*(..))"/>
<!-- 代理对象 引入代理对象AOPHandler-->
<aop:aspect ref="AOPHandler">
<!-- 设置通知方法==>before:前置通知 pointcut-ref的值应与上面point-cut的id值对应-->
<aop:before method="handlerBefore" pointcut-ref="point"/>
<!-- 通知方法==>after-returning:返回通知-->
<aop:after-returning method="handlerAfter" pointcut-ref="point"/>
<!-- 通知方法==>round:环绕通知-->
<aop:around method="handlerAround" pointcut-ref="point"/>
<!-- 通知方法==>after-throwing:异常通知-->
<aop:after-throwing method="handlerExcep" pointcut-ref="point"/>
<!-- 通知方法==>after:最终通知-->
<aop:after method="handlerFinally" pointcut-ref="point"/>
<!-- 有参数的-->
<aop:before method="handlerBefore1" pointcut="execution(* com.neuedu.service.*.*(..)) and args(add1,add2)"/>
</aop:aspect>
</aop:config>
- 测试类
import com.neuedu.service.TargetService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AOPTest {
@Autowired
private TargetService targetService;
@Test
public void testTarget(){
targetService.selectAccount();
targetService.add(1,3);
}
}
切入点标识符
执行表达式的格式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(parampattern)throws-pattern?)
切入点表达式的格式:
注解? 修饰符? 返回值类型 类型声明?方法名(参数列表) 异常列表?
-
其中,返回值类型,方法名,参数列表必填,其他都为可选
参数列表: "()"表示方法没有任何参数; "(..)"表示匹配接受任意个参数方法 切入点表达式通配符: * : 匹配所有字符 .. : 一般用于匹配多个包,多个参数 + : 表示类及其子类
Proceedingjoinpoint简述
proceedingjoinpoint简述参考
proceedingjoinpoint继承了JoinPoint,在JoinPoint的基础上暴露出proceed(),这个方法是AOP代理链执行的方法.
JoinPoint仅能获取相关参数,无法执行连接点.暴露出proceed()这个方法,就能支持aop:around这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面的类型有关),就能控制走代理链还是走自己拦截的其他逻辑
四.AOP注解配置
-
启动aop注解—>applicationContext.xml页面
<!-- AOP注解配置 (推荐使用)--> <!-- 启动aop注解--> <aop:aspectj-autoproxy proxy-target-class="true"/>
-
目标类
public class 类名(){ ... //方法体 }
-
AOP处理注解类
/** * @Component 该对象由Spring容器来创建管理 * @Aspect 标识是一个AOP对象 */ @Component @Aspect public class ExeTimeAop { @Pointcut("execution(* com.neuedu.service.*..*(..))") public void anyMethod(){} //无参数的前置通知 @Before("anyMethod()") public void before(){...} //有参数的前置通知 @Before("anyMethod()&&args(add1,add2)") public void beforeParam(Object add1,Object add2){...} //环绕通知 @Around("anyMethod()") //返回通知 @AfterReturning(pointcut = "anyMethod()",returning = "result") //异常通知 @AfterThrowing(pointcut = "anyMethod()",throwing = "ex") //最终通知 @After("anyMethod()")
代码实例
- 启动aop注解
<aop:aspectj-autoproxy proxy-target-class="true"/>
- 目标类
package com.neuedu.service;
import org.springframework.stereotype.Service;
@Service
public class AopService {
public void print(){
System.out.println("aop注解配置");
}
public Integer add(Integer add1,Integer add2){
if (add1>0){
throw new java.lang.ArithmeticException("零除异常");
}
return add1+add2;
}
}
- AOP处理注解类
package com.neuedu.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @Component 该对象由Spring容器来创建管理
* @Aspect 标识是一个AOP对象
*/
@Component
@Aspect
public class ExeTimeAop {
@Pointcut("execution(* com.neuedu.service.*..*(..))")
public void anyMethod(){}
@Before("anyMethod()")
public void before(){
System.out.println("前置通知");
}
//一般参数传递都会在before中--->一般参数都会在方法执行之前传进来
//方法执行后传进来就没意义了
@Before("anyMethod()&&args(add1,add2)")
public void beforeParam(Object add1,Object add2){
System.out.println("前置通知 参数传递");
System.out.println(add1);
System.out.println(add2);
}
@Around("anyMethod()")
public void around(ProceedingJoinPoint pjt) throws Throwable {
System.out.println("环绕前通知");
long startTime = System.currentTimeMillis();
//控制方法是否执行
pjt.proceed();
long endTime = System.currentTimeMillis();
System.out.println("环绕后通知");
System.out.println("方法:"+pjt.getSignature().getName()+"执行时间"+(endTime-startTime)+"ms");
}
@AfterReturning(pointcut = "anyMethod()",returning = "result")
public void aroundRunning(Object result){
System.out.println("返回通知");
System.out.println("返回值:"+result);
}
@AfterThrowing(pointcut = "anyMethod()",throwing = "ex")
public void aroundThrowing(Exception ex){
System.out.println("异常通知");
System.out.println(ex);
}
@After("anyMethod()")
public void after(){
System.out.println("最终通知");
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AOPTest {
@Autowired
private AopService aopService;
@Test
public void testAnnAop(){
aopService.print();
System.out.println("-------------");
aopService.add(1,4);
}
}