javascript
SpringBoot之AOP详解
面向方面編程(AOP)通過提供另一種思考程序結(jié)構(gòu)的方式來補充面向?qū)ο缶幊?#xff08;OOP)。
OOP中模塊化的關(guān)鍵單元是類,而在AOP中,模塊化單元是方面。
文章目錄
- 準(zhǔn)備工作
- 1. @Pointcut 切入點
- 2.@Before前置通知
- 3.@After 后置通知
- 4.@Around環(huán)繞通知
- 5.@AfterReturning
- 6.@AfterThrowing
- 7.AOP用在全局異常處理
- 8.以上用的是log4j2的日志處理
準(zhǔn)備工作
首先,使用AOP要在pom.xml中加入依賴
//引入AOP依賴
<!-- AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>然后在application.yml中加入
spring:aop:proxy-target-class: true1. @Pointcut 切入點
定義一個切點。
例如我們要在一個方法加上切入點,根據(jù)方法的返回的對象,方法名,修飾詞來寫成一個表達式或者是具體的名字
我們現(xiàn)在來定義一個切點
package com.example.aop;import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;/*** 類定義為切面類*/ @Aspect @Component public class AopTestController {private static final Logger logger = LoggerFactory.getLogger(AopTestController.class);/*** 定義一個切點*/@Pointcut(value = "execution(public String test (..))")public void cutOffPoint() {} }這里的切點定義的方法是
@GetMapping("hello")public String test(){logger.info("歡迎關(guān)注Java知音");return "i love java";}如果你想寫個切入點在所有返回對象為Area的方法,如下
@Pointcut(“execution(public com.example.entity.Area (…))”)
等很多寫法,也可以直接作用在某些包下
注意:private修飾的無法攔截
2.@Before前置通知
在切入點開始處切入內(nèi)容
在之前的AopTestController類中加入對test方法的前置通知
@Before("cutOffPoint()")public void beforeTest(){logger.info("我在test方法之前執(zhí)行");}這里@Before里的值就是切入點所注解的方法名
在方法左側(cè)出現(xiàn)的圖標(biāo)跟過去以后就是所要通知的方法 這里就是配置正確了,我們來瀏覽器調(diào)用一下方法
聯(lián)想一下,這樣的效果可以用在哪里,想像如果要擴展一些代碼,在不需要動源代碼的基礎(chǔ)之上就可以進行拓展,美滋滋
3.@After 后置通知
和前置通知相反,在切入點之后執(zhí)行
@After("cutOffPoint()")public void doAfter(){logger.info("我是在test之后執(zhí)行的");}控制臺執(zhí)行結(jié)果
這里定義一個通知需要重啟啟動類,而修改通知方法的內(nèi)容是可以熱部署的
4.@Around環(huán)繞通知
和前兩個寫法不同,實現(xiàn)的效果包含了前置和后置通知。
當(dāng)使用環(huán)繞通知時,proceed方法必須調(diào)用,否則攔截到的方法就不會再執(zhí)行了
環(huán)繞通知=前置+目標(biāo)方法執(zhí)行+后置通知,proceed方法就是用于啟動目標(biāo)方法執(zhí)行的
ThreadLocal<Long> startTime = new ThreadLocal<>();@Around("cutOffPoint()")public Object doAround(ProceedingJoinPoint pjp){startTime.set(System.currentTimeMillis());logger.info("我是環(huán)繞通知執(zhí)行");Object obj;try{obj = pjp.proceed();logger.info("執(zhí)行返回值 : " + obj);logger.info(pjp.getSignature().getName()+"方法執(zhí)行耗時: " + (System.currentTimeMillis() - startTime.get()));} catch (Throwable throwable) {obj=throwable.toString();}return obj;}執(zhí)行結(jié)果:
5.@AfterReturning
切入點返回結(jié)果之后執(zhí)行,也就是都前置后置環(huán)繞都執(zhí)行完了,這個就執(zhí)行了
/*** 執(zhí)行完請求可以做的* @param result* @throws Throwable*/@AfterReturning(returning = "result", pointcut = "cutOffPoint()")public void doAfterReturning(Object result) throws Throwable {logger.info("大家好,我是@AfterReturning,他們都秀完了,該我上場了");}執(zhí)行結(jié)果
應(yīng)用場景可以用來在訂單支付完成之后就行二次的結(jié)果驗證,重要參數(shù)的二次校驗,防止在方法執(zhí)行中的時候參數(shù)被修改等等
6.@AfterThrowing
這個是在切入執(zhí)行報錯的時候執(zhí)行
// 聲明錯誤e時指定的拋錯類型法必會拋出指定類型的異常// 此處將e的類型聲明為Throwable,對拋出的異常不加限制@AfterThrowing(throwing = "e",pointcut = "cutOffPoint()")public void doAfterReturning(Throwable e) {logger.info("大家好,我是@AfterThrowing,他們犯的錯誤,我來背鍋");logger.info("錯誤信息"+e.getMessage());}在其他切入內(nèi)容中隨意整個錯誤出來,制造一個環(huán)境。
下面是@AfterThrowing的執(zhí)行結(jié)果
7.AOP用在全局異常處理
定義切入點攔截ResultBean或者PageResultBean
@Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))")public void handlerPageResultBeanMethod() {}@Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))")public void handlerResultBeanMethod() {}下面是AopController.java
package com.example.aop;import com.example.beans.PageResultBean; import com.example.beans.ResultBean; import com.example.entity.UnloginException; import com.example.exception.CheckException; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;/*** 使用@Aspect注解將此類定義為切面類* 根據(jù)曉風(fēng)輕著的ControllerAOP所修改* 曉風(fēng)輕大佬(很大的佬哥了):https://xwjie.github.io/*/ @Aspect @Component public class AopController {private static final Logger logger = LoggerFactory.getLogger(AopController.class);ThreadLocal<ResultBean> resultBeanThreadLocal = new ThreadLocal<>();ThreadLocal<PageResultBean<?>> pageResultBeanThreadLocal = new ThreadLocal<>();ThreadLocal<Long> start = new ThreadLocal<>();/*** 定義一個切點*/@Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))")public void handlerPageResultBeanMethod() {}@Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))")public void handlerResultBeanMethod() {}@Around("handlerPageResultBeanMethod()")public Object handlerPageResultBeanMethod(ProceedingJoinPoint pjp) {start.set(System.currentTimeMillis());try {pageResultBeanThreadLocal.set((PageResultBean<?>)pjp.proceed());logger.info(pjp.getSignature() + " 方法執(zhí)行耗時:" + (System.currentTimeMillis() - start.get()));} catch (Throwable e) {ResultBean<?> resultBean = handlerException(pjp , e);pageResultBeanThreadLocal.set(new PageResultBean<>().setMsg(resultBean.getMsg()).setCode(resultBean.getCode()));}return pageResultBeanThreadLocal.get();}@Around("handlerResultBeanMethod()")public Object handlerResultBeanMethod(ProceedingJoinPoint pjp) {start.set(System.currentTimeMillis());try {resultBeanThreadLocal.set((ResultBean<?>)pjp.proceed());logger.info(pjp.getSignature() + " 方法執(zhí)行耗時:" + (System.currentTimeMillis() - start.get()));} catch (Throwable e) {resultBeanThreadLocal.set(handlerException(pjp , e));}return resultBeanThreadLocal.get();}/*** 封裝異常信息,注意區(qū)分已知異常(自己拋出的)和未知異常*/private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {ResultBean<?> result = new PageResultBean();logger.error(pjp.getSignature() + " error ", e);// 已知異常if (e instanceof CheckException) {result.setMsg(e.getLocalizedMessage());result.setCode(ResultBean.FAIL);} else if (e instanceof UnloginException) {result.setMsg("Unlogin");result.setCode(ResultBean.NO_LOGIN);} else {result.setMsg(e.toString());result.setCode(ResultBean.FAIL);}return result;} }用上面的環(huán)繞通知可以對所有返回ResultBean或者PageResultBean的方法進行切入,這樣子就不用在業(yè)務(wù)層去捕捉錯誤了,只需要去打印自己的info日志。
看下面一段代碼
@Transactional@Overridepublic int insertSelective(Area record) {record.setAddress("test");record.setPostalcode(88888);record.setType(3);int i=0;try {i = areaMapper.insertSelective(record);}catch (Exception e){logger.error("AreaServiceImpl insertSelective error:"+e.getMessage());}return i;}假如上面的插入操作失敗出錯了? 你認(rèn)為會回滾嗎?
答案是:不會。
為什么?
因為你把錯誤捕捉了,事物沒檢測到異常就不會回滾。
那么怎么才能回滾呢?
在catch里加throw new RuntimeException().
可是那么多業(yè)務(wù)方法每個設(shè)計修改的操作都加,代碼繁瑣,怎么進行處理呢?
在這里用到上面的AOP切入處理,錯誤不用管,直接拋,拋到控制層進行處理,這樣的話,接口調(diào)用的時候,出錯了,接口不會什么都不返回,而是會返回給你錯誤代碼,以及錯誤信息,便于開發(fā)人員查錯。
8.以上用的是log4j2的日志處理
先移除springboot自帶的log日志處理
在pom.xml中增加
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency>然后在application.yml中增加
logging:level:com:example:dao: debugconfig: classpath:log4j2-spring.xmllog4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!--日志級別以及優(yōu)先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,這個用于設(shè)置log4j2自身內(nèi)部的信息輸出,可以不設(shè)置,當(dāng)設(shè)置成trace時,你會看到log4j2內(nèi)部各種詳細(xì)輸出--> <!--monitorInterval:Log4j能夠自動檢測修改配置 文件和重新配置本身,設(shè)置間隔秒數(shù)--> <configuration status="INFO" monitorInterval="30"><!--先定義所有的appender--><appenders><!--這個輸出控制臺的配置--><console name="Console" target="SYSTEM_OUT"><!--輸出日志的格式--><PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/></console><!--文件會打印出所有信息,這個log每次運行程序會自動清空,由append屬性決定,這個也挺有用的,適合臨時測試用--><File name="Test" fileName="logs/test.log" append="false"><PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/></File><RollingFile name="RollingFileInfo" fileName="logs/log.log" filePattern="logs/info.log.%d{yyyy-MM-dd}"><!-- 只接受level=INFO以上的日志 --><ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/><PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/><Policies><TimeBasedTriggeringPolicy modulate="true" interval="1"/><SizeBasedTriggeringPolicy/></Policies></RollingFile><RollingFile name="RollingFileError" fileName="logs/error.log" filePattern="logs/error.log.%d{yyyy-MM-dd}"><!-- 只接受level=WARN以上的日志 --><Filters><ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" /></Filters><PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/><Policies><TimeBasedTriggeringPolicy modulate="true" interval="1"/><SizeBasedTriggeringPolicy/></Policies></RollingFile></appenders><!--然后定義logger,只有定義了logger并引入的appender,appender才會生效--><loggers><!--過濾掉spring和mybatis的一些無用的DEBUG信息--><logger name="org.springframework" level="INFO"></logger><logger name="org.mybatis" level="INFO"></logger><root level="all"><appender-ref ref="Console"/><appender-ref ref="Test"/><appender-ref ref="RollingFileInfo"/><appender-ref ref="RollingFileError"/></root></loggers> </configuration>之后在你要打印日志的類中增加
private static final Logger logger = LoggerFactory.getLogger(你的類名.class);public static void main(String[] args) {logger.error("error級別日志");logger.warn("warning級別日志");logger.info("info級別日志");}有了日志后就很方便了,在你的方法接收對象時打印下,然后執(zhí)行了邏輯之后打印下, 出錯之后很明確了,就會很少去Debug的,養(yǎng)成多打日志的好習(xí)慣,多打印一點info級別的日志,用來在開發(fā)環(huán)境使用,在上線的時候把打印的最低級別設(shè)置為warning,這樣你的info級別日志也不會影響到項目的重要Bug的打印
總結(jié)
以上是生活随笔為你收集整理的SpringBoot之AOP详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue-cli-service不是内部或
- 下一篇: MySQL 8.0.26 图形化安装教程