1. AOP 和 相关术语
AOP(Aspect Oriented Programming,即 面向切面编程)
- 在外部编写代码而不侵入原始代码层,使其增加额外功能
- 应用场景:权限、缓存、内容传递、错误处理、懒加载、调试、记录跟踪、性能优化、持久化、资源池、同步、事务、日志
AOP 相关术语
- 关注点:根据功能划分系统的一部分
- 横切关注点:系统中每个业务都实现的功能,即这个关注点横切了整个系统
- 连接点:Spring中指被拦截到的方法
- 切面:是一个类,里面定义了切点和通知
- 切点:带有通知的连接点,利用切入点表达式实现切入
- 通知:切点上执行的增强处理
雷神的图
AOP的底层就是动态代理,IOC容器中保存的组件实际都是代理对象
2. 通知
通知实际就是在目标方法前后执行的方法,有以下5中通知注解类型:
- @Before:在目标方法被调用之前做增强处理(前置通知)
- @AfterReturning:在目标方法完成后增强(返回通知)
- @AfterThrowing:处理执行过程中未处理的异常(异常通知)
- @After:在目标方法完成之后增强,无论目标方法是否成功完成(后置通知)
- @Around:在目标方法完成前后增强处理,和动态代理写法一样(环绕通知)
通知方法的执行顺序
- 正常情况:@Before——>@After——>@AfterReturning
- 异常情况:@Before——>@After——>@AfterThrowing
通知方法中的参数
JoinPoint 类:通知时获取目标方法的全部信息
使用:作为参数放入通知方法的参数列表中,然后就可以在方法体里get各种信息
环绕通知的使用(和动态代理使用方法一样)
@PointCut("execution(public int com.codfish.impl.Calculator.add())")
public void myPoint(){} //把切入点表达式封装到一个方法中,可在其他通知中直接调用
@Arround("myPoint()")
public Object arroundCalculator(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
Object proceed = null;
try{
/*前置通知*/
proceed = pjp.proceed(args); //开始执行目标方法
/*返回通知*/
}catch(Exception e){
e.printStackTrace();
/*异常通知*/
}finally{
/*后置通知*/
}
return proceed;
}
3. @Aspect (定义切面类)
//假设有一个计算器类,里面定义了加、减、乘、除、方法
@Component
public class Calculator implements ICalculator{
@Override
public void add() {
System.out.println("加法运算执行");
}
@Override
public void sub() {
System.out.println("减法运算执行");
}
@Override
public void mul() {
System.out.println("乘法运算执行");
}
@Override
public void div() {
System.out.println("除法运算执行");
}
}
//定义一个 切面类 使计算器执行前后增加其他功能
@Aspect
@Component
public class OrientUtils {
//前置通知方法
@Before("execution(public void codfish.Calculator.*())")
public void beforeCalculate(){
System.out.println("打开计算器");
}
//后置通知方法
@After("execution(public void codfish.Calculator.*())")
public void afterCalculate(){
System.out.println("关闭计算器");
}
}
//测试切入是否成功
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class TestCalculator {
@Resource
public ICalculator iCalculator;
@Test
public void test(){
iCalculator.add();
System.out.println("-----------------------");
iCalculator.sub();
System.out.println("-----------------------");
iCalculator.mul();
System.out.println("-----------------------");
iCalculator.div();
}
}
执行结果:
打开计算器
加法运算执行
关闭计算器
-----------------------
打开计算器
减法运算执行
关闭计算器
-----------------------
打开计算器
乘法运算执行
关闭计算器
-----------------------
打开计算器
除法运算执行
关闭计算器
切入点表达式
语法:execution(访问权限符 返回值类型 方法全签名(参数类型 参数变量))
4. 应用场景
4.1 日志处理
定义一个 AOP 切面,监控 controller 层的所有方法
- 定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface MyLog {
boolean value() default true;
}
- 定义切面类
@Aspect
@Component
public class LoggerAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggerAspect.class);
@Pointcut("@annotation(com.codfish.myproject.annotation.MyLog)")
public void point(){}
@Around("point()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long beginTime = System.currentTimeMillis();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
List<Object> logArgs = Arrays.stream(pjp.getArgs())
.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
.collect(Collectors.toList());
try {
logger.info("请求url={}, 请求参数={}", request.getRequestURI(), JSON.toJSONString(logArgs));
} catch (Exception e) {
logger.error("请求参数获取异常", e);
}
Object result = pjp.proceed(); //执行目标方法
long time = System.currentTimeMillis() - beginTime; //计算时常
try {
logger.info("请求耗时={}ms, 返回结果={}", time, JSON.toJSONString(result));
} catch (Exception e) {
logger.error("返回参数获取异常", e);
}
return result;
}
}
- 在 Controller 层需要处理日志的方法上添加 @MyLog 注解实现切入
4.2 声明式事务
Spring的事务管理器可以通过控制数据源 dataSource 实现事务的自动控制
Springboot直接在应用程序主类上添加@EnableTransactionManagement即可开启
非Springboot,则需要使用 xml 配置开启:
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
配置好后直接在需要进行事务控制的方法上添加注解 @Transactional 即可
@Transactional注解的常用属性