aop是spring的两大功能模块之一,功能非常强大,为解耦提供了非常优秀的解决方案。

现在就以springboot中aop的使用来了解一下aop。

一:使用aop来完成全局请求日志处理

创建一个springboot的web项目,勾选aop,pom如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>com.example</groupId>  <artifactId>testaop</artifactId>  <version>0.0.1-SNAPSHOT</version>  <packaging>jar</packaging>  <name>testaop</name>  <description>Demo project for Spring Boot</description>  <parent>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-parent</artifactId>  <version>1.5.3.RELEASE</version>  <relativePath/> <!-- lookup parent from repository -->  </parent>  <properties>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  <java.version>1.8</java.version>  </properties>  <dependencies>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-aop</artifactId>  </dependency>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId>  </dependency>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope>  </dependency>  </dependencies>  <build>  <plugins>  <plugin>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-maven-plugin</artifactId>  </plugin>  </plugins>  </build>
</project>  

创建个controller

package com.example.controller;  import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;  /** * Created by wuwf on 17/4/27. * */
@RestController
public class FirstController {  @RequestMapping("/first")  public Object first() {  return "first controller";  }  @RequestMapping("/doError")  public Object error() {  return 1 / 0;  }
}  

创建一个aspect切面类

package com.example.aop;  import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;  import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;  /** * Created by wuwf on 17/4/27. * 日志切面 */
@Aspect
@Component
public class LogAspect {  @Pointcut("execution(public * com.example.controller.*.*(..))")  public void webLog(){}  @Before("webLog()")  public void deBefore(JoinPoint joinPoint) throws Throwable {  // 接收到请求,记录请求内容  ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();  HttpServletRequest request = attributes.getRequest();  // 记录下请求内容  System.out.println("URL : " + request.getRequestURL().toString());  System.out.println("HTTP_METHOD : " + request.getMethod());  System.out.println("IP : " + request.getRemoteAddr());  System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());  System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));  }  @AfterReturning(returning = "ret", pointcut = "webLog()")  public void doAfterReturning(Object ret) throws Throwable {  // 处理完请求,返回内容  System.out.println("方法的返回值 : " + ret);  }  //后置异常通知  @AfterThrowing("webLog()")  public void throwss(JoinPoint jp){  System.out.println("方法异常时执行.....");  }  //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行  @After("webLog()")  public void after(JoinPoint jp){  System.out.println("方法最后执行.....");  }  //环绕通知,环绕增强,相当于MethodInterceptor  @Around("webLog()")  public Object arround(ProceedingJoinPoint pjp) {  System.out.println("方法环绕start.....");  try {  Object o =  pjp.proceed();  System.out.println("方法环绕proceed,结果是 :" + o);  return o;  } catch (Throwable e) {  e.printStackTrace();  return null;  }  }
}  

启动项目

模拟正常执行的情况,访问http://localhost:8080/first,看控制台结果:

URL : http://localhost:8080/first
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.example.controller.FirstController.first
ARGS : []
方法环绕proceed,结果是 :first controller
方法最后执行.....
方法的返回值 : first controller

/****************************分割线****************************/

模拟出现异常时的情况,访问http://localhost:8080/doError,看控制台结果:
方法环绕start.....
URL : http://localhost:8080/doError
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.example.controller.FirstController.error
ARGS : []
java.lang.ArithmeticException: / by zero

......

方法最后执行.....
方法的返回值 : null

/****************************分割线****************************/

通过上面的简单的例子,可以看到aop的执行顺序。知道了顺序后,就可以在相应的位置做切面处理了。

二: 切面方法说明

@Aspect

作用是把当前类标识为一个切面供容器读取

@Before
标识一个前置增强方法,相当于BeforeAdvice的功能

@AfterReturning

后置增强,相当于AfterReturningAdvice,方法退出时执行

@AfterThrowing

异常抛出增强,相当于ThrowsAdvice

@After

final增强,不管是抛出异常或者正常退出都会执行

@Around

环绕增强,相当于MethodInterceptor

/****************************分割线****************************/

各方法参数说明:

除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,本例中的返回值是“first controller”。@AfterThrowing方法里,可以加throwing = "XXX",供读取异常信息,如本例中可以改为:

    //后置异常通知  @AfterThrowing(throwing = "ex", pointcut = "webLog()")  public void throwss(JoinPoint jp, Exception ex){  System.out.println("方法异常时执行.....");  }  

一般常用的有before和afterReturn组合,或者单独使用Around,即可获取方法开始前和结束后的切面。

三:关于切面PointCut的切入点

execution切点函数

execution函数用于匹配方法执行的连接点,语法为:

execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选))

参数部分允许使用通配符:

*  匹配任意字符,但只能匹配一个元素

.. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用

+  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

参考:http://blog.csdn.net/autfish/article/details/51184405

除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询

@annotation()

表示标注了指定注解的目标类方法

例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法

args()

通过目标类方法的参数类型指定切点

例如 args(String) 表示有且仅有一个String型参数的方法

@args()

通过目标类参数的对象类型是否标注了指定注解指定切点

如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法

within()

通过类名指定切点

如 with(examples.chap03.Horseman) 表示Horseman的所有方法

target()

通过类名指定,同时包含所有子类

如 target(examples.chap03.Horseman)  且Elephantman extends Horseman,则两个类的所有方法都匹配

@within()

匹配标注了指定注解的类及其所有子类

如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配

@target()

所有标注了指定注解的类

如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法

this()

大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配

/****************************分割线****************************/

 

逻辑运算符

表达式可由多个切点函数通过逻辑运算组成

&&

与操作,求交集,也可以写成and

例如 execution(* chop(..)) && target(Horseman)  表示Horseman及其子类的chop方法

||

或操作,求并集,也可以写成or

例如 execution(* chop(..)) || args(String)  表示名称为chop的方法或者有一个String型参数的方法

!

非操作,求反集,也可以写成not

例如 execution(* chop(..)) and !args(String)  表示名称为chop的方法但是不能是只有一个String型参数的方法

execution常用于匹配特定的方法,如update时怎么处理,或者匹配某些类,如所有的controller类,是一种范围较大的切面方式,多用于日志或者事务处理等。

其他的几个用法各有千秋,视情况而选择。

以上标红的比较常用。下面来看annotation的。

四:自定义注解

一般多用于某些特定的功能,比较零散的切面,譬如特定的某些方法需要处理,就可以单独在方法上加注解切面。

我们来自定义一个注解:

package com.example.aop;  import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;  /** * Created by wuwf on 17/4/27. */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {  String desc() default "无信息";
}  

注解里提供了一个desc的方法,供被切面的地方传参,如果不需要传参可以不写。

在Controller里加个方法

    @RequestMapping("/second")  @UserAccess(desc = "second")  public Object second() {  return "second controller";  }  

切面类:

package com.example.aop;  import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;  /** * Created by wuwf on 17/4/27. */
@Component
@Aspect
public class UserAccessAspect {  @Pointcut(value = "@annotation(com.example.aop.UserAccess)")  public void access() {  }  @Before("access()")  public void deBefore(JoinPoint joinPoint) throws Throwable {  System.out.println("second before");  }  @Around("@annotation(userAccess)")  public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {  //获取注解里的值  System.out.println("second around:" + userAccess.desc());  try {  return pjp.proceed();  } catch (Throwable throwable) {  throwable.printStackTrace();  return null;  }  }
}  

主要看一下@Around注解这里,如果需要获取在controller注解中赋给UserAccess的desc里的值,就需要这种写法,这样UserAccess参数就有值了。

/****************************分割线****************************/

启动项目,访问http://localhost:8080/second,看控制台:

方法环绕start.....
URL : http://localhost:8080/second
HTTP_METHOD : GET
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.example.controller.FirstController.second
ARGS : []
second around:second
second before
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller

/****************************分割线****************************/

通知结果可以看到,两个aop切面类都工作了,顺序呢就是下面的

spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。
对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。
那不同的切面,顺序怎么决定呢,尤其是同格式的切面处理,譬如两个execution的情况,那spring就是随机决定哪个在外哪个在内了。
所以大部分情况下,我们需要指定顺序,最简单的方式就是在Aspect切面类上加上@Order(1)注解即可,order越小最先执行,也就是位于最外层。像一些全局处理的就可以把order设小一点,具体到某个细节的就设大一点。

Spring boot中使用aop详解相关推荐

  1. Spring Boot的启动器Starter详解

    Spring Boot的启动器Starter详解 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs Spring Boot ...

  2. ElasticSearch——Spring Boot 集成 ES 操作详解

    文章目录 ElasticSearch--Spring Boot 集成 ES 操作详解 1.SpringBoot 集成 ES 2.索引的API操作详解 3.文档的API操作详解 ElasticSearc ...

  3. 轻量级数据库sqlite,spring boot+sqlite的配置详解 (一)

    spring boot+sqlite的配置,及成功运行详解 sqlite数据库的安装与调试 首先,通过sqlite官方地址下载对应的安装包 https://www.sqlite.org/downloa ...

  4. 轻量级数据库sqlite,spring boot+sqlite的配置详解 (二)

    轻量级数据库sqlite,spring boot+sqlite的配置详解 (二) 轻量级数据库sqlite,spring boot+sqlite的配置详解 (一) 首先,需要创建一个spring bo ...

  5. Spring Boot中使用AOP统一处理Web请求日志

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是Spring框架中的一个重要内容,它通 ...

  6. 用Spring Boot开发API请求详解--API开发

    那么,如何写一套漂亮的API接口呢? 本次我们先了解一下Spring对API接口开发的支持,然后我们采用Spring Boot搭建项目,借用Swagger2列出API接口,便于查阅. 返回格式 API ...

  7. Spring Boot jackson配置使用详解

    Spring Boot系列-json框架jackson配置详解 T1 - 前言 目前Java最常见的3中JSON操作框架分别为Gson.Jackson.FastJson,该篇文章主要讲解jackson ...

  8. Spring Boot的SpringApplication类详解

    相信使用过Spring Boot的开发人员,都对Spring Boot的核心模块中提供的SpringApplication类不陌生.SpringApplication类的run()方法往往在Sprin ...

  9. spring boot application.properties 属性详解

    2019年3月21日17:09:59 英文原版: https://docs.spring.io/spring-boot/docs/current/reference/html/common-appli ...

最新文章

  1. 方法 retrun 异步的值,创建一个变量直接等于一个异步方法返回的值
  2. [20170622]传输表空间与dblink.txt
  3. 如何禁止电脑某个程序运行(强制自律)
  4. 交易性金融资产账务处理问题及改进
  5. Android学习记录1--布局的类型
  6. CF626F. Bear and Fair Set
  7. Stackoverflow的见解:投票最多的是Spring 4问题
  8. MySQL + MyBatis 批量插入时存在则忽略或更新记录
  9. Android 4 开发环境配置中的诸多陷阱
  10. 最开始教学html5的人,初识html5的个人看法
  11. Numpy Binary operations
  12. 【嵌入式】使用Cross Toolchain构建交叉工具链
  13. DNS Server 的设置使用
  14. 家用计算机键盘图,电脑键盘示意图,教您如何正确的使用键盘
  15. IDEA找不到应用程序(localhost:8080)
  16. 攻防世界-MISC新手区
  17. 淘宝店铺宝贝转化率该如何提升
  18. ISC2 成功的网络安全领导者必备的9大特质
  19. 84、举办大型群众性活动的消防安全要求
  20. ERP和MES、QAS以及APS在制造企业信息化的了解

热门文章

  1. 跑步记录日期怎么改_快捷增加历史记录-鲨鱼记账App功能优化
  2. python知识笔记_[Python笔记]第一篇:基础知识
  3. 正则表达式(面试会考)
  4. 2020年二级计算机考试真题,2020年3月计算机等级考试《二级MS Office高级应用》历年真题-试题答案...
  5. GIT的初步使用记录
  6. textfield获取其中内容_41页内容介绍电气安装工程,详细介绍施工图纸,值得收藏...
  7. 提交日期表单状态操作_奇怪的知识又增加了,表单还能查寝?
  8. 人脸离线识别模块_人脸识别模块做到市场份额60%,这家AI公司如何用狼性在安防杀开一条血道?...
  9. java数组复制_Java自学-数组 复制数组
  10. Java执行外部命令,并把结果回显到控制台