需求:在不停止服務(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());
}