閱讀PDF版本java
標題有點標題黨了,這裏說的容易犯錯不是Spring AOP的錯,是指使用的時候容易犯錯。本文會以一些例子來展開討論AOP的使用以及使用過程當中容易出錯的點。git
織入說通俗點就是怎麼把加強代碼注入到鏈接點,和被加強的代碼融入到一塊兒。github
新建一個模塊:web
<?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>me.josephzhu</groupId> <artifactId>spring101-aop</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring101-aop</name> <description></description> <parent> <groupId>me.josephzhu</groupId> <artifactId>spring101</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
在這裏咱們引入了jackson,之後咱們會用來作JSON序列化。引入了mybatis啓動器,之後咱們會用mybstis作數據訪問。引入了h2嵌入式數據庫,方便本地測試使用。引入了web啓動器,以後咱們還會來測試一下對web項目的Controller進行注入。
先來定義一下咱們的測試數據類:spring
package me.josephzhu.spring101aop; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; @Data @NoArgsConstructor @AllArgsConstructor public class MyBean { private Long id; private String name; private Integer age; private BigDecimal balance; }
而後,咱們在resources文件夾下建立schema.sql文件來初始化h2數據庫:sql
CREATE TABLE PERSON( ID BIGINT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(255), AGE SMALLINT, BALANCE DECIMAL );
還能夠在resources文件夾下建立data.sql來初始化數據:數據庫
INSERT INTO PERSON (NAME, AGE, BALANCE) VALUES ('zhuye', 35, 1000);
這樣程序啓動後就會有一個PERSON表,表裏有一條ID爲1的記錄。
經過啓動器使用Mybatis很是簡單,無需進行任何配置,建一個Mapper接口:apache
package me.josephzhu.spring101aop; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface DbMapper { @Select("SELECT COUNT(0) FROM PERSON") int personCount(); @Insert("INSERT INTO PERSON (NAME, AGE, BALANCE) VALUES ('zhuye', 35, 1000)") void personInsertWithoutId(); @Insert("INSERT INTO PERSON (ID, NAME, AGE, BALANCE) VALUES (1,'zhuye', 35, 1000)") void personInsertWithId(); @Select("SELECT * FROM PERSON") List<MyBean> getPersonList(); }
這裏咱們定義了4個方法:編程
logging.level.org.mybatis.spring.transaction=DEBUG
如今咱們來建立服務接口:瀏覽器
package me.josephzhu.spring101aop; import java.time.Duration; import java.util.List; public interface MyService { void insertData(boolean success); List<MyBean> getData(MyBean myBean, int count, Duration delay); }
定義了插入數據和查詢數據的兩個方法,下面是實現:
package me.josephzhu.spring101aop; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Duration; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @Service public class MyServiceImpl implements MyService { @Autowired private DbMapper dbMapper; @Transactional(rollbackFor = Exception.class) public void _insertData(boolean success){ dbMapper.personInsertWithoutId(); if(!success) dbMapper.personInsertWithId(); } @Override public void insertData(boolean success) { try { _insertData(success); } catch (Exception ex) { ex.printStackTrace(); } System.out.println("記錄數:" + dbMapper.personCount()); } @Override public List<MyBean> getData(MyBean myBean, int count, Duration delay) { try { Thread.sleep(delay.toMillis()); } catch (InterruptedException e) { e.printStackTrace(); } return IntStream.rangeClosed(1,count) .mapToObj(i->new MyBean((long)i,myBean.getName() + i, myBean.getAge(), myBean.getBalance())) .collect(Collectors.toList()); } }
getData方法咱們就不細說了,只是實現了休眠而後根據傳入的myBean做爲模板組裝了count條測試數據返回。咱們來重點看一下insertData方法,這就是使用Spring AOP的一個坑了。看上去配置啥的都沒問題,可是_insertData是不能生效自動事務管理的。
咱們知道Spring AOP使用代理目標對象方式實現AOP,在從外部調用insertData方法的時候其實走的是代理,這個時候事務環繞能夠生效,在方法內部咱們經過this引用調用_insertData方法,雖然方法外部咱們設置了Transactional註解,可是因爲走的不是代理調用,Spring AOP天然沒法經過AOP加強爲咱們作事務管理。
咱們來建立主程序測試一下:
package me.josephzhu.spring101aop; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; import java.math.BigDecimal; import java.time.Duration; @SpringBootApplication public class Spring101AopApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Spring101AopApplication.class, args); } @Autowired private MyService myService; @Override public void run(String... args) throws Exception { myService.insertData(true); myService.insertData(false); System.out.println(myService.getData(new MyBean(0L, "zhuye",35, new BigDecimal("1000")), 5, Duration.ofSeconds(1))); } }
在Runner中,咱們使用true和false調用了兩次insertData方法。後面一次調用確定會失敗,由於_insert方法中會進行重複ID的數據插入。運行程序後獲得以下輸出:
2018-10-07 09:11:44.605 INFO 19380 --- [ main] m.j.s.Spring101AopApplication : Started Spring101AopApplication in 3.072 seconds (JVM running for 3.74) 2018-10-07 09:11:44.621 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@2126664214 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 2018-10-07 09:11:44.626 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@775174220 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 記錄數:2 2018-10-07 09:11:44.638 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@2084486251 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 2018-10-07 09:11:44.638 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@26418585 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 2018-10-07 09:11:44.642 INFO 19380 --- [ main] o.s.b.f.xml.XmlBeanDefinitionReader : Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml] org.springframework.dao.DuplicateKeyException: ### Error updating database. Cause: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.PERSON(ID)"; SQL statement: INSERT INTO PERSON (ID, NAME, AGE, BALANCE) VALUES (1,'zhuye', 35, 1000) [23505-197] 2018-10-07 09:11:44.689 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@529949842 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 記錄數:3 [MyBean(id=1, name=zhuye1, age=35, balance=1000), MyBean(id=2, name=zhuye2, age=35, balance=1000), MyBean(id=3, name=zhuye3, age=35, balance=1000), MyBean(id=4, name=zhuye4, age=35, balance=1000), MyBean(id=5, name=zhuye5, age=35, balance=1000)]
從日誌的幾處咱們均可以獲得結論,事務管理沒有生效:
那麼,如何解決這個問題呢,有三種方式:
@Override @Transactional(rollbackFor = Exception.class) public void insertData(boolean success) { dbMapper.personInsertWithoutId(); if(!success) dbMapper.personInsertWithId(); }
這裏還容易犯錯的地方是,這裏不能對異常進行捕獲,不然Spring事務代理沒法捕獲到異常也就沒法實現回滾。
那麼原來這段代碼如何不改造實現事務呢?能夠經過AspjectJ編譯時靜態織入實現。整個配置過程以下:
首先在pom中加入下面的配置:
<build> <sourceDirectory>${project.build.directory}/generated-sources/delombok</sourceDirectory> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.projectlombok</groupId> <artifactId>lombok-maven-plugin</artifactId> <version>1.18.0.0</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>delombok</goal> </goals> </execution> </executions> <configuration> <addOutputDirectory>false</addOutputDirectory> <sourceDirectory>src/main/java</sourceDirectory> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.10</version> <configuration> <complianceLevel>1.8</complianceLevel> <source>1.8</source> <aspectLibraries> <aspectLibrary> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> </aspectLibraries> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
這裏的一個坑是ajc編譯器沒法支持lambok,咱們須要先使用lombok的插件在生成源碼階段對lombok代碼進行預處理,而後咱們再經過aspjectj插件來編譯代碼。Pom文件中還須要加入以下依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency>
而後須要配置Spring來使用ASPECTJ的加強方式來作事務管理:
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class Spring101AopApplication implements CommandLineRunner {
從新使用maven編譯代碼後能夠看到,相關代碼已經變了樣:
@Transactional( rollbackFor = {Exception.class} ) public void _insertData(boolean success) { AnnotationTransactionAspect var10000 = AnnotationTransactionAspect.aspectOf(); Object[] var3 = new Object[]{this, Conversions.booleanObject(success)}; var10000.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(this, new MyServiceImpl$AjcClosure1(var3), ajc$tjp_0); } public void insertData(boolean success) { try { this._insertData(success); } catch (Exception var3) { var3.printStackTrace(); } System.out.println("記錄數:" + this.dbMapper.personCount()); }
運行程序能夠看到以下日誌:
2018-10-07 09:35:12.360 DEBUG 19459 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@1169317628 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will be managed by Spring
並且最後輸出的結果是2,說明第二次插入數據總體回滾了。
若是使用IDEA的話還能夠配置先由javac編譯再由ajc後處理,具體參見IDEA官網這裏不詳述。
咱們先使用剛纔說的方法3改造一下代碼,使得Spring AOP能夠處理事務(Aspject AOP功能雖然強大可是和Spring結合的很差,因此咱們接下去的測試仍是使用Spring AOP),刪除aspjectj相關依賴,在IDEA配置回javac編譯器從新編譯項目。本節中咱們嘗試創建第一個咱們的切面:
package me.josephzhu.spring101aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; @Aspect @Component @Slf4j class TransactionalAspect extends TransactionSynchronizationAdapter { @Autowired private DbMapper dbMapper; private ThreadLocal<JoinPoint> joinPoint = new ThreadLocal<>(); @Before("@within(org.springframework.transaction.annotation.Transactional) || @annotation(org.springframework.transaction.annotation.Transactional)") public void registerSynchronization(JoinPoint jp) { joinPoint.set(jp); TransactionSynchronizationManager.registerSynchronization(this); } @Override public void afterCompletion(int status) { log.info(String.format("【%s】【%s】事務提交 %s,目前記錄數:%s", joinPoint.get().getSignature().getDeclaringType().toString(), joinPoint.get().getSignature().toLongString(), status == 0 ? "成功":"失敗", dbMapper.personCount())); joinPoint.remove(); } }
在這裏,咱們的切點是全部標記了@Transactional註解的類以及標記了@Transactional註解的方法,咱們的加強比較簡單,在事務同步管理器註冊一個回調方法,用於事務完成後進行額外的處理。這裏的一個坑是Spring如何實例化切面。經過查文檔或作實驗能夠得知,默認狀況下TranscationalAspect是單例的,在多線程狀況下,可能會有併發,保險起見咱們使用ThreadLocal來存放。運行代碼後能夠看到以下輸出:
2018-10-07 10:01:32.384 INFO 19599 --- [ main] m.j.spring101aop.TransactionalAspect : 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】事務提交 成功,目前記錄數:2 2018-10-07 10:01:32.385 DEBUG 19599 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@1430104337 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will be managed by Spring 2018-10-07 10:01:32.449 DEBUG 19599 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@1430104337 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will be managed by Spring 2018-10-07 10:01:32.449 INFO 19599 --- [ main] m.j.spring101aop.TransactionalAspect : 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】事務提交 失敗,目前記錄數:2
能夠看到Spring AOP作了事務管理,咱們兩次事務提交第一次成功第二次失敗,失敗後記錄數仍是2。這個功能還能夠經過Spring的@TransactionalEventListener註解實現,這裏不詳述。
咱們如今注入的是接口,咱們知道對於這種狀況Spring AOP應該使用的是JDK代理。可是SpringBoot默認開啓了下面的屬性來全局啓用CGLIB代理:
spring.aop.proxy-target-class=true
咱們嘗試把這個屬性設置成false,而後在剛纔的TransationalAspect中的加強方法設置斷點,能夠看到這是一個ReflectiveMethodInvocation:
把配置改成true從新觀察能夠看到變爲了CglibMethodInvocation:
咱們把開關改成false,而後切換到注入實現,運行程序會獲得以下錯誤提示,意思就是我咱們走JDK代理的話不能注入實現,須要注入接口:
The bean 'myServiceImpl' could not be injected as a 'me.josephzhu.spring101aop.MyServiceImpl' because it is a JDK dynamic proxy that implements: me.josephzhu.spring101aop.MyService
咱們修改咱們的MyServiceImpl,去掉實現接口的代碼和@Override註解,使之成爲一個普通的類,從新運行程序能夠看到咱們的代理方式自動降級爲了CGLIB方式(雖然spring.aop.proxy-target-class參數咱們如今設置的是false)。
如今咱們來實現一個複雜點的切面的例子。咱們知道,出錯記錄異常信息,對於方法調用記錄打點信息(若是不知道什麼是打點能夠參看《朱曄的互聯網架構實踐心得S1E4:簡單好用的監控六兄弟》),甚至有的時候爲了排查問題須要記錄方法的入參和返回,這三個事情是咱們常常須要作的和業務邏輯無關的事情,咱們能夠嘗試使用AOP的方式一鍵切入這三個事情的實現,在業務代碼無感知的狀況下作好監控和打點。
首先實現咱們的註解,經過這個註解咱們能夠細化控制一些功能:
package me.josephzhu.spring101aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Metrics { /** * 是否在成功執行方法後打點 * @return */ boolean recordSuccessMetrics() default true; /** * 是否在執行方法出錯時打點 * @return */ boolean recordFailMetrics() default true; /** * 是否記錄請求參數 * @return */ boolean logParameters() default true; /** * 是否記錄返回值 * @return */ boolean logReturn() default true; /** * 是否記錄異常 * @return */ boolean logException() default true; /** * 是否屏蔽異常返回默認值 * @return */ boolean ignoreException() default false; }
下面咱們就來實現這個切面:
package me.josephzhu.spring101aop; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; @Aspect @Component @Slf4j public class MetricsAspect { private static ObjectMapper objectMapper = new ObjectMapper(); @Around("@annotation(me.josephzhu.spring101aop.Metrics) || @within(org.springframework.stereotype.Controller)") public Object metrics(ProceedingJoinPoint pjp) throws Throwable { //1 MethodSignature signature = (MethodSignature) pjp.getSignature(); Metrics metrics; String name; if (signature.getDeclaringType().isInterface()) { Class implClass = pjp.getTarget().getClass(); Method method = implClass.getMethod(signature.getName(), signature.getParameterTypes()); metrics = method.getDeclaredAnnotation(Metrics.class); name = String.format("【%s】【%s】", implClass.toString(), method.toString()); } else { metrics = signature.getMethod().getAnnotation(Metrics.class); name = String.format("【%s】【%s】", signature.getDeclaringType().toString(), signature.toLongString()); } //2 if (metrics == null) metrics = new Metrics() { @Override public boolean logException() { return true; } @Override public boolean logParameters() { return true; } @Override public boolean logReturn() { return true; } @Override public boolean recordFailMetrics() { return true; } @Override public boolean recordSuccessMetrics() { return true; } @Override public boolean ignoreException() { return false; } @Override public Class<? extends Annotation> annotationType() { return Metrics.class; } }; RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes != null) { HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); if (request != null) name += String.format("【%s】", request.getRequestURL().toString()); } //3 if (metrics.logParameters()) log.info(String.format("【入參日誌】調用 %s 的參數是:【%s】", name, objectMapper.writeValueAsString(pjp.getArgs()))); //4 Object returnValue; Instant start = Instant.now(); try { returnValue = pjp.proceed(); if (metrics.recordSuccessMetrics()) log.info(String.format("【成功打點】調用 %s 成功,耗時:%s", name, Duration.between(Instant.now(), start).toString())); } catch (Exception ex) { if (metrics.recordFailMetrics()) log.info(String.format("【失敗打點】調用 %s 失敗,耗時:%s", name, Duration.between(Instant.now(), start).toString())); if (metrics.logException()) log.error(String.format("【異常日誌】調用 %s 出現異常!", name), ex); if (metrics.ignoreException()) returnValue = getDefaultValue(signature.getReturnType().toString()); else throw ex; } //5 if (metrics.logReturn()) log.info(String.format("【出參日誌】調用 %s 的返回是:【%s】", name, returnValue)); return returnValue; } private static Object getDefaultValue(String clazz) { if (clazz.equals("boolean")) { return false; } else if (clazz.equals("char")) { return '\u0000'; } else if (clazz.equals("byte")) { return 0; } else if (clazz.equals("short")) { return 0; } else if (clazz.equals("int")) { return 0; } else if (clazz.equals("long")) { return 0L; } else if (clazz.equals("flat")) { return 0.0F; } else if (clazz.equals("double")) { return 0.0D; } else { return null; } } }
看上去代碼量不少,其實實現比較簡單:
2018-10-07 10:47:00.813 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【入參日誌】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 的參數是:【[true]】 2018-10-07 10:47:00.864 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【成功打點】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 成功,耗時:PT-0.048S 2018-10-07 10:47:00.864 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【出參日誌】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 的返回是:【null】 2018-10-07 10:47:00.927 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【入參日誌】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 的參數是:【[false]】 2018-10-07 10:47:01.084 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【失敗打點】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 失敗,耗時:PT-0.156S 2018-10-07 10:47:01.102 ERROR 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【異常日誌】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 出現異常! 2018-10-07 10:47:01.231 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【入參日誌】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public java.util.List me.josephzhu.spring101aop.MyServiceImpl.getData(me.josephzhu.spring101aop.MyBean,int,java.time.Duration)】 的參數是:【[{"id":0,"name":"zhuye","age":35,"balance":1000},5,{"seconds":1,"zero":false,"nano":0,"units":["SECONDS","NANOS"],"negative":false}]】 2018-10-07 10:47:02.237 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【成功打點】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public java.util.List me.josephzhu.spring101aop.MyServiceImpl.getData(me.josephzhu.spring101aop.MyBean,int,java.time.Duration)】 成功,耗時:PT-1.006S 2018-10-07 10:47:02.237 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【出參日誌】調用 【class me.josephzhu.spring101aop.MyServiceImpl】【public java.util.List me.josephzhu.spring101aop.MyServiceImpl.getData(me.josephzhu.spring101aop.MyBean,int,java.time.Duration)】 的返回是:【[MyBean(id=1, name=zhuye1, age=35, balance=1000), MyBean(id=2, name=zhuye2, age=35, balance=1000), MyBean(id=3, name=zhuye3, age=35, balance=1000), MyBean(id=4, name=zhuye4, age=35, balance=1000), MyBean(id=5, name=zhuye5, age=35, balance=1000)]】 [MyBean(id=1, name=zhuye1, age=35, balance=1000), MyBean(id=2, name=zhuye2, age=35, balance=1000), MyBean(id=3, name=zhuye3, age=35, balance=1000), MyBean(id=4, name=zhuye4, age=35, balance=1000), MyBean(id=5, name=zhuye5, age=35, balance=1000)]
正確實現了參很多天志、異常日誌、成功失敗打點(含耗時統計)等功能。
下面咱們建立一個Controller來測試一下是否能夠自動切入Controller:
package me.josephzhu.spring101aop; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.List; @Controller public class MyController { @Autowired private DbMapper dbMapper; @ResponseBody @GetMapping("/data") public List<MyBean> getPersonList(){ return dbMapper.getPersonList(); } }
運行程序打開瀏覽器訪問http://localhost:8080/data後能看到以下輸出:
2018-10-07 10:49:53.811 INFO 19737 --- [nio-8080-exec-1] me.josephzhu.spring101aop.MetricsAspect : 【入參日誌】調用 【class me.josephzhu.spring101aop.MyController】【public java.util.List me.josephzhu.spring101aop.MyController.getPersonList()】【http://localhost:8080/data】 的參數是:【[]】 2018-10-07 10:49:53.819 INFO 19737 --- [nio-8080-exec-1] me.josephzhu.spring101aop.MetricsAspect : 【成功打點】調用 【class me.josephzhu.spring101aop.MyController】【public java.util.List me.josephzhu.spring101aop.MyController.getPersonList()】【http://localhost:8080/data】 成功,耗時:PT-0.008S 2018-10-07 10:49:53.819 INFO 19737 --- [nio-8080-exec-1] me.josephzhu.spring101aop.MetricsAspect : 【出參日誌】調用 【class me.josephzhu.spring101aop.MyController】【public java.util.List me.josephzhu.spring101aop.MyController.getPersonList()】【http://localhost:8080/data】 的返回是:【[MyBean(id=1, name=zhuye, age=35, balance=1000), MyBean(id=2, name=zhuye, age=35, balance=1000)]】
最後,咱們再來踩一個坑。咱們來測一下ignoreException吞掉異常的功能(默認爲false):
@Transactional(rollbackFor = Exception.class) @Override @Metrics(ignoreException = true) public void insertData(boolean success){ dbMapper.personInsertWithoutId(); if(!success) dbMapper.personInsertWithId(); }
這個功能會吞掉異常,在和Transactional事務管理結合時候會不會出問題呢?
開啓這個配置後刷新頁面能夠看到數據庫內有三條記錄了,說明第二次的insertData方法執行沒有成功回滾事務。這也是合情合理的,畢竟咱們的MetricsAspect吃掉了異常。
怎麼繞開這個問題呢?答案是咱們須要手動控制一下咱們的切面的執行優先級,咱們但願這個切面優先級比Spring事務控制切面優先級低:
@Aspect @Component @Slf4j @Order(1) public class MetricsAspect {
再次運行程序能夠看到事務正確回滾。
本文咱們經過一些例子覆蓋了以下內容:
在整個過程當中,也踩了下面的坑,印證的本文的標題:
老樣子,本系列文章代碼見個人github:https://github.com/JosephZhu1983/Spring101。