SpringAop@Aspect注解实现切面编程

SpringAOP在springboot中如何使用

#什么是aop
## 概念
> aop全称Aspect OrientedProgramming,面向切面,AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足。
通俗点讲就是提供一个为一个业务实现提供切面注入的机制,通过这种方式,在业务运行中将定义好的切面通过切入点绑定到业务中,以实现将一些特殊的逻辑绑定到此业务中。

## aop名词

> * 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。

> * 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

> * 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

> * 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

> * 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。

> * 目标对象(Target Object):被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

> * AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

> * 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

#如何使用

## 首先添加依赖
```
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
```
## 添加aop配置类

```
package com.example.mongo_demo.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

/**
* created by qinming on 2018/5/29
**/
@Configuration
@Aspect
public class AopConfig {
public static final Logger LOGGER = LoggerFactory.getLogger(AopConfig.class);

@Pointcut(value = "execution(* com.example.mongo_demo.ctrl..*(..))")
public void poceed(){

}

@Around("poceed()")
public Object around(ProceedingJoinPoint joinPoint){
try {
LOGGER.error(">>>>>>>>>{}>>>>>>>>","around begin");
Object proceed = joinPoint.proceed();
LOGGER.error(">>>>>>>>>{}>>>>>>>>","around stop");
return proceed;
}catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}

@Before(value = "execution(* com.example.mongo_demo.ctrl..*(..))")
public void before(){
LOGGER.error(">>>>>>{}>>>>>>","before");
}
}
```
> @Aspect:描述一个切面类,定义切面类的时候需要打上这个注解
@Configuration:spring-boot配置类

## 配置切点@Pointcut

> @Pointcut:声明一个切入点,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。
注:作为切入点签名的方法必须返回void 类型(例如上面实例中的poceed()方法)

### 切点表达式
> 现在我们介绍一下最重要的切入点表达式:
如上文所说,定义切入点时需要一个包含名字和任意参数的签名,还有一个切入点表达式,就是execution(* com.example.mongo_demo.ctrl..*(..))这一部分。
切入点表达式的格式:execution([可见性] 返回类型 [声明类型].方法名(参数) [异常])
其中【】中的为可选,其他的还支持通配符的使用:
*:匹配所有字符
..:一般用于匹配多个包,多个参数
+:表示类及其子类
运算符有:&&、||、!
切入点表达式关键词:
> * execution:用于匹配子表达式。
```
//匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意
@Pointcut("execution(* com.cjm.model..*.*(..))")
public void before(){}
```
> * within:用于匹配连接点所在的Java类或者包。
```
//匹配Person类中的所有方法
public void before(){}

//匹配com.cjm包及其子包中所有类中的所有方法
@Pointcut("within(com.cjm..*)")
public void before(){}
```
> * this:用于向通知方法中传入代理对象的引用。
```
@Before("before() && this(proxy)")
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}
```
> * target:用于向通知方法中传入目标对象的引用。
```
@Before("before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}
```
> * args:用于将参数传入到通知方法中。
```
@Before("before() && args(age,username)")
public void beforeAdvide(JoinPoint point, int age, String username){
//处理逻辑
}
```
> * @within :用于匹配在类一级使用了参数确定的注解的类,其所有方法都将被匹配。
```
@Pointcut("@within(com.cjm.annotation.AdviceAnnotation)") - 所有被@AdviceAnnotation标注的类都将匹配
public void before(){}
```

> * @target :和@within的功能类似,但必须要指定注解接口的保留策略为RUNTIME。
```
@Pointcut("@target(com.cjm.annotation.AdviceAnnotation)")
public void before(){}
```

> * @args :传入连接点的对象对应的Java类必须被@args指定的Annotation注解标注。
```
@Before("@args(com.cjm.annotation.AdviceAnnotation)")
public void beforeAdvide(JoinPoint point){
//处理逻辑
}
```
> * @annotation :匹配连接点被它参数指定的Annotation注解的方法。也就是说,所有被指定注解标注的方法都将匹配。
```
@Pointcut("@annotation(com.cjm.annotation.AdviceAnnotation)")
public void before(){}
```
>* bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
```
@Pointcut("bean(person)")
public void before(){}
```

## springaop支持的通知模式

> * @Before:前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
> * @AfterReturning :后置通知:在某连接点正常完成后执行的通知,通常在一个匹配的方法返回的时候执行。可以在后置通知中绑定返回值,如:

```
@AfterReturning("pointcut=com.test.service.CacheDemoService.findById(..))",returning="retVal")
public void doFindByIdCheck(Object retVal) {
// ...
}
```
> * @AfterThrowing:异常通知:在方法抛出异常退出时执行的通知.
> * @After 最终通知。当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
> * @Around:环绕通知:包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
环绕通知最麻烦,也最强大,其是一个对方法的环绕,具体方法会通过代理传递到切面中去,切面中可选择执行方法与否,执行方法几次等。
环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型,在通知体内,调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个Object[]对象-该数组中的值将被作为方法执行时的参数。

### 通知参数

>任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型(环绕通知需要定义第一个参数为ProceedingJoinPoint类型,它是 JoinPoint 的一个子类)。JoinPoint接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)
有时候我们定义切面的时候,切面中需要使用到目标对象的某个参数,如何使切面能得到目标对象的参数。
从例子中可以看出一个方法:
使用args来绑定。如果在一个args表达式中应该使用类型名字的地方 使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。

```
@Before("execution(* findById*(..)) &&" + "args(id,..)")
public void twiceAsOld1(Long id){
System.err.println("切面before执行了。。。。id==" + id);
}
```
```
@Around("execution(List<Account> find*(..)) &&" +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern)
throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
```

 

posted @ 2018-05-25 18:23  JustTheWayIAm  阅读(636)  评论(0编辑  收藏  举报