Spring Aop 简单入门应用
- 部分摘自网络(排名不分先后)
1.Aop切面概念
-
作用意图
面向切面编程(Aspect Oriented Programming),在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
-
基本概念
1 Aspect(切面):通常是一个类,里面可以定义切入点和通知
2 JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
3 Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning, afterThrowing,around
4 Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
5 AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动 态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
2.Spring Aop
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
-
默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
-
当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
基于注解的AOP配置方式(使用xml的配置这里不作叙述)
1 通知类型介绍
-
Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
-
AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
-
AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象
-
After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
-
Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
2 通知执行的优先级
进入目标方法时,先织入Around,再织入Before,退出目标方法时,先织入Around,再织入AfterReturning,最后才织入After。
注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用!同时使用也没啥意义。
3 切入点的定义和表达式
切入点表达式的定义算是整个AOP中的核心,有一套自己的规范
Spring AOP支持的切入点指示符:
-
execution:用来匹配执行方法的连接点
@Pointcut(“execution(* com.demo.springaop.service….(…))”)
第一个*表示匹配任意的方法返回值,…(两个点)表示零个或多个,上面的第一个…表示service包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个…表示方法的任意参数个数
-
within限定匹配方法的连接点,上面的就是表示匹配service包下的任意连接点
@Pointcut(“within(com.demo.springaop.service.*)”)
-
this用来限定AOP代理必须是指定类型的实例,如上,指定了一个特定的实例,就是UserService
@Pointcut(“this(com.demo.springaop.service.UserService)”)
-
bean也是非常常用的,bean可以指定IOC容器中的bean的名称
@Pointcut(“bean(userService)”)
概念说的再好,不如实际运用
1 准备依赖包
<!-- spring框架aop支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- 下面这个包也是aop需要的包,但是经测试 不要这个包也可以 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
注意:如果启动报错如下错,
error at ::0 can't find referenced pointcut serviceLogAspect
一般是因为aspectjweaver包与jdk版本冲突的问题导致的。
1.9.4版本支持JDK1.8 如果是低版本的JDK使用aspectjweaver低一点的版本
2 首页要启用@AsjectJ支持 在项目中的context.xml加入以下配置(实际项目中可能叫applicationContext.xml,根据自己项目即可)
<!-- 启用注解 -->
<context:annotation-config />
<!-- 配置注解扫描包 -->
<context:component-scan base-package="com.demo.springaop"/>
<!-- 启用@AsjectJ支持 -->
<aop:aspectj-autoproxy />
3 代码案例
@Component
@Aspect
@Slf4j(topic = "bizLogger")
public class SoLoggerAspect {
@Pointcut("execution(* com.demo.springaop.service..*.*(..))")
public void serviceLogAspect() {
}
@Before("serviceLogAspect()")
public void before(JoinPoint jp) {
try {
log.info("Aop before ["+jp.getSignature().toShortString()+"] ");
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
}
}
/**
* 方法执行结束
* @param jp
* @param o 是返回出参
*/
@AfterReturning(pointcut = "serviceLogAspect()", returning = "o")
public void afterReturning(JoinPoint jp, Object o) {
log.info("Aop afterReturning ["+jp.getSignature().toShortString()+"] ");
}
//已使用环绕通知
// @AfterThrowing(pointcut = "serviceLogAspect()", throwing = "t")
// public void AfterThrowing(JoinPoint jp, Throwable t) {
// log.error("Aop AfterThrowing ["+jp.getSignature().toShortString()+"] "+ t.toString());
// }
/**
* 统计方法执行耗时Around环绕通知
* @param joinPoint
* @return
*/
@Around("serviceLogAspect()")
public Object timeAround(ProceedingJoinPoint joinPoint) {
//定义返回对象、得到方法需要的参数
Object obj = null;
Object[] args = joinPoint.getArgs();
long startTime = System.currentTimeMillis();
try {
obj = joinPoint.proceed(args);
} catch (Throwable e) {
log.error("Aop Around error ["+joinPoint.getSignature().toShortString()+"]"+(e.getMessage() == null? e.toString(): e.getMessage()));
}
long endTime = System.currentTimeMillis();
long diffTime = endTime - startTime;
//超过1秒打印耗时
if (diffTime > 1000) {
log.info("run time ["+joinPoint.getSignature().toShortString()+"] 耗时:" + diffTime + " :ms");
}
return obj;
}
4 运行效果参考,对于日志的打印直接换成自己的即可。
2019-06-13 11:21:50 -·- INFO -·- Aop before [UserService.getUserList(..)]
2019-06-13 11:21:51 -·- INFO -·- run time [UserService.getUserList(..)] 耗时:1004 :ms
2019-06-13 11:21:51 -·- INFO -·- Aop afterReturning [UserService.getUserList(..)]
2019-06-13 11:21:57 -·- INFO -·- Aop before [UserService.getUserById(..)]
2019-06-13 11:21:57 -·- INFO -·- Aop afterReturning [UserService.getUserById(..)]