springboot動態(tài)添加aop切面

需求:在不停止服務(wù)的情況下录煤,通過上傳一個jar包然后捕獲某方法的異常進(jìn)行處理

思路:

使用springaop實現(xiàn)

  • 定義一個切入點為service包下面的所以方法

  • 將jar文件加載到classLoader

  • 動態(tài)添加切入點到指定的方法

至于為什么要定義一個切入點到service包下面的所以方法,感興趣的可以研究一下springAop的源碼舰讹,里面有個postProcessBeforeInstantiation方法穷绵,會返回代理對象带斑,如果沒有則不會返回代理對象席噩。
當(dāng)然還有一種思路模捂,就是在動態(tài)添加切入點的時候把spring容器中的對象替換成自己的代理對象(沒有實驗過捶朵,在非單例模式的時候有問題,這里不深入研究)狂男。

引入aop的starter:


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

第一步:


@Aspect
@Configuration
public class TestAop {
    /*
     * 定義一個大的切入點
     */
    @Pointcut("execution(* com.cdinit.spring.demo..*(..))")
    public void initAllAop(){}
    @Before("initAllAop()")
    public void initAllAop1(){
    }
}

第二步:


//核心邏輯 實例化jar包里面的類
private Advice buildAdvice(PluginConfig config) throws Exception {

        if (adviceCache.containsKey(config.getClassName())) {
            return adviceCache.get(config.getClassName());
        }

        File jarFile = new File(config.getLocalUrl());

        // 將本地jar 文件加載至 classLoader
        URLClassLoader loader = (URLClassLoader) getClass().getClassLoader();
        URL targetUrl = jarFile.toURI().toURL();
        boolean isLoader = false;
        for (URL url : loader.getURLs()) {
            if (url.equals(targetUrl)) {
                isLoader = true;
                break;
            }
        }
        if (!isLoader) {
            Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
            add.setAccessible(true);
            add.invoke(loader, targetUrl);
        }
        // Advice 實例化
        Class<?> adviceClass = loader.loadClass(config.getClassName()); //上傳的jar文件的類
        if (!Advice.class.isAssignableFrom(adviceClass)) {
            throw new RuntimeException("無法實例化非" + Advice.class.getName() + "的實例");
        }
        adviceCache.put(adviceClass.getName(), (Advice) adviceClass.newInstance());
        return adviceCache.get(adviceClass.getName());
    }

@Service
public class AopPluginTest implements ApplicationContextAware, InitializingBean {
//核心邏輯 根據(jù)切入點動態(tài)切入
    private ApplicationContext applicationContext; // 應(yīng)用上下文
    private Map<String, Advice> adviceCache = new HashMap<>();

    private PluginConfig pluginConfig = new PluginConfig()
            .setId("1")
            .setName("test")
            .setClassName("CountingBeforeAdvice")
            .setLocalUrl("E:\\aop-fix-zero\\target\\aop-fix-zero-1.0-SNAPSHOT.jar")
            .setActive("true")
//            .setExp("execution(* *.test(..))") // 加入切入點到切面
            .setExp("execution(* test(..))")
            .setVersion("1.0");

    public void activePlugin(String pluginId) {

        PluginConfig config = pluginConfig; // TODO 這里應(yīng)該從數(shù)據(jù)庫里面查詢config的配置

        for (String name : applicationContext.getBeanDefinitionNames()) {
            Object bean = applicationContext.getBean(name);
            if (bean == this)
                continue;
            if (!(bean instanceof Advised)) // 如果bean不是Advised類型則跳過
                continue;
            if (findAdvice(config.getClassName(), (Advised) bean) != null) // 如果bean已經(jīng)注冊了Advised則跳過
                continue;

            Advice advice = null;
            try {
                advice = buildAdvice(config); //初始化 Plugin Advice 實例化
                // 包一層 advisor
                AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
                advisor.setExpression(config.getExp());
                advisor.setAdvice(advice);
                ((Advised) bean).addAdvisor(advisor);
            } catch (Exception e) {
                throw new RuntimeException("安裝失敗", e);
            }
        }
    }

    private Advice findAdvice(String className, Advised advised) {
        for (Advisor a : advised.getAdvisors()) {
            if (a.getAdvice().getClass().getName().equals(className)) {
                return a.getAdvice();
            }
        }
        return null;
    }
}

jar包怎么寫综看?只需要實現(xiàn)對應(yīng)的切面方法就行了


public class ServerLogPlugin implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        String result = String.format("%s.%s() 參數(shù):%s", method.getDeclaringClass().getName(), method.getName(),
                Arrays.toString(args));
        System.out.println(result);
    }

}


通常有方法前攔截,方法后攔截岖食,以及異常攔截红碑。通過在這些攔截中編寫自己的業(yè)務(wù)處理,可以達(dá)到特定的需求泡垃。

  • MethodBeforeAdvice

  • MethodBeforeAdvice

  • ThrowsAdvice

execution表達(dá)式


匹配所有類public方法  execution(public * *(..))
匹配指定包下所有類方法 execution(* com.baidu.dao.*(..)) 不包含子包
execution(* com.baidu.dao..*(..))  ..*表示包析珊、子孫包下所有類
匹配指定類所有方法 execution(* com.baidu.service.UserService.*(..))
匹配實現(xiàn)特定接口所有類方法
    execution(* com.baidu.dao.GenericDAO+.*(..))
匹配所有save開頭的方法 execution(* save*(..))

20200401:添加注入applicationContext到j(luò)ar里面

public Advice buildAdvice(PluginConfig config) throws Exception {

        if (adviceCache.containsKey(config.getClassName())) {
            return adviceCache.get(config.getClassName());
        }

        File jarFile = new File(config.getLocalUrl());

        // 將本地jar 文件加載至 classLoader
        URLClassLoader loader = (URLClassLoader) getClass().getClassLoader();
        URL targetUrl = jarFile.toURI().toURL();
        boolean isLoader = false;
        for (URL url : loader.getURLs()) {
            if (url.equals(targetUrl)) {
                isLoader = true;
                break;
            }
        }
        if (!isLoader) {
            Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
            add.setAccessible(true);
            add.invoke(loader, targetUrl);
        }
        // 初始化 Plugin Advice 實例化
        Class<?> adviceClass = loader.loadClass(config.getClassName());
        if (!Advice.class.isAssignableFrom(adviceClass)) {
            throw new RuntimeException(
                    String.format("plugin 配置錯誤 %s非 %s的實現(xiàn)類 ", config.getClassName(), Advice.class.getName()));
        }

//        Advice advice = (Advice) adviceClass.newInstance();

        DefaultListableBeanFactory factory = (DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
        // 通過BeanDefinitionBuilder創(chuàng)建bean定義
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(adviceClass);
        beanDefinitionBuilder.addPropertyValue("applicationContext",applicationContext);
        // 注冊bean
        factory.registerBeanDefinition("adviceClass", beanDefinitionBuilder.getRawBeanDefinition());
        Advice bean = (Advice) this.applicationContext.getBean("adviceClass");
        adviceCache.put(adviceClass.getName(), (Advice)bean);
        return adviceCache.get(adviceClass.getName());
    }

https://github.com/cdInit/aopHotPlugin

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蔑穴,隨后出現(xiàn)的幾起案子唾琼,更是在濱河造成了極大的恐慌,老刑警劉巖澎剥,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赶舆,居然都是意外死亡哑姚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門芜茵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叙量,“玉大人,你說我怎么就攤上這事九串〗逝澹” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我姐直,道長姨涡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任炬搭,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涯呻。我一直安慰自己凉驻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布复罐。 她就那樣靜靜地躺著涝登,像睡著了一般。 火紅的嫁衣襯著肌膚如雪效诅。 梳的紋絲不亂的頭發(fā)上胀滚,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音填帽,去河邊找鬼蛛淋。 笑死,一個胖子當(dāng)著我的面吹牛篡腌,可吹牛的內(nèi)容都是我干的褐荷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嘹悼,長吁一口氣:“原來是場噩夢啊……” “哼叛甫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杨伙,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤其监,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后限匣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抖苦,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年米死,在試婚紗的時候發(fā)現(xiàn)自己被綠了锌历。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡峦筒,死狀恐怖究西,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情物喷,我是刑警寧澤卤材,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站峦失,受9級特大地震影響扇丛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尉辑,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一晕拆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦实幕、人聲如沸吝镣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽末贾。三九已至,卻和暖如春整吆,著一層夾襖步出監(jiān)牢的瞬間拱撵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工表蝙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拴测,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓府蛇,卻偏偏與公主長得像集索,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汇跨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354