咱们都知道,Spring 框架做为后端主流框架之一,最有特色的三部分就是IOC控制反转、依赖注入、以及AOP切面。固然AOP做为一个Springhtml
的重要组成模块,固然IOC是不依赖于Spring框架的,这就说明你有权选择是否要用AOP来完成一些业务。前端
AOP面向切面编程,经过另外一种思考的方式,来弥补面向对象编程OOP当中的不足,OOP当中最重要的单元是类,因此万物皆对象,万物皆是git
对象类。而在AOP的模块单元中,最基础的单元是切面,切面对切点进行模块化的管理。web
最后再提一句:Spring当中的AOP是利用Java的代理模式实现的spring
让咱们从一些基础的术语开始了解面向切面编程AOP,术语不是特别的直观,最好的方式就是经过文本理解+图像理解+代码实例理解数据库
这样对于咱们来讲才是真正意义上的理解。编程
说了这么多,都感受迷迷糊糊的,咱们首先来看一个例子吧,经过这个例子来理解AOP切面,经过例子在具体说明后端
版本信息:Springboot 2.1.6api
添加依赖 Web AOPapp
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
写一个最简单的控制器
@RestController @RequestMapping("/") public class AuthController { private Logger logger = LoggerFactory.getLogger(AuthController.class); @GetMapping("login") public String login(String user,String pass) { logger.info("login--->user",user); logger.info("login--->pass",pass); return "success"; } }
这样的控制器在SpringBoot的Web开发当中是很常见的,收到前端的请求,将参数进行校验,咱们这里为了简单,不做校验,只是打印出来,为了配合咱们后面的AOP的理解而
作的一个最简单的控制器,测试请求如下路径,控制台打印如下内容
http://localhost:8080/login?user=root&pass=root
2019-11-09 09:59:00.585 INFO 11100 --- [nio-8080-exec-1] c.e.demo.controller.AuthController : login--->user
2019-11-09 09:59:00.588 INFO 11100 --- [nio-8080-exec-1] c.e.demo.controller.AuthController : login--->pass
一、定义一个切面类,加入@Aspect注解和@Component注解
@Aspect @Component public class WebLogAsp {}
@Aspect 注解将找个类定义为一个切面对象,经过@Component注解将这个类对象注入到IOC容器,交给Spring来进行管理。
二、定义一个切入点 经过@Pointcut
@Pointcut("execution(public * com.example.demo.controller.*.*(..))") public void controllerLog(){}
@Pointcut这个注解主要用来定义切入点,经过表达式的方式,来告诉Spring,我这个切点要切到什么位置,经常使用的就是execution去匹配链接点。
主要来讲一下execution 匹配表达是的表达方法,咱们按照如下的例子来讲明:
@Pointcut("execution(public * com.example.demo.controller.*.*(..))")
语法:execution( [方法修饰符(可选)]__返回类型__类路径__方法名__(参数)__[异常模式(可选)] )
这里我用下划线来代替空格,比较直观的能够看出,咱们这个例子里面,我将这个切点切入到com.example.demo.controller包下全部类的全部方法上面。
*就是通配符,(..)表明任意多个参数,也就说明我切入到的方法它的参数我是不限定的,能够有任意个参数。
三、定义通知项,@Before定义一个前置通知
@Before("controllerLog()") public void beforeAdvice(JoinPoint joinPoint){}
@Before注解传入字符串方法名,也就是咱们上面定义的切入点的方法名,告诉它这个通知是在切入点 controllerLog()上面执行的通知,它会在切入点方法执行以前率先执行
这个方法传入了一个JoinPoint 对象,也就是咱们所说的链接点对象,链接点能够理解为切入点方法执行时候所产生的一个对象。
这里有几个具体的方法很重要,须要说明一下:
Object[] getArgs();获取切入点方法执行时候传入的参数。
Object getTarget();获取的是切入方法所在的类对象,简单理解例子里面的切入点是login()方法,因此返回的对象就是AuthController对象
Object getThis();返回AOP框架为目标对象生成的代理对象。
这里将详细代码举例,经过打印的方式进行输出,经过SpringMvc的RequestContextHolder获取线程的请求对象,这里咱们能够拿到当前请求
的一些具体参数,好比访问人的IP信息,它所请求的URL以及请求所带的参数等待。
具体请参考:https://www.runoob.com/servlet/servlet-client-request.html
@Before("controllerLog()") public void beforeAdvice(JoinPoint joinPoint){ logger.info("前置通知开始--->"); /*获取当前请求的HttpServletRequest*/ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest(); logger.info("URL-->"+request.getRequestURL().toString()); logger.info("IP-->"+request.getRemoteAddr()); logger.info("HTTP_Method-->"+request.getMethod()); logger.info("Request_args-->"+ Arrays.toString(joinPoint.getArgs())); logger.info("前置通知结束--->"); }
四、环绕通知方法 @Around
首先来看一部分代码,这里传递了一个 ProceedingJoinPoint 对象,它是JoinPoint 切点对象的一个子类,也能够为它是切点所在的方法执行时候所产生的一个对象
这里最重要的一个方法当属于 proceed() ;执行proceed()就表示执行切点所在的方法执行,它会返回一个Object对象,也就是目标方法所返回的对象。
@Around("controllerLog()") public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { logger.info("环绕通知开始-->"); Object result = null; /*proceed()方法表示链接点方法执行 result 为链接点方法的返回值*/ result = proceedingJoinPoint.proceed(); logger.info("环绕通知返回值-->" + result); logger.info("环绕通知结束-->"); return result; }
踩坑:环绕方法必定要将result对象返回出去,若定义为void 无返回值的话,将在后面的@AfterReturning 后置通知中没法取到值!!!!
五、后置通知 @AfterReturning 链接点正常执行完毕后的通知
@AfterReturning(value = "controllerLog()",returning = "obj") public void afterReturning(JoinPoint joinPoint,Object obj){ logger.info("后置通知正常返回-->"+obj); }
这里传递了两个参数,第一个参数仍是指定切点,第二个参数指定的是返回值,这个返回值就是链接点所返回的参数值,须要在环绕通知里面将其返回出来才能够
取到值,否则返回的就是Null
六、异常通知 @AfterThrowing
@AfterThrowing(value = "controllerLog()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ logger.info("异常通知-->"+ex.getMessage()); }
这里和上面的后置通知差很少,不过第二个参数是一个异常类型对象,能够取出发生异常时候的异常信息。
七、最终通知 @After
这个和@Before同样,再也不细说!
这里能够很明确的看到,一个正常的返回路径,相信我不说你们均可以看的清楚,画图最清楚的,接收到请求后,首先工做的是环绕通知,
环绕通知里面执行proceed()方法后,才会进入链接点方法执行,Before是在链接点方法执行以前所执行的,然后执行链接点方法内容,
正常执行完后,不发生异常状况,环绕通知结束,就会执行最终通知After After执行完毕后,才会执行后置通知AfterReturning
咱们在控制器里面模拟进行一个异常的抛出,看一下执行的顺序
logger.info("将要抛出异常"); int a = 1/0;
能够很明明显的看到,咱们在控制器里面经过一个除0的操做,进行抛出一个异常,这里环绕通知是没有执行完毕的,由于抛出异常,中止了运行
从接收到请求开始,进入环绕通知,而后环绕通知里面调用了proceed()方法,其实就是让链接点的方法开始运行,这时候前置通知首先跑起来,能够看到
前置通知是完彻底全走完的。前置通知完毕后,下来是链接点的方法运行起来了,这里由于抛出了异常,没有进行捕获,最终通知仍是同样正常执行,不过最后执行的是异常的通知,
而不是像上面同样。这里不一样的地方仍是要多进行理解。
经过对SpringAOP框架的研究,以及画图的理解,可以更深入的理解里面运行时候的内层含义,以及AOP的一些应用,好比系统里面的日志系统,经过学习AOP
完彻底全能够很轻松的经过切面,将须要记录的日志信息存到数据库, 节约大量时间。
https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html
http://shouce.jb51.net/spring/aop.html
http://www.javashuo.com/article/p-riqaztfy-gq.html