小白的springboot之路(十四)、AOP
程序员文章站
2023-11-02 15:18:10
0、前言 1、什么是AOP AOP(面向切面编程),是一种横切技术,是对OOP的补充和完善; 使用AOP的横切,可以对系统进行无侵入性的日志监听、事务、权限管理等; 思想上跟拦截器其实类似;拦截器是对action进行拦截处理,AOP是对切面进行拦截处理,其实切面也属于一种action集合; AOP可 ......
0、前言
1、什么是aop
aop(面向切面编程),是一种横切技术,是对oop的补充和完善;
使用aop的横切,可以对系统进行无侵入性的日志监听、事务、权限管理等;
思想上跟拦截器其实类似;拦截器是对action进行拦截处理,aop是对切面进行拦截处理,其实切面也属于一种action集合;
aop可以很好解耦;
2、aop的组成
aspect:切面;
join point:连接点;
advice:通知,在切入点上执行的操作;
poincut:带有通知的连接点;
target:被通知的对象;
aop proxy;aop代理;
其中,advice(通知)分为以下几种:
- before(前置通知): 在方法开始执行前执行
- after(后置通知): 在方法执行后执行
- afterreturning(返回后通知): 在方法返回后执行
- afterthrowing(异常通知): 在抛出异常时执行
- around(环绕通知): 在方法执行前和执行后都会执行
around > before > around > after > afterreturning
一、实现示例
光看理论和定义,很多人可能都觉得很难理解,其实用法比较简单,不难的,
我们先来个简单的例子,看完例子你可能就豁然开朗了,
所谓程序员,好看书不如多动手:
实现:
1、添加依赖
<!-- 8、集成aop --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid> </dependency>
2、添加切面类logaspect
package com.anson.common.aspect; import org.aspectj.lang.joinpoint; import org.aspectj.lang.proceedingjoinpoint; import org.aspectj.lang.annotation.*; import org.slf4j.logger; import org.slf4j.loggerfactory; 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; /** * @description: aop切面 * @author: anson * @date: 2019/12/20 10:11 */ @aspect //1、添加aop相关注解 @component public class logaspect { private static final logger logger = loggerfactory.getlogger(logaspect.class); //2、定义切入点(可以匹配、注解的方式,可混用) // @pointcut("execution(public * com.anson.controller.*.*(..))") @pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.logannotation)") // @pointcut("execution(public * com.anson.controller.testcontroller.get*()) && @annotation(com.anson.common.annotation.logannotation)") public void pointcut(){} //===========通知 多中通知可根据需要灵活选用,一般before 、afterreturning即可======================= /** * 前置通知:在连接点之前执行的通知 * @param joinpoint * @throws throwable */ @before("pointcut()") public void dobefore(joinpoint joinpoint) throws throwable { // 接收到请求,记录请求内容 servletrequestattributes attributes = (servletrequestattributes) requestcontextholder.getrequestattributes(); httpservletrequest request = attributes.getrequest(); // 记录下请求内容 logger.info("url : " + request.getrequesturl().tostring()); logger.info("http_method : " + request.getmethod()); logger.info("ip : " + request.getremoteaddr()); logger.info("class_method : " + joinpoint.getsignature().getdeclaringtypename() + "." + joinpoint.getsignature().getname()); logger.info("args : " + arrays.tostring(joinpoint.getargs())); } @afterreturning(returning = "ret",pointcut = "pointcut()") public void doafterreturning(object ret) throws throwable { // 处理完请求,返回内容 logger.info("response : " + ret); } //============================================== @after("pointcut()") public void commit() { logger.info("after commit"); } @afterthrowing("pointcut()") public void afterthrowing() { logger.info("afterthrowing afterthrowing rollback"); } @around("pointcut()") public object around(proceedingjoinpoint joinpoint) throws throwable { try { system.out.println("around"); return joinpoint.proceed(); } catch (throwable e) { e.printstacktrace(); throw e; } finally { logger.info("around"); } } }
需要注意的是:上面代码注释2的地方【2、定义切入点(可以匹配、注解的方式,可混用)】;
简单点说就是通过匹配或者注解(也可两种同时使用)匹配哪些类的哪些方法;
再直白点说,就是我们要对哪些类的哪些方法执行处理,要处理的范围是哪些;
类用*作为通配符,方法用(..)表示,括号中的两点表示匹配任何参数,包括没有参数;
上面这样其实就可以完成一个切面了,
比如将切面定义那里改为:@pointcut("execution(public * com.anson.controller.*.*(..))")
那么运行程序后,所有com.anson.controller包下的所有类的所有方法,都会执行这个切面定义的方法进行相关写日志处理了,结果如下:
around [2019-12-20 11:19:02,205][info ][http-nio-8095-exec-1] url : http://localhost:8095/user/userall (logaspect.java:45) [2019-12-20 11:19:02,205][info ][http-nio-8095-exec-1] http_method : get (logaspect.java:46) [2019-12-20 11:19:02,206][info ][http-nio-8095-exec-1] ip : 0:0:0:0:0:0:0:1 (logaspect.java:47) [2019-12-20 11:19:02,207][info ][http-nio-8095-exec-1] class_method : com.anson.controller.usercontroller.getuserall (logaspect.java:48) [2019-12-20 11:19:02,208][info ][http-nio-8095-exec-1] args : [] (logaspect.java:49) [2019-12-20 11:19:02,510][debug][http-nio-8095-exec-1] ==> preparing: select id, username, password, realname from user (basejdbclogger.java:143) [2019-12-20 11:19:02,606][debug][http-nio-8095-exec-1] ==> parameters: (basejdbclogger.java:143) [2019-12-20 11:19:02,631][debug][http-nio-8095-exec-1] <== total: 4 (basejdbclogger.java:143) [2019-12-20 11:19:02,634][info ][http-nio-8095-exec-1] around (logaspect.java:77) [2019-12-20 11:19:02,635][info ][http-nio-8095-exec-1] after commit (logaspect.java:60) [2019-12-20 11:19:02,635][info ][http-nio-8095-exec-1] response : com.anson.common.result.resultbody@6d9947d (logaspect.java:55)
如果不用注解的话,上面就已经完成一个切面了,如果用注解来定义切面范围呢,好,也简单,我们来定义一个注解
--------------华丽丽的分割线-------------------------------
--------------增加自定义注解的方式----------------------------
3、添加一个logannotation注解
package com.anson.common.annotation; import java.lang.annotation.*; /** * @description: 自定义注解 * @author: anson * @date: 2019/12/20 10:32 */ @documented @retention(retentionpolicy.runtime) @target(elementtype.method) public @interface logannotation
{ }
这样就可以了,然后,在需要的地方,加入这个自定义注解:
//2、获取所有用户 @apioperation(value = "获取所有用户", notes = "获取所有用户") @requestmapping(value="/userall",method= requestmethod.get) @logannotation //自定义注解 public resultbody getuserall() { list<user> users = userservice.getall(); return resultbody.success(users,"获取所有用户信息成功"); }
同时,修改切面范围的定义即可:
//2、定义切入点(可以匹配、注解的方式,可混用)--以下表示范围为:controller包下所有包含@logannotation注解的方法
@pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.logannotation)")
public void pointcut(){}
完了,就这么简单;
至于什么地方该使用aop,以及aop和拦截器用哪个比较好,这个就要根据业务场景灵活取舍了,掌握了思想,具体使用那就可以灵活发挥了