赞
踩
AOP
(Aspect Oriented Programming)–面向切面编程
通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
实现方式:JDK动态代理、CGLIB代理。前者基于接口,后者基于子类。
正常:
异常:
@Aspect:切面,通常是一个类的注解,里面可以定义切入点和通知。
@Pointcut:切入点,书写切入点表达式,指明Advice要在什么样的条件下才能被触发。
由下列方式来定义或者通过 &&、 ||、 !、 的方式进行组合:
Advice:通知,某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。
JointPoint:连接点,程序运行中的某个阶段点,比如方法的调用、异常的抛出等。
Advisor:增强, 是PointCut和Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。
<aop:aspectj-autoproxy/> <aop:config proxy-target-class="true"> <aop:pointcut id="servicePointcut" expression="execution(* com.cpic..*Service.*(..))" /> <aop:advisor pointcut-ref="servicePointcut" advice-ref="txAdvice" order="3" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="list*" read-only="true" /> <!-- log方法会启动一个新事务 --> <tx:method name="log*" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" /> </tx:attributes> </tx:advice> <!-- OK所以一个Spring增强(advisor)=切面(advice)+切入点(PointCut) -->
@Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType
定义,常用的包括:
@Retention: 表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy
中,取值为:
注意:
- 只有定义为
RetentionPolicy.RUNTIME
时,我们才能通过注解反射获取到注解。- 所以,假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用。
@Documented:表示这个注解是由 javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注解了文档化,它的注解成为公共API的一部分。
示例-反射获取注解
定义注解:
@Target(ElementType.FIELD) // 注解用于字段上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface MyField {
String description();
int length();
}
通过反射获取注解
public class MyFieldTest { //使用我们的自定义注解 @MyField(description = "用户名", length = 12) private String username; @Test public void testMyField(){ // 获取类模板 Class c = MyFieldTest.class; // 获取所有字段 for(Field f : c.getDeclaredFields()){ // 判断这个字段是否有MyField注解 if(f.isAnnotationPresent(MyField.class)){ MyField annotation = f.getAnnotation(MyField.class); System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]"); } } } }
直接上图,先大致看看项目结构:
<!-- AOP --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.12</version> </dependency>
import com.example.demo.module.annotation.SystemLog; import com.example.demo.module.utils.AtomicCounter; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * <p> @Title ApiVisitHistory * <p> @Description API访问历史统计 * * @author ACGkaka * @date 2021/3/31 17:29 */ @Component @Aspect public class ApiLogAopAction { private static final Logger LOGGER = LoggerFactory.getLogger(ApiLogAopAction.class); ThreadLocal<Long> startTime = new ThreadLocal<>(); @Autowired HttpServletRequest request; /** * 定义切面 * - 此处代表com.example.demo.module.controller包下的所有接口都会被统计 */ @Pointcut("@annotation(com.example.demo.module.annotation.SystemLog)") // @Pointcut("execution(* cn.agile.platform.core.web.controller..*.*(..))") public void log() { } /** * 在接口原有的方法执行前,将会首先执行此处的代码 */ @Before("log()") public void doBefore(JoinPoint joinPoint) { startTime.set(System.currentTimeMillis()); MethodSignature signature = (MethodSignature)joinPoint.getSignature(); SystemLog annotation = signature.getMethod().getAnnotation(SystemLog.class); AtomicCounter.init(annotation.module(), request.getRequestURI()); // 计数 AtomicCounter.getInstance().increaseVisit(request.getRequestURI()); } /** * 只有正常返回才会执行此方法 * 如果程序执行失败,则不执行此方法 */ @AfterReturning(returning = "returnVal", pointcut = "log()") public void doAfterReturning(JoinPoint joinPoint, Object returnVal) { LOGGER.info("URI:[{}], 耗费时间:[{}] ms, 访问次数:{}", request.getServletPath(), System.currentTimeMillis() - startTime.get(), AtomicCounter.getInstance().increaseSuccess(request.getRequestURI())); } /** * 当接口报错时执行此方法 */ @AfterThrowing(pointcut = "log()") public void doAfterThrowing(JoinPoint joinPoint) { LOGGER.info("接口访问失败,URI:[{}], 耗费时间:[{}] ms", request.getServletPath(), AtomicCounter.getInstance().increaseFail(request.getRequestURI())); } /** * 在接口原有的方法执行后,都会执行此处的代码(final) */ @After("log()") public void doAfter(JoinPoint joinPoint) { LOGGER.info("End.{}", AtomicCounter.getInstance().getValue(request.getRequestURI())); } }
import java.lang.annotation.*; /** * <p> @Title SystemLog * <p> @Description 接口日志注解 * * @author ACGkaka * @date 2021/4/1 11:36 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SystemLog { String module() default "";//模块 String method() default "";//方法 String operateType() default "OTHER" ;//事件类型:LOGIN;LOGINOUT;ADD;DELETE;UPDATE;SELETE;UPLOAD;DOWNLOAD;OTHER String logType() default "0";//日志类型:0:系统日志;1:业务日志 }
import com.example.demo.module.annotation.SystemLog; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> @Title DemoController * <p> @Description 待统计接口 * * @author ACGkaka * @date 2021/3/31 17:24 */ @RestController public class DemoController { @GetMapping("/index") @SystemLog(module = "首页", method = "hello", operateType = "SELECT", logType = "1") public String index() { return "<h1>Hello World.</h1>"; } @GetMapping("login") @SystemLog(module = "首页", method = "login", operateType = "LOGIN", logType = "1") public String login() { int i = 1 / (Math.random() > 0.5 ? 0 : 1); return "测试报错的AOP方法"; } }
https://gitee.com/acgkaka/SpringBootExamples/tree/master/springboot-aop
参考文章:
1.@Pointcut()的execution、@annotation等参数说明
https://blog.csdn.net/java_green_hand0909/article/details/90238242
2.Java 自定义注解及使用场景
https://www.jianshu.com/p/a7bedc771204
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。