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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容