AOP:
AOP介绍:
什么是AOP:
Aspect Oriented Programming面向切面编程。OOP中遇到的一些问题。是OOP的延续和扩展
在程序运行期间。
优势:
将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式,面向切面编程。AOP可以进行权限校验,日志记录,性能监控,事务控制。
Spring的AOP:
AOP思想最早是由AOP联盟组织提出的。Spring是使用这种思想最好的框架。
Spring两套AOP开发方式:
Spring传统开发方式(弃用)。
Spring基于AspectJ的AOP的开发(使用)。
底层实现(代理机制):
Spring的AOP的底层用到两种代理机制,Spring的AOP根据相关类有没有实现接口动态的在以上两种动态代理机制中切换。
JDK的动态代理:
针对实现了接口的类产生代理。
Cglib的动态代理(第三方技术):
针对没有实现接口的类产生代理。应用的是底层的字节码增强的技术生成当前类的子类对象。
注:Spring通过IOC得到的对象有AOP增强,自己new出来的对象不会被增强。
AOP相关术语:
Joinpoint(连接点):
在程序执行过程中,需要拦截的方法。根据规则,可以指定拦截的方法,我们将每一个被拦截的方法称为连接点。如:UserService中的save()方法就是连接点。
Pointcut(切入点):
切入点,就是拦截方法设置的规则,连接点的一系列集合。
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):
所谓通知是指拦截到Joinpoint 之后所要做的事情就是通知。
通知/增强类型:
前置通知:
public void aaa(JoinPoint joinPoint) {
System.out.println("前置增强===========");
System.out.println(joinPoint);
}
<aop:before method="aaa" pointcut-ref="pointcut1" />
作用:在目标方法执行之前执行。可以使用切入点信息。
后置通知:
public void after(JoinPoint joinPoint,String result) {
System.out.println("后置通知:" + result);
}
<aop:after-returning method="after" pointcut-ref="pointcut1" returning="result"/>
作用:在目标方法执行之后执行。可以获取切入点信息,获取方法返回值。
环绕通知:
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("环绕通知前增强");
Object obj = null;
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("环绕通知后增强");
return obj;
}
<aop:around method="around" pointcut-ref="pointcut3"/>
作用:在目标方法执行前和执行后执行。
注意:
-
ProceedingJoinPoint对象.proceed();返回的是Object类型。
-
切面类的环绕方法返回值类型可以是其他类型。
3)如果被环绕的方法是有参数的:
Object[] args = pjp.getArgs();//得到方法执行所需的参数
pjp.proceed(args);
4)切入点方法如果产生异常,环绕通知方法内应该捕获处理(横向捕获);调用切入点方法的地方也要捕获异常(纵向捕获)。如果切入点抛出的异常被环绕通知捕获了,纵向就不会再捕获。
异常抛出通知:
public void afterThrow(Throwable ex) {
System.out.println("异常抛出通知:" + ex.getMessage());
}
<aop:after-throwing method="afterThrow" pointcut-ref="pointcut1" throwing="ex"/>
作用:在目标方法执行出现异常的时候执行。
注意:
切入点抛出了异常,虽然会被切面类抛出通知方法处理,但是依然需要在调用切入点方法的地方捕获异常。
异常抛出通知仅仅在切入点异常抛出的时候仅仅相关操作,并没有捕获异常
最终通知:
<aop:after method="after" pointcut-ref="pointcut1"/>
作用:不论异常有没有抛出,都会执行,相当于finally
Target(目标对象):
被代理的目标对象,方法等。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而 AspectJ 采用编译期织入和类装在期织入。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知的结合。
execution(表达式):
表达式格式:
[方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)
…代表任意参数:
public * cn.itcast.spring.dao.*.*(…)
*dao表示spring包下的所有dao:
* cn.itcast.spring.*dao.*.*(…)
+代表当前类和其子类:
* cn.itcast.spring.dao.UserDao+.*(…)
dao…表示spring包下的所有包:
* cn.itcast.spring.dao…*.*(…)
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
AOP-切面类指定增强:
切面类:
@Component
@Aspect
public class WebAspect {
/**
* 切入点方法,返回值为__void
* 匹配 com.example.controller1 包及其子包下的所有类的所有方法
*/
@Pointcut("execution(* com.redfish.webdemo.aoptest.aspectassign…*.*(…))")
public void executePackage(){
}
/**
* 前置通知,目标方法调用前被调用
* _ @param _ joinPoint
*/
@Before("executePackage()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("- - - - - 前置通知- - - - -");
Signature signature = joinPoint.getSignature();
System.out.println("返回目标方法的签名:"+signature);
System.out.println("代理的是哪一个方法:"+signature.getName());
Object[] obj = joinPoint.getArgs();
System.out.println("获取目标方法的参数信息:"+ Arrays.asList(obj));
}
/**
* 后置最终通知,目标方法执行完执行,不论异常如何,该通知一定会执行
*/
@After("executePackage()")
public void afterAdvice(){
System.out.println("- - - - - 后置最终通知- - - - -");
}
/**
* 后置返回通知,如果目标方法异常没有被环绕通知或其他方式捕获,该通知不会执行
* 如果参数中的第一个参数为 JoinPoint ,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为 JoinPoint ,则第一个参数为 returning 中对应的参数
* returning 只有目标方法返回值与通知方法相同应参数类型时才能执行后置返回通知,否则不执行
* _ @param _ joinPoint
* _ @param _ keys
*/
@AfterReturning(value = "execution(* com.redfish.webdemo.aoptest.aspectassign…*.*(…))",returning = "keys")
public void afterReturningAdvice(JoinPoint joinPoint,String keys){
System.out.println("- - - - - 后置返回通知- - - - -");
System.out.println("后置返回通知 返回值:"+keys);
}
/**
* 后置异常通知
* 定义一个名字( throwing = "exception" ),该名字用于匹配通知实现方法的一个参数名_(NullPointerException exception),当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法定义的名字(exception)__;_
* throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* _ @param _ joinPoint
* _ @param _ exception
*/
@AfterThrowing(value = "executePackage()",throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint,NullPointerException exception){
System.out.println("- - - - - 后置异常通知- - - - -");
}
/**
* 环绕通知:
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是 org.aspectj.lang.ProceedingJoinPoint 类型
*/
@Around("execution(* com.redfish.webdemo.aoptest.aspectassign.AopController1.testAround(…))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("- - - - - 环绕通知- - - -");
System.out.println("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName());
try {//obj之前可以写目标方法执行前的逻辑
Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
System.out.println("- - - - - 环绕通知end - - - -");
}
return null;
}
}
目标类:
@RestController
@RequestMapping("/aop1")
public class AopController1 {
@RequestMapping("/test")
public String testAop(String key){
return "key="+key;
}
@RequestMapping("/testAfterThrowing")
public String testAfterThrowing(String key){
throw new NullPointerException();
}
@RequestMapping("/testAround")
public String testAround(String key){
return "key="+key;
}
}
AOP-注解指定增强:
切面类:
@Component
@Aspect
public class WebAspectAnno {
/**
* 切入点,使用了该注解的地方会被增强
* 用注解
*/
@Pointcut("@annotation(com.redfish.webdemo.aoptest.annotationassign.WebDesc)")
public void executeAnnotation(){
}
@Before("executeAnnotation()")
public void beforeAdviceAnnotation(){
System.out.println("- - - - - 前置通知annotation - - - - -");
}
/**
* @annotation(webDesc) 中的 webDesc 和环绕增强方法的参数 (WebDesc webDesc) 对应,对使用了 webDesc__注解的方法进行增强
*
* _ @param _ proceedingJoinPoint
* _ @param _ webDesc
* _ @return _
*/
@Around("@annotation(webDesc)")
public Object aroundAnnotation(ProceedingJoinPoint proceedingJoinPoint, WebDesc webDesc){
System.out.println("- - - - - 环绕通知annotation - - - -");
//获取注解里的值
System.out.println("注解的值:" + webDesc.describe());
try {//obj之前可以写目标方法执行前的逻辑
Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
System.out.println("- - - - - 环绕通知annotation end - - - -");
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
目标类:
@RestController
@RequestMapping("/aop2")
public class AopController2 {
@RequestMapping("/testAnnotation")
@WebDesc(describe = "This is testAnnotation Controller")
@Before("fd")
public String testAnnotation(String key){
return "key="+key;
}
}
JoinPoint:
除@Around外,每个方法里都可以加或者不加参数JoinPoint。JoinPoint包含了类名、被切面的方法名、参数等属性。@Around参数必须为ProceedingJoinPoint。