Java自定义注解的简单介绍就不说了,这里主要说一下自定义注解 aop 切面的使用。
一、什么是AOP?
1、AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。
AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构。AOP最早由AOP联盟的组织提出的,制定了一套规范。Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
2、AOP的作用及优势是什么?
(1)作用:
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
在程序运行期间,不修改源码对已有方法进行增强
将业务逻辑和系统处理的代码(关闭连接、事务管理、操作日志记录)解耦
(2)优势:减少重复代码、提高开发效率、维护方便
3、AOP相关术语介绍:
Joinpoint(连接点) -- 所谓连接点是指那些被拦截到的点。在spring中这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点) -- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
Advice(通知/增强) -- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)
Introduction(引介) -- 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
Target(目标对象) -- 代理的目标对象
Weaving(织入) -- 是指把增强应用到目标对象来创建新的代理对象的过程
Proxy(代理) -- 一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面) -- 是切入点和通知的结合,以后咱们自己来编写和配置的
以上内容摘自Spring AOP文档!
二、编写自定义注解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public @interface AnnoationCeshi {
public String ceshiValue() default "";
}
三、编写切面
1、编写切面
@Component @Aspect public class AnnoationCeshiFine { // @annotation 表示 使用该注解的方法 @Around(value = "@annotation(AnnoationCeshi)") public Object aroundMethod(ProceedingJoinPoint jp) throws Throwable { // String module = getModule(jp); // 逻辑代码 // System.out.printf("**********" + module); return jp.proceed(); }
}
@Component 等价于 @Controller @Service ... spring 扫描并被 spring 容器管理
@Aspect 作用是把当前类标识为一个切面供容器读取
AnnoationCeshi 为自己定义的注解
只要加了 @AnnocationCeshi 注解的 方法 都会执行 上述代码
2、定义切面
// 定义切点
// @poincut execution 表达式
//exxcution( * com.xy.xywx.controller.CeshiController.*(..)) CeshiController 类中所有方法 public 可省略
@Pointcut("execution(public * com.xy.xywx.controller..*.*(..))")
public void poincut(){}
@poincut 定义切面,里面为 excution 表达式,其中:
(1)excution 表达式中 public 可省略不写
(2)第一个 * 表示 controller 包中的所有类
(3)第二个 * 表示 类中的所有方法
(4).. 表示 不限参数
@Before(value = "poincut()") //通知前增强
@AfterReturning(value = "poincut()") // 通知后 增强
@AfterThrowing(value = "poincut()") // 异常 增强
@After(value = "poincut()") // final 增强
@Around(value = "poincut()")
3、具体使用
@Around(value = "poincut()")
public Object aroundMethod(ProceedingJoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
Arrays.stream(args).forEach(x -> System.out.println(x));
return jp.proceed();
}
return jp.proceed() 重点
四、具体介绍
@Component //加入到spring容器
@Aspect //切面
public class AspectDemo {
// 方法用途(切入点表达式可以用&&,||,!来组合使用)
@Pointcut("@annotation(com.example.demo.annotation.AnnotationDemo)")
public void asAnnotation() {}
// 方法用途:在AnnotationDemo注解之前执行,标识一个前置增强方法,相当于BeforeAdvice的功能
@Before("asAnnotation()")
public void beforeRun() {
System.out.println("在AnnotationDemo注解之前执行");
}
/* 方法用途:
* @Around 环绕增强,相当于MethodInterceptor,对带@AnnotationDemo注解的方法进行切面,并获取到注解的属性值
* ProceedingJoinPoint: 环绕通知
*/
@Around("asAnnotation() && @annotation(annotationDemo)")
public Object around(ProceedingJoinPoint joinPoint,AnnotationDemo annotationDemo) {
Object obj = null;
try {
// AnnotationDemo注解的属性值
annotationDemo.remark();
Object[] args = joinPoint.getArgs();
String arg = JSON.toJSONString(args);
System.out.println("请求参数:"+ arg);
Signature signature = joinPoint.getSignature();
// 方法package路径
String methodUrl = signature.getDeclaringTypeName();
// 方法名,不包含package路径
String method = signature.getName();
System.out.println("正在执行" + method + "方法,路径:" + methodUrl);
// obj是返回的结果,joinPoint.proceed用于启动目标方法执行,非常重要
obj = joinPoint.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("切面执行");
return obj;
}
/**
* 方法用途: 在AnnotationDemo注解之后执行
* final增强,不管是抛出异常或者正常退出都会执行。
* @AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行
* @AfterThrowing: 异常抛出增强,相当于ThrowsAdvice
*/
@AfterReturning(returning = "obj", Pointcut = "asAnnotation()")
public void after(Object obj) {
System.out.println("在AnnotationDemo注解之后执行");
logger.info("执行成功,Result:{}", JSON.toJsonString(obj));
}
}
五、具体应用
1、比如我们限制下重复点击
(1)先定义注解
(2)再写注解的 AOP
@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {
// @Autowired 可引入一些类
@Around("execution(* com.enmox.emcs.*.controller.*Controller.*(..)) && @annotation(noRepeatSubmit)")
public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
try {
// 业务处理:通过注解的 time()拿到默认设置的值,然后进行一些业务处理即可
int time = noRepeatSubmit.time()
return pjp.proceed();
} catch (EmcsCustomException e) {
throw new EmcsCustomException(e.getMessage());
} catch (Throwable e) {
log.error("校验表单重复提交时异常: {}", LogUtil.getStack(e));
throw new EmcsCustomException("校验表单重复提交时异常");
}
}
}
2、比如我们记录操作日志
/**
* 记录操作日志
*/
@Aspect
@Component
@Slf4j
public class RecordOperLogAspect {
@Pointcut("@annotation(com.baj.iam.authn.aop.UserOperLog)")
public void userLogpointcut() {}
@Pointcut("@annotation(com.baj.iam.authn.aop.AdminOperLog)")
public void adminLogpointcut() {}
@Pointcut("@annotation(com.baj.iam.authn.aop.AppOperLog)")
public void appLogpointcut() {}
/**
* 用户日志
* @param userOperLog
*/
@After("userLogpointcut() && @annotation(userOperLog)")
public void recordUserOperLog(UserOperLog userOperLog) {
// 业务处理
}
@Before("userLogpointcut() && @annotation(userOperLog)")
public void recordUpdatePasswordLog(JoinPoint point, UserOperLog userOperLog) {
try {
// 单独的before切面记录修改密码,因为after拿不到用户信息
} catch (Throwable e) {
log.error("记录操作日志异常: {}", e.getMessage(), e);
}
}
/**
* 应用操作日志
* @param appOperLog
*/
@Around("appLogpointcut() && @annotation(appOperLog)")
public Object recordAppOperLog(ProceedingJoinPoint point, AppOperLog appOperLog) throws Throwable {
// 业务处理
}
}