1.AOP概述
1.1 什么是AOP?
AOP(Aspect Oriented Programming 面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
常用于日志记录,性能统计,安全控制,事务处理,异常处理等等。
1.2 AOP术语
切面(Aspect):由横切关注点构成的特殊对象。
连接点(Join Point):连接点是指在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候;
通知(Advice):指在切面的某个特定的连接点上执行的动作。
Spring切面可以应用5种通知:
- 前置通知(Before):在目标方法或者说连接点被调用前执行的通知;
- 后置通知(After):指在某个连接点完成后执行的通知;
- 返回通知(After-returning):指在某个连接点成功执行之后执行的通知;
- 异常通知(After-throwing):指在方法抛出异常后执行的通知;
- 环绕通知(Around):指包围一个连接点通知,在被通知的方法调用之前和之后执行自定义的方法。
切点(Pointcut):指匹配连接点的断言。通知与一个切入点表达式关联,并在满足这个切入的连接点上运行,例如:当执行某个特定的名称的方法。
引入(Introduction):引入也被称为内部类型声明,声明额外的方法或者某个类型的字段。
目标对象(Target Object):目标对象是被一个或者多个切面所通知的对象。
AOP代理(AOP Proxy):向目标对象应用通知之后创建的对象。
织入(Wearving):增强添加到目标类具体连接点上的过程。AOP有三种织入的方式:编译期织入、类装载期织入、动态代理织入(spring采用动态代理织入)。
1.3 通知执行顺序
- 正常情况
@Around ->@Before->主方法体->@Around中pjp.proceed()->@After->@AfterReturning
- 存在异常
- 异常在Around中pjp.proceed()之前
@Around -> @After -> @AfterThrowing
- 异常在Around中pjp.proceed()之后
@Around ->@Before->主方法体->@Around中pjp.proceed()->@After->@AfterThrowing
1.4 实现原理
- JDK动态代理(JDK提供,只能代理接口)
使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。
- CGLib动态代理: (适用CGLib工具)
采用动态的字节码生成技术,运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP 。
1.5 Pointcut切入点的语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
@Pointcut("within(com.example.controller..*)") public void pointcutWithin(){ }
@Pointcut("this(com.example.controller.HelloController)") public void pointcutThis(){ }
@Pointcut("target(com.leo.service.UserInfoService)") public void pointcutTarge(){ }
@Pointcut("bean(*ServiceImpl)") public void pointcutBean(){ }
@Pointcut("args(String, ..)") public void pointcutArgs(){ }
@Pointcut("@annotation(org.springframework.stereotype.Controller)") public void pointcutAnnocation(){ }
@Pointcut("@within(org.springframework.stereotype.Controller)") public void pointcutWithinAnno(){
}
@Pointcut("@target(org.springframework.stereotype.Controller)") public void pointcutTargetAnno(){ }
@Pointcut("@args(org.springframework.stereotype.Service)") public void pointcutArgsAnno(){ }
@Pointcut(value = "execution(public * com.example.controller.HelloController.hello*(..))") public void pointCut() { }
|
2.AOP实践
2.1 HTTP接口鉴权
需求:
- 可以定制地为某些指定的 HTTP RESTful api 提供权限验证功能。
- 当调用方的权限不符时, 返回错误。
相关依赖1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
2.1.1 自定义注解
1 2 3 4
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthChecker { }
|
AuthChecker
注解是一个方法注解,它用于注解 RequestMapping 方法。
2.1.2 aspect的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Component @Aspect public class HttpAopAdviseDefine { @Pointcut("@annotation(com.example.annotation.AuthChecker)") public void pointcut(){
} @Around("pointcut()") public Object checkAuth(ProceedingJoinPoint proceedingJoinPoint){ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String token = getToken(request); if (!token.equalsIgnoreCase("111")){ return "token不合法!"; } try { return proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); return null; } } private String getToken(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null) { return ""; } for (Cookie cookie : cookies) { if (cookie.getName().equalsIgnoreCase("token")) { return cookie.getValue(); } } return ""; } }
|
2.1.3 Controller
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController @RequestMapping("/aop/http") public class AopController { @GetMapping("alive") public String alive(){ return "服务一切正常"; } @AuthChecker @GetMapping("login") public String login(){ return "登录成功!"; } }
|
2.1.4 测试
token缺失/不正确:
token正确:
2.2 方法调用日志
需求:
- 某个服务下的方法的调用需要有log记录调用的参数以及返回结果。
- 当方法调用出异常时,有特殊处理,例如打印异常 log,报警等。
2.2.1 aspect 的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Component @Aspect public class LogAopAdviseDefine { private Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("within(com.example.service..*)") public void poincut() { } @Before("poincut()") public void logMethodInvokeParam(JoinPoint joinPoint) { logger.info("---Before method {} invoke, param: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); }
@AfterReturning(pointcut = "poincut()",returning = "message") public void logMethodInvokeResult(JoinPoint joinPoint,Object message){ logger.info("---After method {} invoke, result: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); }
@AfterThrowing(pointcut = "poincut()",throwing = "exception") public void logMethodInvokeException(JoinPoint joinPoint,Exception exception){ logger.info("---method {} invoke exception: {}---", joinPoint.getSignature().toShortString(), exception.getMessage()); } }
|
2.2.2 Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Service public class LogServiceImpl implements LogService { private Logger logger = LoggerFactory.getLogger(getClass()); private Random random = new Random(System.currentTimeMillis());
@Override public int LogMethod(String param) { logger.info("---LogService: logMethod invoked, param: {}---", param); return random.nextInt(); }
@Override public void exceptionMethod() throws Exception { logger.info("---LogService: exceptionMethod invoked---"); throw new Exception("Something bad happened!"); } }
|
1 2 3 4 5 6 7 8 9
| @Service public class NormalServiceImpl implements NormalService { private Logger logger = LoggerFactory.getLogger(getClass());
@Override public void normalMethod() { logger.info("---NormalService: someMethod invoked---"); } }
|
2.2.3 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @RunWith(SpringRunner.class) @SpringBootTest public class LogAdviseTest { @Autowired private LogService logService; @Autowired private NormalService normalService; @Test @PostConstruct public void testLogAdvise(){ logService.LogMethod("LogMethod Test!"); try { logService.exceptionMethod(); }catch (Exception e){ } normalService.normalMethod(); } }
|
2.3 方法耗时统计
需求:
- 为服务中的每个方法调用进行调用耗时记录.
- 将方法调用的时间戳, 方法名, 调用耗时上报到监控平台
2.3.1 aspect 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Component @Aspect public class ExpiredAopAdviseDefine { private Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("within(com.example.service.impl.ExpiredServiceImpl)") public void pointcut() { } @Around("pointcut()") public Object methodInvokeExpiredTime( ProceedingJoinPoint proceedingJoinPoint) { try { StopWatch stopWatch = new StopWatch(); stopWatch.start(); Object proceed = proceedingJoinPoint.proceed(); stopWatch.stop(); reportToMonitorSystem( proceedingJoinPoint.getSignature().toShortString(), stopWatch.getTotalTimeMillis()); return proceed; } catch (Throwable throwable) { throwable.printStackTrace(); return null; } } public void reportToMonitorSystem(String methodName, long expiredTime) { logger.info("---method {} invoked, expired time: {} ms---", methodName, expiredTime); } }
|
2.3.2 Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Service public class ExpiredServiceImpl implements ExpiredService { private Logger logger = LoggerFactory.getLogger(getClass()); private Random random = new Random(System.currentTimeMillis()); @Override public void expiredTimeMethod() { logger.info("---SomeService: someMethod invoked---"); try { Thread.sleep(random.nextInt(500)); } catch (InterruptedException e) { e.printStackTrace(); } } }
|
2.3.3 测试
1 2 3 4 5 6 7 8 9 10 11 12
| @RunWith(SpringRunner.class) @SpringBootTest public class ExpiredAdviseTest { @Autowired private ExpiredService expiredService; @Test @PostConstruct public void testExpiredTime() { expiredService.expiredTimeMethod(); } }
|