Spring Boot 中引入AOP(面向切面編程)尤為簡(jiǎn)單荷腊,添加AOP依賴即可女仰。AOP的作用在于分離系統(tǒng)中的各種關(guān)注點(diǎn)疾忍,將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)分離開來(lái)一罩。更好的解耦和聂渊,提高代碼的可重用性汉嗽。結(jié)合注解就更加的靈活方便了饼暑。
實(shí)現(xiàn)步驟如下:
一、引入AOP的 pom.xml 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二迈着、編寫自定義注解 @Log
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 描述
* @return
*/
String value() default "";
/**
* 是否保存請(qǐng)求記錄
*/
boolean isRecord() default false;
/**
* 是否打印返回值
*/
boolean isWrite() default true;
}
三裕菠、定義一個(gè)切面LogAspect
//使用@Aspect注解將一個(gè)java類定義為切面類
@Aspect
@Component
public class LogAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
//保存記錄Mapper
@Autowired
OperateRecordMapper operateRecordMapper;
//使用@Pointcut定義一個(gè)切入點(diǎn),可以是一個(gè)規(guī)則表達(dá)式画髓,比如下例中某個(gè)package下的所有函數(shù),也可以是一個(gè)注解等奈虾。
@Pointcut("@annotation(com.xiduoduo.core.annotation.Log)")
public void log(){}
//使用@Before在切入點(diǎn)開始處切入內(nèi)容
@Before("log()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
//防止參數(shù)轉(zhuǎn)換報(bào)錯(cuò)
try{
//方法名稱
String methodName = joinPoint.getSignature().getName();
//方法參數(shù)
String argsJsonStr = JSON.toJSONString(joinPoint.getArgs());
logger.info("【"+methodName+"】開始執(zhí)行,參數(shù):"+argsJsonStr);
}catch (Exception e){
logger.error("日志參數(shù)轉(zhuǎn)換異常肉微,不影響業(yè)務(wù)邏輯碉纳!錯(cuò)誤信息:"+e.getMessage());
}
}
//使用@AfterReturning在切入點(diǎn)return內(nèi)容之后切入內(nèi)容(可以用來(lái)對(duì)處理返回值做一些加工處理)
@AfterReturning(returning = "ret", pointcut = "@annotation(log)")
public void doAfterReturning(JoinPoint joinPoint, Object ret, Log log) throws Throwable {
//防止參數(shù)轉(zhuǎn)換報(bào)錯(cuò)
try{
//方法名稱
String methodName = joinPoint.getSignature().getName();
//方法參數(shù)
String argsJsonStr = JSON.toJSONString(joinPoint.getArgs());
String retStr = JSON.toJSONString(ret);
//判斷是否輸出返回值劳曹,列表數(shù)據(jù)盡量不打印
if(log.isWrite()){
logger.info("【"+methodName+"】執(zhí)行完畢,返回值:"+retStr);
}else {
logger.info("【"+methodName+"】執(zhí)行完畢");
}
//是否保存請(qǐng)求記錄
if(log.isRecord()){
OperateRecord operateRecord = new OperateRecord();
operateRecord .setAddTime(DateUtil.getCurrentDate());
operateRecord .setContent("[方法:"+methodName+"],[參數(shù):"+argsJsonStr+"],[返回值:"+retStr+"]");
operateRecordMapper.insert(operateRecord );
}
}catch (Exception e){
logger.error("日志參數(shù)轉(zhuǎn)換異常,不影響業(yè)務(wù)邏輯房资!錯(cuò)誤信息:"+e.getMessage());
}
}
}
四志膀、使用@Log注解輸出日志及保存請(qǐng)求記錄
/**
* 添加用戶 保存請(qǐng)求記錄熙宇,正常打印返回值
* @param params
* @return
*/
@Log(isRecord = true)
public String addUser(String params) {
return userService.addUser(parmas);
}
/**
* 查詢所有用戶 不保存請(qǐng)求記錄,不打印返回值(列表數(shù)據(jù)過(guò)多)
* @param params
* @return
*/
@Log(isWrite = false)
public String getUserList(String params) {
return userService.getUserList(parmas);
}
/**
* 根據(jù)用戶編號(hào)獲取用戶信息 不保存請(qǐng)求記錄溉浙,正常打印返回值
* @param params
* @return
*/
@Log
public String getUserByUserNo(String params) {
return userService.getUserByUserNo(parmas);
}
其他
一烫止、AOP的相關(guān)概念
連接點(diǎn)(Joinpoint): 表示需要在程序中插入橫切關(guān)注點(diǎn)的擴(kuò)展點(diǎn),連接點(diǎn)可能是類初始化戳稽、方法執(zhí)行馆蠕、方法調(diào)用期升、字段調(diào)用或處理異常等等,Spring只支持方法執(zhí)行連接點(diǎn)互躬;在AOP中表示為“在哪里干”播赁;
切入點(diǎn)(Pointcut): 選擇一組相關(guān)連接點(diǎn)的模式,即可以認(rèn)為連接點(diǎn)的集合吼渡,Spring支持perl5正則表達(dá)式和AspectJ切入點(diǎn)模式坎背,Spring默認(rèn)使用AspectJ語(yǔ)法盒犹;在AOP中表示為“在哪里干的集合”;
通知(Advice): 在連接點(diǎn)上執(zhí)行的行為墅茉,通知提供了在AOP中需要在切入點(diǎn)所選擇的連接點(diǎn)處進(jìn)行擴(kuò)展現(xiàn)有行為的手段悍募;包括前置通知(before advice)喜鼓、后置通知(after advice)隅忿、環(huán)繞通知(around advice),在Spring中通過(guò)代理模式實(shí)現(xiàn)AOP畦娄,并通過(guò)攔截器模式以環(huán)繞連接點(diǎn)的攔截器鏈織入通知;在AOP中表示為“干什么”喂柒;
切面(Aspect):橫切關(guān)注點(diǎn)的模塊化艳吠,比如日志組件黍匾】恼铮可以認(rèn)為是通知莱褒、引入和切入點(diǎn)的組合炮障;在Spring中可以使用Schema和@AspectJ方式進(jìn)行組織實(shí)現(xiàn)智末;在AOP中表示為“在哪干和干什么集合”由蘑;
引入(Introduction): 也稱為內(nèi)部類型聲明,為已有的類添加額外新的字段或方法鹿响,Spring允許引入新的接口(必須對(duì)應(yīng)一個(gè)實(shí)現(xiàn))到所有被代理對(duì)象(目標(biāo)對(duì)象)指孤;在AOP中表示為“干什么(引入什么)”黎做;
目標(biāo)對(duì)象(Target Object):需要被織入橫切關(guān)注點(diǎn)的對(duì)象,即該對(duì)象是切入點(diǎn)選擇的對(duì)象摊溶,需要被通知的對(duì)象拉岁,從而也可稱為“被通知對(duì)象”;由于Spring AOP 通過(guò)代理模式實(shí)現(xiàn)哄啄,從而這個(gè)對(duì)象永遠(yuǎn)是被代理對(duì)象咨跌;在AOP中表示為“對(duì)誰(shuí)干”刊殉;
AOP代理(AOP Proxy): AOP框架使用代理模式創(chuàng)建的對(duì)象遍膜,從而實(shí)現(xiàn)在連接點(diǎn)處插入通知(即應(yīng)用切面)冀偶,就是通過(guò)代理來(lái)對(duì)目標(biāo)對(duì)象應(yīng)用切面。在Spring中侥猩,AOP代理可以用JDK動(dòng)態(tài)代理或CGLIB代理實(shí)現(xiàn)榔至,而通過(guò)攔截器模型應(yīng)用切面。
織入(Weaving): 織入是一個(gè)過(guò)程欺劳,是將切面應(yīng)用到目標(biāo)對(duì)象從而創(chuàng)建出AOP代理對(duì)象的過(guò)程唧取,織入可以在編譯期、類裝載期划提、運(yùn)行期進(jìn)行枫弟。組裝方面來(lái)創(chuàng)建一個(gè)被通知對(duì)象。這可以在編譯時(shí)完成(例如使用AspectJ編譯器)鹏往,也可以在運(yùn)行時(shí)完成淡诗。Spring和其他純Java AOP框架一樣,在運(yùn)行時(shí)完成織入伊履。
二韩容、注解相關(guān)概念
@Target
說(shuō)明了Annotation所修飾的對(duì)象范圍
取值(ElementType)有:
1.CONSTRUCTOR:用于描述構(gòu)造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部變量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述參數(shù)
7.TYPE:用于描述類、接口(包括注解類型) 或enum聲明
@Retention
定義了該Annotation被保留的時(shí)間長(zhǎng)短:某些Annotation僅出現(xiàn)在源代碼中唐瀑,而被編譯器丟棄群凶;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略哄辣,而另一些在class被裝載時(shí)將被讀惹肷摇(請(qǐng)注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)柔滔。使用這個(gè)meta-Annotation可以對(duì) Annotation的“生命周期”限制溢陪。
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
@Documented
用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化睛廊。Documented是一個(gè)標(biāo)記注解,沒(méi)有成員杉编。
等你年事稍長(zhǎng)超全,就會(huì)發(fā)現(xiàn)咆霜,要使世界成為一個(gè)尚可容忍的生活場(chǎng)所,首先得承認(rèn)人類的自私是不可避免的嘶朱。 -- 威廉·薩默賽特·毛姆 《人生的枷鎖》