一、AOP介绍
AOP ,面向切面编程,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。其实就是在代码运行,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理
先讲一下AspectJ和Spring AOP关系,网上很多文章对AspectJ存在错误说法
AspectJ:
- AspectJ 来自于 Eclipse 基金会,是Eclipse托管给Apache基金会的一个开源项目
- 属于静态织入,它是通过修改代码来实现的,它的织入时机可以是:
- Compile-time weaving:编译期织入
- Post-compile weaving:也就是已经生成了 .class 文件,就要用到编译后织入
- Load-time weaving:指的是在加载类的时候进行织入
- AspectJ框架非常强大,它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入)
- AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的
SpringAOP:
- 基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现
- Spring AOP 和AspectJ并没有什么太多的关系,仅仅是Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能
- Spring AOP 需要依赖于 IOC 容器来管理,只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法
- Spring AOP 比 AspectJ 的性能稍差
二、Spring AOP术语解释
Joinpoint(连接点)
所谓连接点是指能够被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点(任何一个方法都可以称为连接点)
Pointcut(切入点)
切入点是指我们要对哪些Joinpoint进行拦截的定义(对哪个方法进行增强)
Advice(通知/增强)
通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)(要给它增加什么功能)
Target(目标对象)
代理的目标对象
Weaving(织入)
是指把增强应用到目标对象来创建新的代理对象的过程(怎样得到代理对象)
Proxy(代理)
一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面)
是切入点和通知的结合,构成切面,我们可以使用注解或者xml进行配置
三、Spring AOP注解使用
1. 在 xml 中配置
开启 @AspectJ 的注解,还有其它方式,这里不介绍
<aop:aspectj-autoproxy/>
2. 使用@Aspect注解
定义实现AOP的配置类
@Aspect 注解要作用在 bean 上面
@Component
@Aspect
public class LogAspect {
}
3. 配置 Pointcut
用于定义哪些方法需要被增强或者说需要被拦截
@Pointcut(""execution(* com.ljj.service(..))"")
private void controllerAspect() {
// TODO Auto-generated method stub
}
@Pointcut("@annotation(com.ljj.annotation.Log)")
private void controllerAspect1() {
// TODO Auto-generated method stub
}
@Pointcut(""within(com.ljj.service..*)"")
private void controllerAspect2() {
// TODO Auto-generated method stub
}
@Pointcut("bean(*Service)")
private void controllerAspect3() {
// TODO Auto-generated method stub
}
- execution ,正则匹配方法签名
- @annotation。匹配对应注解的方法
- within,指定所在类或所在包下面的方法
- bean(idOrNameOfBean), 匹配 bean 的名字
4. 配置 Advice
@Aspect
public class AdviceExample {
// 下面方法就是写拦截 "dao层实现"
@Before("com.ljj.aop.dataAccessOperation()")
public void doAccessCheck() {
// ... 实现代码
}
@Before("execution(* com.ljj.dao.*.*(..))")
public void doAccessCheck() {
// ... 实现代码
}
@AfterReturning("com.ljj.aop.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning(
pointcut="com.ljj.aop.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// 这样,进来这个方法的处理时候,retVal 就是相应方法的返回值,是不是非常方便
// ... 实现代码
}
// 异常返回
@AfterThrowing("com.ljj.aop.dataAccessOperation()")
public void doRecoveryActions() {
// ... 实现代码
}
@AfterThrowing(
pointcut="com.ljj.aop.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ... 实现代码
}
// 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
@After("com.ljj.aop.dataAccessOperation()")
public void doReleaseLock() {
// 通常就像 finally 块一样使用,用来释放资源。
// 无论正常返回还是异常退出,都会被拦截到
}
// 既能做 @Before 的事情,也可以做 @AfterReturning 的事情
@Around("com.ljj.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
上面Advice都已经匹配了对应的PointCut,这样定义可以不用再定义PointCut了。也可以使用下面这种PointCut + Advice
//定义日志注解
@Target({ ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
//操作名
String optName();
//操作类型
OperateType optType();
//操作表名
String optTable();
//操作编码
String optCode();
}
@Pointcut("@annotation(com.ljj.annotation.Log)")
private void controllerAspect() {
// TODO Auto-generated method stub
}
//日志切面
@Around("controllerAspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
//方法执行前操作
result = point.proceed();
//方法执行后操作
} catch (Exception e) {
e.printStackTrace();
}
}