基於Springboot的Spring AOP學習記錄
前段時間各種面試,aop問到就蒙逼,所以結合新學的springboot重新理一下這玩意兒,很重要啊
一、AOP概述
AOP(面對切面程式設計)是對OOP(面向物件程式設計)的補充,總體來說,程式設計正規化包含:面向過程程式設計、面向物件程式設計、函數語言程式設計、事件驅動程式設計、面向切面程式設計。
AOP的出現主要是為了解決如下的幾個問題:
1.程式碼重複性的問題
2.關注點的分離(包含了水平分離:展示層->服務層->持久層;垂直分離:模組劃分(訂單、庫存等);切面分離:分離功能性需求與非功能性需求)
AOP使用優勢:
1.集中處理某一關注點/橫切邏輯
2.可以方便的新增/刪除關注點
3.侵入性少,增強程式碼的可讀性和可維護性
AOP的應用場景:
許可權控制、快取控制、事務控制、審計日誌、效能監控、分散式追蹤、異常處理
所以這麼優秀的東西絕不僅僅侷限於Java,AOP是支援多語言開發的。
二、SpringAOP的使用
Spring AOP的使用方式包含XML配置和註解方式(比較方便),下面看一下基於註解方式的AOP。
AOP的註解主要包括@Aspect、@Pointcut、Advice三種。在詳細記錄前先給出一個AOP的簡單使用程式碼(流程:引入依賴–>定義切面類–>在切面類中定義Advice以及前後邏輯),如果要獲取方法的一些屬性(比如方法名,返回值、引數等等),除了環繞通知需要傳入ProceedingJoinPoint物件,其他的Advice則是需要傳入JoinPoint物件,兩類不同!
<!-- 新增AOP的依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
package com.imooc.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect//表明它是一個切面類
@Component//交給Spring管理
public class HttpAspect {
// //定義攔截GirlController中所有public方法
// @Before("execution(public * com.imooc.controller.GirlController.*(..))")
// public void log() {
// System.out.println("******攔截前的邏輯******");
// }
//
// @After("execution(public * com.imooc.controller.GirlController.*(..))")
// public void doAfter() {
// System.out.println("******攔截後的邏輯******");
// }
/*
* 上面攔截的方式比較繁瑣,因為Before和After的匹配規則有重複程式碼
*
* 可以先定義一個Pointcut,然後直接攔截這個方法即可
*
*/
//這裡就定義了一個總的匹配規則,以後攔截的時候直接攔截log()方法即可,無須去重複寫execution表示式
@Pointcut("execution(public * com.imooc.controller.GirlController.*(..))")
public void log() {
}
@Before("log()")
public void doBefore() {
System.out.println("******攔截前的邏輯******");
}
@After("log()")
public void doAfter() {
System.out.println("******攔截後的邏輯******");
}
}
[email protected]
主要用來標註Java類,表明它是一個切面配置的類,通常下面也會加上@Component註解來表明它由Spring管理
[email protected]
主要有pointCutExpression(切面表示式)來表達,用來描述你要在哪些類的哪些方法上注入程式碼。其中切面表示式包含了designators(指示器,主要描述通過哪些方式去匹配Java類的哪些方法:如execution()等)和wildcards(萬用字元:如*)以及operators(運算子:如&&、||、!),具體如下所示
designators指示器
表示想要通過什麼樣的方式匹配想要的方法,具體組成如下圖,重點是execution()
1.匹配包/型別within()
//匹配ProductService類種的所有方法
@Poincut("within(com.hhu.service.ProductService)")
public void matchType(){
...
}
//匹配com.hhu包及其子包下所有類的方法
@Pointcut("within(com.hhu..*)")
public void matchPackage(){
...
}
2.匹配物件(主要有this和target以及bean)
//匹配AOP物件的目標物件為指定型別的方法,即DemoDao的aop的代理物件
@Pointcut("this(com.hhu.DemaoDao)")
public void thisDemo() {
...
}
//匹配實現IDao介面的目標物件(而不是aop代理後的物件,這裡即DemoDao的方法)
@Pointcut("target(com.hhu.Idao)")
public void targetDemo() {
...
}
//匹配所有以Service結尾的bean中的方法
@Pointcut("bean(*Service)")
public void beanDemo() {
...
}
3.引數匹配(主要有execution()和args()方法)
//過濾出第一個引數是long型別的並且在com.hhu.service包下的方法,如果是第一個引數是Long,第二個引數是String則可以寫成args(Long,String),如果匹配第一個為Long,其它任意的話則可以寫成args(Long..)
@Pointcut("args(Long) && within(com.hhu.service.*)")
public void matchArgs() {
...
}
@Before("mathArgs()")
public void befor() {
System.out.println("");
System.out.println("###before");
}
4.匹配註解
//匹配方法標註有AdminOnly註解的方法
@Pointcut("@annotation(com.hhu.demo.security.AdminOnly)")
public void annoDemo() {
...
}
//匹配標註有Beta的類下的方法,要求annotation的RetentionPplicy級別為CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo() {
...
}
//匹配標註有Repository類下的方法,要求的annotation的RetentionPolicy級別為RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo() {
...
}
//匹配傳入的引數型別標註有Repository註解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo() {
...
}
5.execution()
標準的execution表示式如下,共有5個引數:
execution(
//許可權修飾符(如public、private)
[modifier-pattern],
//返回值(如*表示任意返回值,void表示無返回值)
ret-type-pattern,
//包名(如com.hhu.service.*Service.*(..)表示對應包com.hhu.service中以Service結尾的類中的任意方法(該方法可以帶任意引數),如果匹配無參的則可以寫成(),如果像攔截產生異常的方法,則可以寫成(..) throws+具體異常 )
[declaring-type-pattern],
name-pattern(或者是param-pattern),
[throws-pattern]
)
其中帶有方括號的引數表示可以預設。比如
@Pointcut("execution(* *..find*(Long)) && within(com.imooc..*) ")
wildcard主要包括常用的三種:
*表示匹配任意數量的字元
+表示匹配製定類及其子類
..表示一般用於匹配任意數的子包或引數。
operators主要包括如下的三種:
&&表示與操作
||表示或操作
!表示非操作
3.Advice註解
表示程式碼織入的時機,如執行之前、執行之後等等,主要有5種,通常是進行註解切面註解後會緊接著寫Advice,格式為Advice(“切面攔截方法”)。比如下面的
//切面註解
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
public void matchAnno(){}
//Advice註解
@After("matchAnno()")
public void after() {
System.out.println("###after");
}
- @Before,前置通知
- @After,後置通知,不管程式碼是成功還是丟擲異常,都會織入
- @AfterReturning,返回通知,當且僅當方法成功執行
- @AfterThrowing,異常通知,當且僅當方法丟擲異常
- @Around,環繞通知,基本包含了上述所有的植入位置
獲取引數的話一般跟Before的Advice結合在一起的,程式碼如下
@Before("matchLongArg() && args(productId)")
//這裡是因為知道引數的型別是Long
public void before(Long productId) {
System.out.println("###before,get args:" + productId);
}
其中@AfterReturning註解可以獲取方法的返回值,比如下面的
//切面註解
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
public void matchAnno(){}
//Advice註解,returning的值result代表的就是返回值,形參Object類表示
@AfterReturning(value="matchAnno()",returning="result")
public void after(java.lang.Object result) {
System.out.println("###after");
}
@Around註解比較強大,一般有它的存在就可以不用Before或者After這類註解了,如下
//由於這裡可能有返回值所以必須有Object,而且必須獲取Proceeding的上下文才能讓方法得以繼續,所以會有此形參
@Around("matchAnno()")
public java.lang.Object after(ProceedingJoinPoint joinPoint) {
System.out.println("###before");
//定義返回值
java.lang.Object result = null;
try {
//獲取返回值
result = joinPoint.proceed(joinPoint.getArgs());
System.out.println("###after returning");
} catch(Throwable e) {
System.out.println("###ex");
e.printStackTrace();
} finally {
System.out.println("###finally");
}
return result;
}
三、Spring AOP的原理
AOP的設計主要用到的設計模式包含了代理模式、責任鏈模式;AOP的實現方式包含了兩種:基於JDK實現、基於CGlib實現。
織入時機:主要有以下的3種
1. 編譯期(如AspectJ)
2. 類載入時(如AspectJ5以上版本)
3. 執行時(如Spring AOP)
Spring AOP屬於執行時織入,這種織入方式是基於代理實現的,這樣代理才能在實際方法執行前後進行一些邏輯處理,可以有兩種:從靜態代理到動態代理(動態代理包含基於介面代理與基於繼承代理)。
1.代理模式(簡單AOP的模擬)–靜態代理
//1.首先定義一個介面
package com.imooc.pattern;
public interface Subject {
void request();
}
//2.讓實際物件和代理物件都實現這個介面
//2.1實際物件
package com.imooc.pattern;
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("real subject execute request");
}
}
//2.2織入邏輯物件,持有實際物件
package com.imooc.pattern;
public class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("before");
try {
realSubject.request();
} catch (Exception e) {
System.out.println("ex:" + e.getMessage());
throw e;
} finally {
System.out.println("after");
}
}
}
//3.客戶端呼叫
package com.imooc.pattern;
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
realSubject.request();
proxy.request();
}
}
靜態代理方便易懂,但是當目標類中需要邏輯的處理方法很多,由於邏輯處理的前後大部分都是相差不大的,那麼此時就需要寫很多重複的程式碼,從而向動態代理過渡,動態代理分為基於介面的動態代理(典型代表是JDK代理)和基於繼承的動態代理(典型代表是CGlib代理)。
2.JDK動態代理(只能基於介面)
jdk動態著重理解動態,可以翻一下其原始碼應該就知道了。主要有兩個步驟:
通過java.lang.reflect.Proxy類來動態生成代理類,其次這個代理類需要實現的織入邏輯必須實現InvocationHandler介面(需要重寫其中的invoke()方法),程式碼如下
//1.Subject介面同上
//2.jdk動態代理
package com.imooc.dynamic;
import com.imooc.pattern.RealSubject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 就是需要織入的邏輯程式碼,相當與AOP中aspect
*/
//必須實現InvocationHandler介面
public class JdkProxySubject implements InvocationHandler{
//jdk動態代理仍然需要引用目標類物件
private RealSubject realSubject;
public JdkProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
//重寫invoke()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//執行動態代理邏輯
System.out.println("before");
//初始化返回值
Object result = null;
try {
//通過反射執行目標類的所有方法
result = method.invoke(realSubject,args);
} catch (Exception e) {
System.out.println("ex:" + e.getMessage());
//異常一定要丟擲,因為上面的catch只是指標的是代理類,這裡的throw是指目標類的異常丟擲
throw e;
}finally {
System.out.println("after");
}
return result;
}
}
//3.客戶端呼叫
package com.imooc.pattern;
import com.imooc.dynamic.JdkProxySubject;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//通過java.lang.reflect.Proxy類來動態生成代理類,注意引數
Subject subject = (Subject)Proxy.newProxyInstance(Client.class.getClassLoader(),
new Class[]{Subject.class}, new JdkProxySubject(new RealSubject()));
subject.request();
//這裡在接口裡增加一個hello(),如果用靜態代理的話就必須在代理類中重寫該方法,而jdk代理則不需要,直接呼叫即可
subject.hello();
}
}
3.Cglib動態代理(通過繼承實現代理類)
主要兩點:
1.Cglib通過繼承的方式實現代理類
2.Cglib通過Callback方式來織入程式碼
步驟兩不:通過org.springframework.cglib.proxy.Enhancer來建立代理物件,織入的邏輯程式碼需要實現org.springframework.cglib.proxy.MethodInterceptor介面(重寫intercept()方法),程式碼如下
//1.Subject介面同上
//2.織入的邏輯程式碼
package com.imooc.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DemoMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before in cglib");
//定義返回的結果
Object result = null;
try {
//獲取結果
result = methodProxy.invokeSuper(o,objects);
}catch (Exception e) {
System.out.println("ex:" + e.getMessage());
throw e;
}finally {
System.out.println("after in cglib");
}
return result;
}
}
//3.客戶端呼叫
package com.imooc.pattern;
import com.imooc.cglib.DemoMethodInterceptor;
import org.springframework.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//Superclass就是目標類
enhancer.setSuperclass(RealSubject.class);
//Callback就是要織入的程式碼
enhancer.setCallback(new DemoMethodInterceptor());
//通過Enhancer建立代理類
Subject subject = (Subject)enhancer.create();
subject.request();
subject.hello();
}
}
cglib和jdk的主要區別在於:
1.JDK動態代理只能針對有介面的類的介面方法進行動態代理
2.Cglib基於繼承來是實現代理,無法對static、final類以及private、static方法進行代理
4.Spring AOP代理方式的選擇
主要依據以下的原則:
1.如果目標物件實現了介面,則預設使用JDK動態代理
2.如果目標物件沒有實現介面,則採用Cglib動態代理
3.如果目標物件實現了介面,且強制cglib代理,則使用cglib動態代理
關於第3強制使用cglib代理設定如下:
@SpringBootApplication
//強制使用cglib代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ExecutionDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ExecutionDemoApplication.class, args);
}
}
5.多個AOP的疊加–責任鏈模式
責任鏈的原理圖如下:
程式碼演示如下:
//1.定義Handler
package com.imooc.chain;
public abstract class Handler {
protected Handler successor;
public Handler getSuccessor() {
return successor;
}
public void setSuccessor(Handler successor) {
this.successor = successor;
}
//對外暴露
public void execute() {
//先執行自己的handlerProcess
handlerProcess();
//遞迴執行handler
if(successor!=null) {
successor.execute();
}
}
protected abstract void handlerProcess();
}
//2.客戶端多AOP的作用
package com.imooc.chain;
public class Client {
static class HandlerA extends Handler {
@Override
protected void handlerProcess() {
System.out.println("handler by a");
}
}
static class HandlerB extends Handler {
@Override
protected void handlerProcess() {
System.out.println("handler by b");
}
}
static class HandlerC extends Handler {
@Override
protected void handlerProcess() {
System.out.println("handler by c");
}
}
public static void main(String[] args) {
Handler handlerA = new HandlerA();
Handler handlerB = new HandlerB();
Handler handlerC = new HandlerC();
//設定連線關係
handlerA.setSuccessor(handlerB);
handlerB.setSuccessor(handlerC);
handlerA.execute();
}
}
上述程式碼之間的多個AOP需要進行設定之間的連線,下面將這個步驟封裝,無需設定他們之間的連線,更具靈活性,AOP內部也是這種原理實現。
//1.設定Handler
package com.imooc.chain;
public abstract class ChainHandler {
public void execute(Chain chain) {
handlerProcess();
chain.proceed();
}
protected abstract void handlerProcess();
}
//2.封裝多個AOP
package com.imooc.chain;
import java.util.List;
public class Chain {
private List<ChainHandler> handlers;
private int index = 0;
public Chain(List<ChainHandler> handlers) {
this.handlers = handlers;
}
public void proceed() {
if(index>=handlers.size()) {
return ;
}
handlers.get(index++).execute(this);
}
}
//3.客戶端呼叫
package com.imooc.chain;
import java.util.Arrays;
import java.util.List;
public class Client2 {
static class ChainHandlerA extends ChainHandler {
@Override
protected void handlerProcess() {
System.out.println("handle by chain a");
}
}
static class ChainHandlerB extends ChainHandler {
@Override
protected void handlerProcess() {
System.out.println("handle by chain b");
}
}
static class ChainHandlerC extends ChainHandler {
@Override
protected void handlerProcess() {
System.out.println("handle by chain c");
}
}
public static void main(String[] args) {
//利用封裝的方式將chain的方式宣告出來,並且每個chain之間是獨立存在的
//Spring AOP內部也是使用的這種方式來實現的,原理類似,從-1開始,這裡從0開始
List<ChainHandler> handlers = Arrays.asList(
new ChainHandlerA(),
new ChainHandlerB(),
new ChainHandlerC()
);
Chain chain = new Chain(handlers);
chain.proceed();
}
}
四、AOP應用控制的三大註解
AOP在整個Spring框架中佔非常重要的作用,這裡主要結合三個註解理解它的控制應用:@Transactional、@PreAuthorize、@Cacheable.
1. Spring利用@Transactional註解進行事務控制
關於事務控制已經是老生常談了,這裡簡單的利用Springboot和jpa兩者結合給出比較簡單的程式碼來演示(如對springboot不瞭解的話可以參看springboot的學習記錄)
//插入使用者同時記錄一個日誌
@Transactional
public void addUser(String name){
OperationLog log = new OperationLog();
log.setContent("create user:"+name);
operationLogDao.save(log);
User user = new User();
user.setName(name);
userDao.save(user);
}
即插入使用者時同時插入記錄,只要有一個操作不成功,那麼整個事物進行回滾操作。
SpringSecurity安全校驗同樣使用了AOP,首先使用MethodSecurityInterceptor進行攔截,然後這個Interceptor又是呼叫的Voter,這個Voter是處理InvocationAuthorizationAdvice這個型別的,再然後這個Voter又是呼叫的ExpressionBasePreInvocationAdvice進行判斷,工作圖如下
這裡以一個簡單的使用者登陸為例,程式碼如下
//1.配置檔案
package com.imooc.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Created by cat on 2017-03-12.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/index").permitAll()//這是不需要校驗的,其他的都需要校驗
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
//預設在記憶體中建立兩個使用者,一個是USER角色的使用者demo,另一個是ADMIN角色的使用者admin
.withUser("demo").password("demo").roles("USER")
.and()
//
.withUser("admin").password("admin").roles("ADMIN");
}
}
//2.控制器
package com.imooc.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by cat on 2017-03-12.
*/
@RestController
public class DemoController {
//這個前面已經設定過了,是不需要攔截的
@RequestMapping("/index")
public String index() {
return "index";
}
@RequestMapping("/common")
public String commonAccess(){
return "only login can view";
}
@RequestMapping("/admin")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String admin(){
return "only admin can access";
}
}
這裡index頁面是每個人都可以訪問的,common頁面允許demo使用者訪問,admin頁面只允許admin使用者訪問。
3. SpringCache如何利用@Cacheable進行快取控制
首先前面有一個AnnotationCacheAspect的定義,然後通過CacheInterceptor來執行邏輯,最後這個Interceptor又是託給CacheAspectSupport來進行快取控制。時序圖如下
程式碼如下
//1.快取配置檔案
package com.imooc.service;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* Created by cat on 2017-03-12.
*/
@Component
public class MenuService {
//@Cacheable是用來宣告方法返回值是可快取的。將結果儲存到快取中以便後續使用相同引數呼叫時不需執行實際的方法。直接從快取中取值。最簡單的格式只需要需要制定快取名稱即可使用。
@Cacheable(cacheNames = {"menu"})
public List<String> getMenuList(){
System.out.println("");
System.out.println("mock:get from db");
return Arrays.asList("article","comment","admin");
}
}
//2.主檔案
package com.imooc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching//這個註解不能漏
public class CacheDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CacheDemoApplication.class, args);
}
}
//3.測試檔案
package com.hhu;
import com.hhu.service.MenuService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheDemoApplicationTests {
@Autowired
MenuService menuService;
@Test
public void testCache() {
//首次呼叫getMenuList,快取中沒有東西,所以完整執行了 System.out.println("call:"+menuService.getMenuList());
//第二次呼叫時,由於註解了快取,所以並沒有直接呼叫方法,而是直接去快取中獲取了第一次呼叫的返回值List,並沒有執行之前的列印文字 System.out.println("call:"+menuService.getMenuList());
}
}
//4.pom檔案
<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.hhu.config</groupId>
<artifactId>SpringAOPTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringAOPTest</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</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>
執行結果為
mock:get from db
call:[article, comment, admin]
call:[article, comment, admin]
第一次出現了mock標誌,第二次沒有出現mock標誌。
五、綜合案例
以商家產品管理系統為例,需要實現以下功能(非功能性需求)
1.記錄產品修改的操作記錄
2.記錄什麼人在什麼時間修改了哪些產品的哪些欄位以及修改的具體值
思路:
1.利用qspect攔截增刪改的方法
2.利用反射獲取物件的新舊值
3.利用@Around註解的Advice去記錄操作記錄
環境:Eclipse + jdk1.8 資料庫:MySQL+MongoDB
主要程式碼片如下:
1.maven的依賴
<?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.hhu</groupId>
<artifactId>datalog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>datalog</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.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-data-jpa</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mongodb主要用來儲存使用者的操作記錄 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- 主要用於獲取物件bean描述時候用到的 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</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>