AOP为Aspect Oriented Programming
面向切面编程,和动态代理的思想类似(?我觉得)
设计模式——动态代理
1. AOP相关术语介绍
1. Joinpoint(连接点)
连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
2. Pointcut(切入点)
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
3. Advice(通知/增强)
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
4. Introduction(引介)
引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
5. Target(目标对象)
代理的目标对象
6. Weaving(织入)
是指把增强应用到目标对象来创建新的代理对象的过程
7. Proxy(代理)
一个类被AOP织入增强后,就产生一个结果代理类
8. Aspect(切面)
是切入点和通知的结合,以后咱们自己来编写和配置的
9. Advisor(通知器、顾问)
Aspect很相似
2 AspectJ
ApectJ采用的就是静态织入(编译期织入)的方式。使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类
3 Spring AOP
Spring AOP是通过动态代理技术实现的
动态代理技术的实现方式有两种:基于接口的JDK动态代理和基于继承的CGLib动态代理。
3.1 Demo1 前置通知
除了spring IoC中列出的Spring基本依赖,还需要添加spring-aspects依赖
<!-- 基于Aspect的aop依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
3.1.1 定义切面
@Component // 交给IoC容器管理
@Aspect // 定义一个切面类
public class MyAspects {
@Before(value = "execution(* com.cc.service.*.*(..))")
public void log(){
System.out.println("日志处理........");
}
}
3.1.1.1 通知类型
@Before 前置通知
执行时机:在目标对象方法执行之前执行
应用场景:可以在方法开始之前进行校验
@After() 最终通知
执行时机:目标对象方法之后执行通知,有异常则不执行
应用场景:释放资源
@AfterReturning:后置通知
执行时机:目标对象方法之后执行通知,有异常则不执行
应用场景:修改方法的返回值
@AfterThrowing()异常抛出通知
执行时机:在抛出异常后执行
应用场景:包装异常
@Around:环绕通知
执行时机:执行目标对象方法之前和之后都会执行
应用场景:事物、统计代码执行时间等
3.1.1.2 切入点表达式execution
固定格式:execution([修饰符] 返回值 包名.类名.方法名(参数))
修饰符:public等,可省略
返回值:可采用通配符**
包名:多级包名使用 .分割,省略中间的包名可以使用..
类名:可以使用*,也可以写成*ServiceImpl
方法名:可以使用*,也可以写成add*
形参:可以用*代替,多个参数可以写成..
3.1.2 @EnableAspectJAutoProxy
开启AOP
@Configuration
@ComponentScan(basePackages = "com.cc")
@Import(ProertiesConfig.class)
@EnableAspectJAutoProxy
public class SpringConfiguration {
//spring容器初始化时,会调用配置类的无参构造函数
public SpringConfiguration(){
System.out.println("容器启动初始化");
}
}
3.1.3 测试
测试类和业务类都和spring IoC中SpringJunitTest的配置一样
输出结果为:
容器启动初始化
日志处理........
userDao: saveUser:id为100, name为:cc
3.2 Demo2 环绕通知
@Around(value = "execution(* com.cc.service.*.*())")
public Object logTime(ProceedingJoinPoint joinPoint){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String sign = null;
Object rtValue = null;
try {
//获取方法所需的参数
Object[] args = joinPoint.getArgs();
// 获取方法签名
Signature signture =joinPoint.getSignature();
sign = signture.getName();
// 前置通知:打印方法开始时间
System.out.println(sign +" 开始时间:"+sdf.format(new Date()));
Arrays.stream(args).forEach(System.out::println);
// 执行目标方法
rtValue = joinPoint.proceed(args);
}catch (Throwable t){
t.printStackTrace();//异常通知:可以回滚事物等
}finally {
// 可以用来 释放资源
}
// 后置通知:打印结束时间
System.out.println(rtValue +" "+ sign+" 结束时间:"+sdf.format(new Date()));
return rtValue;
}
demo1和demo2结合使用,输出为
容器启动初始化
saveUser 开始时间:2018-12-19 10:54:49 518
日志处理........
userDao: saveUser:id为100, name为:cc
null saveUser 结束时间:2018-12-19 10:54:49 520
3.3 Demo3
@Aspect
@Component
public class MyAspects2 {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String sign;//目标对象的方法
@Before(value = "fun()")
public void startLog(JoinPoint joinPoint){
sign = joinPoint.getSignature().getName();
System.out.println(sign+" 开始:"+sdf.format(new Date()));
}
@Before(value = "fun()")
public void validate(JoinPoint joinPoint){
//目标对象的形参
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(x->System.out.println("参数为:"+x));
System.out.println("参数校验.....");
}
@After(value = "fun()")
public void endLog(JoinPoint joinPoint){
sign = joinPoint.getSignature().getName();
System.out.println(sign+" 结束:"+sdf.format(new Date()));
System.out.println("释放资源.....");
}
@AfterReturning(value = "fun()",returning = "result")
public void endDeal(JoinPoint joinPoint,Object result){
System.out.println("执行结果为:"+result);
System.out.println("结果参数处理.....");
}
@AfterThrowing(value = "fun()",throwing = "ex")
public void throwExpection(JoinPoint joinPoint,Exception ex){
ex.printStackTrace();
System.out.println("异常处理.....");
}
@Pointcut(value = "execution(* *..service.*.*(..))")
public void fun(){
}
}
输出结果
容器启动初始化
saveUser 开始:2018-12-19 15:08:07 209
参数为:com.cc.pojo.User@7e057f43
参数校验.....
userDao: saveUser:id为100, name为:SpringJunit
saveUser 结束:2018-12-19 15:08:07 210
释放资源.....
执行结果为:null
结果参数处理.....