SpringBoot基礎(chǔ)系列之AOP結(jié)合SpEL實(shí)現(xiàn)日志輸出中兩點(diǎn)注意事項(xiàng)

image

【SpringBoot 基礎(chǔ)系列】AOP結(jié)合SpEL實(shí)現(xiàn)日志輸出的注意事項(xiàng)一二

使用 AOP 來打印日志大家一把都很熟悉了捐顷,最近在使用的過程中衅金,發(fā)現(xiàn)了幾個(gè)有意思的問題,一個(gè)是 SpEL 的解析禁悠,一個(gè)是參數(shù)的 JSON 格式輸出

I. 項(xiàng)目環(huán)境

1. 項(xiàng)目依賴

本項(xiàng)目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA進(jìn)行開發(fā)

開一個(gè) web 服務(wù)用于測試

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

II. AOP & SpEL

關(guān)于 AOP 與 SpEL 的知識(shí)點(diǎn)耘沼,之前都有過專門的介紹魁衙,這里做一個(gè)聚合羡忘,一個(gè)非常簡單的日志輸出切面,在需要打印日志的方法上诀浪,添加注解@Log,這個(gè)注解中定義一個(gè)key赋荆,作為日志輸出的標(biāo)記笋妥;key 支持 SpEL 表達(dá)式

1. AOP 切面

注解定義

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String key();
}

切面邏輯

@Slf4j
@Aspect
@Component
public class AopAspect implements ApplicationContextAware {
    private ExpressionParser parser = new SpelExpressionParser();
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(logAno)")
    public Object around(ProceedingJoinPoint joinPoint, Log logAno) throws Throwable {
        long start = System.currentTimeMillis();
        String key = loadKey(logAno.key(), joinPoint);
        try {
            return joinPoint.proceed();
        } finally {
            log.info("key: {}, args: {}, cost: {}", key,
                    JSONObject.toJSONString(joinPoint.getArgs()),
                    System.currentTimeMillis() - start);
        }
    }

    private String loadKey(String key, ProceedingJoinPoint joinPoint) {
        if (key == null) {
            return key;
        }

        StandardEvaluationContext context = new StandardEvaluationContext();

        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        String[] params = parameterNameDiscoverer.getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(params[i], args[i]);
        }

        return parser.parseExpression(key).getValue(context, String.class);
    }

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

上面這個(gè)邏輯比較簡單懊昨,和大家熟知的使用姿勢沒有太大的區(qū)別

2. StandardEvaluationContext 安全問題

關(guān)于StandardEvaluationContext的注入問題窄潭,有興趣的可以查詢一下相關(guān)文章;對于安全校驗(yàn)較高的酵颁,要求只能使用SimpleEvaluationContext嫉你,使用它的話,SpEL 的能力就被限制了

如加一個(gè)測試

@Data
@Accessors(chain = true)
public class DemoDo {

    private String name;

    private Integer age;
}

服務(wù)類

@Service
public class HelloService {

    @Log(key = "#demo.getName()")
    public String say(DemoDo demo, String prefix) {
        return prefix + ":" + demo;
    }
}

為了驗(yàn)證SimpleEvaluationContext躏惋,我們修改一下上面的loadKeys方法

private String loadKey(String key, ProceedingJoinPoint joinPoint) {
    if (key == null) {
        return key;
    }

    SimpleEvaluationContext context = new SimpleEvaluationContext.Builder().build();
    String[] params = parameterNameDiscoverer.getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
    Object[] args = joinPoint.getArgs();
    for (int i = 0; i < args.length; i++) {
        context.setVariable(params[i], args[i]);
    }

    return parser.parseExpression(key).getValue(context, String.class);
}

啟動(dòng)測試

@SpringBootApplication
public class Application {

    public Application(HelloService helloService) {
        helloService.say(new DemoDo().setName("一灰灰blog").setAge(18), "welcome");
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
image

直接提示方法找不到S奈邸!簿姨!

3. gson 序列化問題

上面的 case 中距误,使用的 FastJson 對傳參進(jìn)行序列化,接下來我們采用 Gson 來做序列化

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>

然后新增一個(gè)特殊的方法

@Service
public class HelloService {
    /**
     * 字面量扁位,注意用單引號包裹起來
     * @param key
     * @return
     */
    @Log(key = "'yihuihuiblog'")
    public String hello(String key, HelloService helloService) {
        return key + "_" + helloService.say(new DemoDo().setName(key).setAge(10), "prefix");
    }
}

注意上面方法的第二個(gè)參數(shù)准潭,非常有意思的是,傳參是自己的實(shí)例域仇;再次執(zhí)行

public Application(HelloService helloService) {
    helloService.say(new DemoDo().setName("一灰灰blog").setAge(18), "welcome");

    String ans = helloService.hello("一灰灰", helloService);
    System.out.println(ans);
}

直接拋了異常

image

這就很尷尬了刑然,一個(gè)輸出日志的輔助工具,因?yàn)樾蛄谢苯訉?dǎo)致接口不可用暇务,這就不優(yōu)雅了泼掠;而我們作為日志輸出的切面,又是沒有辦法控制這個(gè)傳參的垦细,沒辦法要求使用的參數(shù)择镇,一定能序列化,這里需要額外注意 (比較好的方式就是簡單對象都實(shí)現(xiàn) toString,然后輸出 toString 的結(jié)果括改;而不是 json 串)

4. 小結(jié)

雖然上面一大串的內(nèi)容沐鼠,總結(jié)下來,也就兩點(diǎn)

  • SpEL 若采用的是SimpleEvaluationContext叹谁,那么注意 spel 的功能是減弱的饲梭,一些特性不支持
  • 若將方法參數(shù) json 序列化輸出,那么需要注意某些類在序列化的過程中焰檩,可能會(huì)拋異常

(看到這里的小伙伴憔涉,不妨點(diǎn)個(gè)贊,順手關(guān)注下微信公眾號”一灰灰 blog“析苫,我的公眾號已經(jīng)寂寞的長草了 ??)

III. 不能錯(cuò)過的源碼和相關(guān)知識(shí)點(diǎn)

0. 項(xiàng)目

AOP 系列博文

1. 一灰灰 Blog

盡信書則不如兜叨,以上內(nèi)容穿扳,純屬一家之言,因個(gè)人能力有限国旷,難免有疏漏和錯(cuò)誤之處矛物,如發(fā)現(xiàn) bug 或者有更好的建議,歡迎批評指正跪但,不吝感激

下面一灰灰的個(gè)人博客履羞,記錄所有學(xué)習(xí)和工作中的博文,歡迎大家前去逛逛

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屡久,一起剝皮案震驚了整個(gè)濱河市忆首,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌被环,老刑警劉巖糙及,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筛欢,居然都是意外死亡浸锨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門版姑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柱搜,“玉大人,你說我怎么就攤上這事漠酿》氚迹” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵炒嘲,是天一觀的道長宇姚。 經(jīng)常有香客問我,道長夫凸,這世上最難降的妖魔是什么浑劳? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮夭拌,結(jié)果婚禮上魔熏,老公的妹妹穿的比我還像新娘。我一直安慰自己鸽扁,他們只是感情好蒜绽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桶现,像睡著了一般躲雅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骡和,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天相赁,我揣著相機(jī)與錄音相寇,去河邊找鬼。 笑死钮科,一個(gè)胖子當(dāng)著我的面吹牛唤衫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绵脯,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼佳励,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了桨嫁?” 一聲冷哼從身側(cè)響起植兰,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤份帐,失蹤者是張志新(化名)和其女友劉穎璃吧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體废境,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畜挨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了噩凹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巴元。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驮宴,靈堂內(nèi)的尸體忽然破棺而出逮刨,到底是詐尸還是另有隱情,我是刑警寧澤堵泽,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布修己,位于F島的核電站,受9級特大地震影響迎罗,放射性物質(zhì)發(fā)生泄漏睬愤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一纹安、第九天 我趴在偏房一處隱蔽的房頂上張望尤辱。 院中可真熱鬧,春花似錦厢岂、人聲如沸光督。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽结借。三九已至,卻和暖如春窗怒,著一層夾襖步出監(jiān)牢的瞬間映跟,已是汗流浹背蓄拣。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留努隙,地道東北人球恤。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像荸镊,于是被迫代替她去往敵國和親咽斧。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344