概述
AOP(Aspect Oriented Programming) 即面向切面编程。面向切面是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程(将交叉业务逻辑封装成成切面,利用AOP功能将切面织入到主业务逻辑———与主业务逻辑无关的代码,使用场景如:安全检查,事物,日志等 。
AOP是一种方式,实现AOP的库有名的有两个AspectJ和spring AOP.
AOP的相关术语
-
连接点(JoinPoint) : 连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点(被代理对象的所有方法)
-
切点(Pointcut): 切入点是指我们要对哪些Joinpoint进行拦截的定义(被代理对象加强的方法)
-
通知(Advice) : 通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
-
目标对象(target): 代理的目标对象, 被通知的对象,也就是真正的业务逻辑;
-
织入(weaving) : 是指把增强应用到目标对象来创建新的代理对象的过程(生成代理对象的过程)
-
切面(Aspect) : 是切入点和通知的结合,以后咱们自己来编写和配置的
AOP的通知类型
-
前置通知 : 在目标类的方法执行之前执行。
- 配置文件信息:<aop: before method=“before” pointcut-ref=“myPointcut3”/>
- 应用:可以对方法的参数来做校验
-
最终通知 : 在目标类的方法执行之后执行,如果程序出现了异常,最终通知也会执行。
-
在配置文件中编写具体的配置:<aop:after method=“after” pointcut-ref=“myPointcut3”/>
-
应用:例如像释放资源
-
-
后置通知 : 方法正常执行后的通知。
-
在配置文件中编写具体的配置:<aop:after-returning method=“afterReturning” pointcut-ref=“myPointcut2”/>
-
应用:可以修改方法的返回值
-
-
异常抛出通知 : 在抛出异常后通知
-
在配置文件中编写具体的配置:<aop:after-throwing method=“afterThorwing” pointcut-ref=“myPointcut3”/>
-
应用:包装异常的信息
-
-
环绕通知: 方法的执行前后执行。环绕通知使用灵活.其他方式都能用环绕方式实现。
-
在配置文件中编写具体的配置:<aop:around method=“around” pointcut-ref=“myPointcut2”/>
-
要注意:目标的方法默认不执行,需要使用ProceedingJoinPoint对来让目标对象的方法执行。
-
使用aspectj实现AOP功能
在pom.xml中引入依赖:
<!--spring aop + aspectj-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!--spring aop + aspectj-->
在Spring Boot中,可以通过添加以下依赖来实现AspectJ的集成:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
该依赖包含了AspectJ的运行时和Spring AOP的实现,因此,您可以在Spring Boot应用程序中使用@Aspect注解来定义切面类。
方式一:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
//注解表明这是一个AspectJ文件,编译器在编译的时候,就会自动去解析,然后将代码注入到相应的JPonit中。
@Aspect //必须的注解
@Order(-1)
public class AOPDemo {
@Pointcut(value = "execution(* com.controller.update(..))")
private void beforePointcut(){
//切面,方法里的内容不会执行
}
@Before(value = "beforePointcut()")
public void before(JoinPoint joinPoint){
//@Before是在方法执行前无法终止原方法执行
System.out.println("前置通知。。。"+joinPoint);
}
@Pointcut(value = "execution(* com.controller.save(..))")
private void afterReturningPointcut(){}
@AfterReturning(value = "afterReturningPointcut()",returning = "result")
public void afterReturning(Object result){
System.out.println("后置通知。。。"+result);
}
@Pointcut(value = "execution(* com.controller.delete(..))")
private void aroundPointcut(){}
@Around(value = "aroundPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//用@Around用环绕通知可以拒绝可以放行
System.out.println("环绕通知。。。");
Object obj = joinPoint.proceed();
System.out.println("环绕通知。。。");
return obj;
}
@Pointcut(value = "execution(* com.controller.findOne(..))")
private void afterThrowingPointcut(){}
@AfterThrowing(value = "afterThrowingPointcut()",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知"+e.getMessage());
}
@Pointcut(value = "execution(* com.controller.findAll(..))")
private void afterPointcut(){}
//|| 关联多个切面
@After(value = "afterPointcut() || afterReturningPointcut()")
public void after(){
System.out.println("最终通知");
}
}
-
execution: 处理JPoint的类型,例如call,execution
例如定义切入点表达式 execution (* com.sample.service.impl…*. *(…)) 整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:表示返回类型, *号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
多个切点例如:
@Before("execution ( android.app.Activity.onCreate(…)) ||execution (* android.app.Activity.onDestroy()) "
JoinPoint 对象
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
常用api:
- Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类
Class等信息- Object[] getArgs(); 获取传入目标方法的参数对象
- Object getTarget(); 获取被代理的对象
- Object getThis(); 获取代理对象
ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,
添加了
- Object proceed() throws Throwable //执行目标方法 ,放行进入被代理对象的方法
- Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法
两个方法.
方式二使用注解:
1.首先,我们需要自定义一个注解类
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
public @interface AopLog {}
2.然后,创建一个切面文件,内部通过一个Pointcut来指定在带有我们上面自定义注解类AopLog注解的所有方法上进行拦截。
@Aspect
public class AopLogPointcut {
//在带有AopLog注解的方法进行切入(注:此处的 * *前面都要有一个空格)
@Pointcut("execution(@com.yn.aspectj.selfmakeaop.AopLog * *(..))")
public void logPointcut(){} //注意,这个函数必须要有实现,否则Java编译器会报错
@After("logPointcut()")
public void onLogPointcutAfter(JoinPoint joinPoint) throws Throwable{
Log.i("AOP","onLogPointcutAfter:"+joinPoint.getSignature());
}
}
3.最后,只需要在要进行切入的函数前加上@AopLog注解即可。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testAopLog();
}
@AopLog
public void testAopLog() {
Log.i("AOP","in testAopLog");
}
}