springboot下自定義注解實(shí)現(xiàn)aop面向切面編程--增強(qiáng)日志示例

AOP面向切面編程是縱向編程,在spring框架中很多注解都是基于aop做的功能增強(qiáng)奔害,原理是java的動(dòng)態(tài)代理模式尔邓。

先理解一下基本概念

切入點(diǎn)(PointCut)

在需要做增強(qiáng)功能的方法上添加自定義注解實(shí)現(xiàn)功能增強(qiáng)掂名,這個(gè)方法就是切入點(diǎn),@Pointcut对湃。

切面(Aspect)

有了切入點(diǎn),把需要增強(qiáng)的功能寫入到某個(gè)實(shí)現(xiàn)類里遗淳,這個(gè)類就叫做切面拍柒,里面可以聲明前置增強(qiáng)、后置增強(qiáng)屈暗、環(huán)繞增強(qiáng)拆讯、發(fā)生異常處理等操作

連接點(diǎn)(JoinPoint)

在切點(diǎn)(即指需要增強(qiáng)的方法)上的哪一個(gè)執(zhí)行階段加入增加代碼,這個(gè)執(zhí)行階段就叫連接點(diǎn)恐锦,比如:方法調(diào)用前@Before往果,方法調(diào)用后@After,發(fā)生異常時(shí)@AfterThrowing等等一铅。

通知(Advice)

連接點(diǎn)里面聲明的代碼處理就是通知陕贮,如@Before注解方法里面的具體代碼操作。

目標(biāo)對(duì)象(Target Object)

被一個(gè)或多個(gè)切面所通知的對(duì)象潘飘,即為目標(biāo)對(duì)象肮之。

AOP代理對(duì)象(AOP Proxy Object)

AOP代理是AOP框架所生成的對(duì)象,該對(duì)象是目標(biāo)對(duì)象的代理對(duì)象卜录。代理對(duì)象能夠在目標(biāo)對(duì)象的基礎(chǔ)上戈擒,在相應(yīng)的連接點(diǎn)上調(diào)用通知。

織入(Weaving)

將切面切入到目標(biāo)方法之中艰毒,使目標(biāo)方法得到增強(qiáng)的過(guò)程被稱之為織入筐高。

示例:
maven在普通springboot的基礎(chǔ)上引入切面jar包

<!-- 切面所用到的jar包 -->
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
            <scope>runtime</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/aspectj/aspectjrt -->
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.5.4</version>
        </dependency>

在springboot中新建一個(gè)TestController,在controller里聲明一個(gè)自定義注解

package com.zhaohy.app.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.zhaohy.app.service.TestService;
import com.zhaohy.app.sys.annotation.RecordLog;


@Controller
public class TestController {
    
    @Autowired
    TestService testService;
    
    @RecordLog(module="moduleTest")
    @RequestMapping("/test/test.do")
    public void test(HttpServletRequest request) {
        String userId = request.getParameter("userId");
            try {
                testService.test(userId);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        
    }
}

TestService:

package com.zhaohy.app.service;

public interface TestService {
    public String doSearch(String userId, String keyword);
    
    public void test(String userId) throws Exception;
}

TestServiceImpl:

package com.zhaohy.app.service.impl;

import org.springframework.stereotype.Service;

import com.zhaohy.app.service.TestService;
import com.zhaohy.app.sys.annotation.RecordLog;
@Service("TestService")
public class TestServiceImpl implements TestService {
    public void test(String userId) throws Exception {
        System.out.println(userId + "====執(zhí)行操作");
        throw new Exception();
    }
    
}

新建注解類:

package com.zhaohy.app.sys.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 日志記錄注解類
* @author zhaohy
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordLog {
   /**
    * 何種場(chǎng)景下的通用日志打印
    *
    * @return
    */
   String module();
}

切面類:

package com.zhaohy.app.sys.annotation;

import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
/**
 * 定義日志切面
 * @author zhaohy
 *@Lazy 注解:容器一般都會(huì)在啟動(dòng)的時(shí)候?qū)嵗袉螌?shí)例 bean,如果我們想要 Spring 在啟動(dòng)的時(shí)候延遲加載 bean丑瞧,需要用到這個(gè)注解
 *value為true柑土、false 默認(rèn)為true,即延遲加載,@Lazy(false)表示對(duì)象會(huì)在初始化的時(shí)候創(chuàng)建
 */
@Aspect
@Component
@Lazy(false)
public class LoggerAspect {
    /**
     * 定義切入點(diǎn):對(duì)要攔截的方法進(jìn)行定義與限制绊汹,如包稽屏、類
     *
     * 1、execution(public * *(..)) 任意的公共方法
     * 2西乖、execution(* set*(..)) 以set開頭的所有的方法
     * 3狐榔、execution(* com.zhaohy.app.sys.annotation.LoggerApply.*(..))com.zhaohy.app.sys.annotation.LoggerApply這個(gè)類里的所有的方法
     * 4、execution(* com.zhaohy.app.sys.annotation.*.*(..))com.zhaohy.app.sys.annotation包下的所有的類的所有的方法
     * 5获雕、execution(* com.zhaohy.app.sys.annotation..*.*(..))com.zhaohy.app.sys.annotation包及子包下所有的類的所有的方法
     * 6薄腻、execution(* com.zhaohy.app.sys.annotation..*.*(String,?,Long)) com.zhaohy.app.sys.annotation包及子包下所有的類的有三個(gè)參數(shù),第一個(gè)參數(shù)為String類型典鸡,第二個(gè)參數(shù)為任意類型被廓,第三個(gè)參數(shù)為L(zhǎng)ong類型的方法
     * 7、execution(@annotation(com.zhaohy.app.sys.annotation.RecordLog))
     */
    @Pointcut("@annotation(com.zhaohy.app.sys.annotation.RecordLog)")
    private void cutMethod() {
 
    }
 
    /**
     * 前置通知:在目標(biāo)方法執(zhí)行前調(diào)用
     */
    @Before("cutMethod()")
    public void begin() {
        System.out.println("==@Before==  begin");
    }
 
    /**
     * 后置通知:在目標(biāo)方法執(zhí)行后調(diào)用萝玷,若目標(biāo)方法出現(xiàn)異常嫁乘,則不執(zhí)行
     */
    @AfterReturning("cutMethod()")
    public void afterReturning() {
        System.out.println("==@AfterReturning==  after returning");
    }
 
    /**
     * 后置/最終通知:無(wú)論目標(biāo)方法在執(zhí)行過(guò)程中出現(xiàn)一場(chǎng)都會(huì)在它之后調(diào)用
     */
    @After("cutMethod()")
    public void after() {
        System.out.println("==@After==  finally returning");
    }
 
    /**
     * 異常通知:目標(biāo)方法拋出異常時(shí)執(zhí)行
     */
    @AfterThrowing("cutMethod()")
    public void afterThrowing() {
        System.out.println("==@AfterThrowing==  after throwing");
    }
 
    /**
     * 環(huán)繞通知:靈活自由的在目標(biāo)方法中切入代碼
     */
    @Around("cutMethod()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 獲取目標(biāo)方法的名稱
        String methodName = joinPoint.getSignature().getName();
        // 獲取方法傳入?yún)?shù)
        Object[] params = joinPoint.getArgs();
        RecordLog recordLog = getDeclaredAnnotation(joinPoint);
        HttpServletRequest request = (HttpServletRequest) params[0];
        System.out.println("==@Around==  --》 method name :'" + methodName + "' args :" + this.getRequestParams(request));
        // 執(zhí)行源方法
        joinPoint.proceed();
        // 模擬進(jìn)行驗(yàn)證
        if (params != null && params.length > 0 && params[0].equals("Blog Home")) {
            System.out.println("==@Around==  --》 " + recordLog.module() + " auth success");
        } else {
            System.out.println("==@Around==  --》 " + recordLog.module() + " auth failed");
        }
    }
 
    private String getRequestParams(HttpServletRequest request) {
        Enumeration enu=request.getParameterNames(); 
        Map<String, Object> paramsMap = new HashMap<String, Object>();
        while(enu.hasMoreElements()){  
            String paraName=(String)enu.nextElement();  
            paramsMap.put(paraName, request.getParameter(paraName));
        }
        return JSON.toJSONString(paramsMap);
    }

    /**
     * 獲取方法中聲明的注解
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    public RecordLog getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        // 獲取方法名
        String methodName = joinPoint.getSignature().getName();
        // 反射獲取目標(biāo)類
        Class<?> targetClass = joinPoint.getTarget().getClass();
        // 拿到方法對(duì)應(yīng)的參數(shù)類型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // 根據(jù)類、方法球碉、參數(shù)類型(重載)獲取到方法的具體信息
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);
        // 拿到方法定義的注解信息
        RecordLog annotation = objMethod.getDeclaredAnnotation(RecordLog.class);
        // 返回
        return annotation;
    }
}

這樣代碼基本寫的差不多了蜓斧,瀏覽器訪問(wèn)`

http://127.0.0.1:8081/test/test.do?userId=楊過(guò)

控制臺(tái)打印結(jié)果:

==@Around==  --》 method name :'test' args :{"userId":"楊過(guò)"}
==@Before==  begin
楊過(guò)====執(zhí)行操作
java.lang.Exception
==@Around==  --》 moduleTest auth failed
==@After==  finally returning
==@AfterReturning==  after returning

切面生效啦,如果在controller里不捕捉那個(gè)exception睁冬,則會(huì)觸發(fā)

/**
     * 異常通知:目標(biāo)方法拋出異常時(shí)執(zhí)行
     */
    @AfterThrowing("cutMethod()")
    public void afterThrowing() {
        System.out.println("==@AfterThrowing==  after throwing");
    }

總結(jié):

aop的思想類似過(guò)濾器挎春、攔截器等,都是縱向編程豆拨,在方法執(zhí)行前或者后或者前后或者拋出異常后等的后續(xù)處理操作直奋,也用到j(luò)ava的反射機(jī)制根據(jù)類路徑拿到類,拿到注解類的屬性施禾,拿到參數(shù)脚线,解析參數(shù),就可以做很多事情弥搞。

參考:https://www.cnblogs.com/lingyejun/p/9941350.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邮绿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子攀例,更是在濱河造成了極大的恐慌船逮,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粤铭,死亡現(xiàn)場(chǎng)離奇詭異挖胃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)梆惯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門酱鸭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人加袋,你說(shuō)我怎么就攤上這事凛辣。” “怎么了职烧?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵扁誓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蚀之,道長(zhǎng)蝗敢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任足删,我火速辦了婚禮寿谴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘失受。我一直安慰自己讶泰,他們只是感情好咏瑟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痪署,像睡著了一般码泞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狼犯,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天余寥,我揣著相機(jī)與錄音,去河邊找鬼悯森。 笑死宋舷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓢姻。 我是一名探鬼主播祝蝠,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼汹来!你這毒婦竟也來(lái)了续膳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤收班,失蹤者是張志新(化名)和其女友劉穎坟岔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摔桦,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡社付,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邻耕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸥咖。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖兄世,靈堂內(nèi)的尸體忽然破棺而出啼辣,到底是詐尸還是另有隱情,我是刑警寧澤御滩,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布鸥拧,位于F島的核電站,受9級(jí)特大地震影響削解,放射性物質(zhì)發(fā)生泄漏富弦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一氛驮、第九天 我趴在偏房一處隱蔽的房頂上張望腕柜。 院中可真熱鬧,春花似錦、人聲如沸盏缤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛾找。三九已至娩脾,卻和暖如春赵誓,著一層夾襖步出監(jiān)牢的瞬間打毛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工俩功, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幻枉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓诡蜓,卻偏偏與公主長(zhǎng)得像熬甫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔓罚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容