基础知识概述
aop 面向切面编程
1、切面(aspect)
散落在系统各处的通用的业务逻辑代码,如日志模块,权限模块,事务模块等,切面用来装载pointcut和advice
2、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
- Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
- After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。
- Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法执行的前后实现逻辑,也可以选择不执行方法
- Afterthrowing advice:在方法抛出异常退出时执行的通知。
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
拦截的方法,连接点拦截后变成切入点
6、目标对象(Target Object)
代理的目标对象,指要织入的对象模块
7、织入(weave)
通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程
8、AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理
元注解
元注解:修饰注解的注解
@Target:注解的作用目标
@Retention:注解的生命周期
@Documented:注解是否应当被包含在 JavaDoc 文档中
@Inherited:是否允许子类继承该注解
@Target:用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。语法如下:
@Target(value = {ElementType.METHOD})
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
ElementType.FIELD:允许作用在属性字段上
ElementType.METHOD:允许作用在方法上
ElementType.PARAMETER:允许作用在方法参数上
ElementType.CONSTRUCTOR:允许作用在构造器上
ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
ElementType.ANNOTATION_TYPE:允许作用在注解上
ElementType.PACKAGE:允许作用在包上
@Retention: 注解指定了被修饰的注解的生命周期。语法如下:
@Retention(value = RetentionPolicy.RUNTIME)
RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
RetentionPolicy.CLASS:注解只被保留到编译进行的时候,不会被加载到JVM中
RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,会被加载到JVM中,所以程序运行时可以获取到它
剩下两种类型的注解我们日常用的不多,也比较简单,需要知道他们各自的作用即可:
@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。
@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
实现步骤:
1、定义一个切面类Aspect
声明一个切面类,增加@Component和@Aspect两个注解,同时SpringBoot要引入spring-boot-stater-aop依赖包。
2、定义切点Pointcut
定义切点,并定义切点在哪些地方执行,采用@Pointcut注解完成,如@Pointcut(public * com.xxx.xxx..(…))
规则:修饰符(可以不写)+返回类型+包下的类+方法+方法参数 “”代表不限,“…”两个点代表参数不限,例如:
切点名称myPointcut,在返回类型不限的com.binlog.study.aop.controller包下的所有类,所有方法并且参数不限。
参考:@Pointcut(value="execution( com.binlog.study.aop.controller..(…))")
3、定义Advice通知
利用通知的5种类型注解@Before、@After、@AfterReturning、@AfterThrowing、@Around来完成在某些切点的增强动作。
springboot 增加 pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
如果是初学者,可以看这个简单的例子;
下面第二个例子是进阶版
实战
第一个例子
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String detail() default "";
}
// LogAspect
@Aspect
@Component
public class LogAspect {
/**
* 此处的切点是注解的方式,也可以用包名的方式达到相同的效果
*/
@Pointcut("@annotation(com.aop.Log)")
public void operationLog(){}
// 环绕增强
@Around("operationLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object res = null;
try {
res = joinPoint.proceed();
return res;
} finally {
try {
System.out.println("方法执行后打印日志");
}catch (Exception e){
System.out.println("LogAspect 操作失败:" + e.getMessage());
e.printStackTrace();
}
}
}
/**
* 处理完请求,返回内容
*/
@AfterReturning(returning = "ret", pointcut = "operationLog()")
public void doAfterReturning(Object ret) {
System.out.println("方法的返回值 : " + ret);
}
/**
* 后置异常通知
*/
@AfterThrowing("operationLog()")
public void throwss(JoinPoint jp){
System.out.println("方法异常时执行.....");
}
/**
* 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
*/
@After("operationLog()")
public void after(JoinPoint jp){
System.out.println("方法最后执行.....");
}
}
// controller
@RestController
@RequestMapping("user")
public class Controller {
@Autowired
private UserService userService;
@GetMapping("/findUserNameByTel")
public String findUserNameByTel(@RequestParam("tel") String tel){
return userService.findUserName(tel);
}
}
// UserService
@Service
public class UserService {
@Log(detail = "通过手机号[{{tel}}]获取用户名")
public String findUserName(String tel) {
System.out.println("tel:" + tel);
return "zhangsan";
}
}
第二个例子
自定义注解打印日志和抛出异常信息
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestAnnotate {
String value() default "";
}
切面类
@Aspect
@Slf4j
@Component
public class RequestAspet {
// PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
// 切面最主要的就是切点,所有的故事都围绕切点发生
// logPointCut()代表切点名称
@Pointcut(value = "@annotation(RequestAnnotate)")
private void requestLog() {
}
//定义切点
@Pointcut(value="execution(* com.example.demo.controller.*.*(..))")
public void operErrorPointCut() { }
/**
* 执行切点之前
* @param joinPoint
*/
@Before(value = "requestLog()")
private void doBefore(JoinPoint joinPoint) throws ClassNotFoundException {
log.info("执行前置");
try {
// 获取被代理对象的类名
String targetName = joinPoint.getTarget().getClass().getName();
// 获取 Signature 对象,包含目标方法名和所属类的 class 信息
String methodName = joinPoint.getSignature().getName();
// 获取方法参数
Object[] arguments = joinPoint.getArgs();
// 根据类名获取类
Class<?> targetClazz = Class.forName(targetName);
Method[] methods = targetClazz.getMethods();
String operation = "";
//遍历方法名和参数长度一致的方法
for (Method m :methods){
// 对比方法名一致
if (methodName.equals(m.getName())){
Class<?>[] clazz = m.getParameterTypes();
// 参数长度一致
if (clazz.length == arguments.length){
// 获取的是方法上注解后面的 value 值,可以作为注释传入
operation = m.getAnnotation(RequestAnnotate.class).value();
break;
}
}
}
StringBuilder sb = new StringBuilder();
for (Object arg : arguments) {
sb.append(arg);
sb.append("&");
}
// *========控制台输出=========*//
log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:" + sb.toString());
} catch (Throwable e){
log.info("around " + joinPoint + " with exception : " + e.getMessage());
}
}
/**
* 执行切点之后
* @param joinPoint
*/
@After(value = "requestLog()")
private void doAfter(JoinPoint joinPoint) {
log.info("执行后置");
try {
String targetName = joinPoint.getTarget().getClass().getName();
String targetMethod = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Class<?> targetClazz = Class.forName(targetName);
Method[] methods = targetClazz.getMethods();
String operation = "";
for (Method m :methods){
if (targetMethod.equals(m.getName())){
if (m.getParameterTypes().length == args.length){
operation= m.getAnnotation(RequestAnnotate.class).value();
}
}
}
StringBuilder sb = new StringBuilder();
for (Object arg:args){
sb.append(arg);
sb.append("&");
}
log.info("用户执行了 operation " + operation + "类:" + targetName + "方法名:" + targetMethod + "参数:" +sb.toString() );
}catch (Throwable e){
log.info("around " + joinPoint + " with exception : " + e.getMessage());
}
}
}
controller
@RequestMapping("test")
@RequestAnnotate(value = "测试")
public void test() {
System.out.println("执行测试");
}
测试结果:
异常接口:
@RequestMapping("test")
@RequestAnnotate(value = "测试")
public void test() {
System.out.println("执行测试");
int[] array = {1,2};
System.out.println(array[2]);
}