Day69-回顾Spring篇之AOP(面向切面编程)
Aop(Aspect Oriented Programming),面向切面编程,这是对面向对象思想的一种补充。面向切面编程,就是在程序运行时,不改变程序代码的情况下,动态的增强方法的功能,常见的使用场景非常多:
- 日志
- 事务
- 数据库操作
- …
这些操作中,无一例外,都有很多模板化的代码,而解决模板化代码,消除臃肿就是Aop的强项。
在aop中,有几个常见的概念:
概念 | 说明 |
---|---|
切点 | 要添加代码的地方,称为切点 |
通知(增强) | 通知就是相切点动态添加的代码 |
切面 | 切点+通知 |
连接点 | 切点的定义 |
一、Aop的实现
Aop实际上基于Java动态代理来实现的。
Java中的动态代理有两种实现方式:
- cglib
- jdk
二、动态代理
基于JDK的动态代理
1.定义一个计算机接口:
public interface MyCalculator{
int add(int a,int b);
}
2.定义计算机接口的实现:
public class MyCalculatorImpl implements MyCalculator{
public int add(int a,int b){
return a+b;
}
}
3.定义代理类
public class CalculatorProxy{
public static Object getInstance(final MyCalculatorImpl myCalculator){
return Proxy.new ProxyInstance(CalculatorProxy.class.getClassLoader(),myCalculator.getClass().getInterfaces(),new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
System.out.println(method.getName()+"方法开始执行啦...");
Object invoke = method.invoke(myCalculator,args);
System.out.println(method.getName()+"方法执行结束啦...");
return invoke;
}
});
}
}
Proxy.newProxyInstance方法接收三个参数,第一个是一个classloader,第二个是代理多项实现的接口,第三个是代理对象方法的处理器,所有要额外添加的行为都在invoke方法中实现。
三、Aop五种通知类型
- 前置通知
- 后置通知
- 异常通知
- 返回通知
- 环绕通知
具体实现,依然是给计算器的方法增强功能。
首先,在项目中,引入Spring依赖(这次需要引入Aop相关的依赖):
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
接下来,定义切点,这里介绍两种切点的定义方法:
- 使用自定义注解
- 使用规则
其中,使用自定义注解标记切点,是侵入式的,所以这种方式在实际开发中不推荐,仅作为了解,另一种使用规则来定义切点的方式,无侵入,一般推荐使用这种方式。
自定义注解
首先自定义一个注解:
package com.fu.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
}
然后在需要拦截的方法上,添加该注解,在add方法上添加@Action注解,表示该方法将被aop拦截,而其他未添加该注解的方法则不受影响。
import org.springframework.stereotype.Component;
@Component
public class MyCalculatorImpl implements MyCalculator {
@Override
@Action
public int add(int a, int b) {
System.out.println(a + "-" + b + "=" + (a+b));
return a+b;
}
@Override
@Action
public void min(int a, int b) {
System.out.println(a + "-" + b + "=" + (a-b));
}
}
接下来,定义增强(通知、Advice)
package com.fu.aop;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
//@Aspect注解表示这是一个切面
@Aspect
public class LogAspect {
/**
* 前置通知
* @param joinPoint 包含了目标方法的关键信息
* @Before 注解表示这是一个前置通知,即在目标方法之前执行,注解中,需要填入切点
*/
@Before("@annotation(Action)")
public void before(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法开始执行...");
}
/**
* 后置通知
* @param joinPoint 包含了目标方法的关键信息
* @After 表示这是一个后置通知,即在目标方法执行之后执行
*/
@After("@annotation(Action)")
public void after(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行结束了...");
}
/**
* 返回通知 可以在该方法中获取目标方法的返回值,如果目标方法的返回值为void,则收到null
* @param joinPoint
* @param r 返回参数名称,和这里方法的参数名一一对应
*/
@AfterReturning(value = "@annotation(Action)",returning = "r")
public void returning(JoinPoint joinPoint,Object r){
String name = joinPoint.getSignature().getName();
System.out.println(name+"返回通知:"+r);
}
/**
* 异常通知
* @param joinPoint
* @param e 目标方法所抛出的异常,注意,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类.
* 只有这样,才会捕获.如果香兰姐所有,参数类型声明为Exception
*/
@AfterThrowing(value = "@annotation(Action)",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法抛出异常了"+e.getMessage());
}
/**
* 环绕通知,环绕通知是上面四种通知集大成这,环绕通知的核心类似在反射中执行方法
* @param pjp
* @return
*/
public Object around(ProceedingJoinPoint pjp){
Object proceed = null;
try{
//这个相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,就相当于前置/后置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}