您的位置:首页 > 编程语言 > Java开发

Spring boot 业务插件开发

2018-02-27 15:51 621 查看

Spring boot 动态加载class, 注册Bean 插件开发简单实现

使用场景

项目运行,在不重启服务器的前提下,动态增加业务逻辑。业务逻辑以插件的形式加入。

技术分析

虚拟机在启动时已经把class文件加载到虚拟机,要考虑如何把外部的 class 文件加载到虚拟机中。

class 文件加载到虚拟机后,要考虑如何把类动态的加到 spring中,注册成bean.

如何调用加载到spring容器中的 bean.

技术实现

外部class文件加载到JVM

public static List<Class> LoadClass(String filePath, ClassLoader beanClassLoader) {
List<Class> classNameList = new ArrayList<>();
File clazzPath = new File(filePath);
int clazzCount = 0;
if (clazzPath.exists() && clazzPath.isDirectory()) {
int clazzPathLen = clazzPath.getAbsolutePath().length() + 1;
Stack<File> stack = new Stack<>();
stack.push(clazzPath);
while (stack.isEmpty() == false) {
File path = stack.pop();
File[] classFiles = path.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory() || pathname.getName().endsWith(".class");
}
});
for (File subFile : classFiles) {
if (subFile.isDirectory()) {
stack.push(subFile);
} else {
if (clazzCount++ == 0) {
Method method = null;
try {
method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
boolean accessible = method.isAccessible();
try {
if (accessible == false) {
method.setAccessible(true);
}
URLClassLoader classLoader = (URLClassLoader) beanClassLoader;
try {
URL url = clazzPath.toURI().toURL();
method.invoke(classLoader, url);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
} finally {
method.setAccessible(accessible);
}
}
String className = subFile.getAbsolutePath();
className = className.substring(clazzPathLen, className.length() - 6);
className = className.replace(File.separatorChar, '.');
try {
classNameList.add(Class.forName(className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
return classNameList;
}


注:filePath是外部class 文件的根目录(例如,插件包名为com.test 类为Plugin. java ,根目录为d:/aaa ,Plugin.java全目录应为:d:/aaa/com/test/Plugin. java)

动态注册bean.

@RequestMapping("/beanLoad")
public String beanLoad(){
ApplicationContext applicationContext = CommonContextUtils.getApplicationContext();
ConfigurableApplicationContext context = (ConfigurableApplicationContext)applicationContext;
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)context.getBeanFactory();
ClassLoader beanClassLoader = beanFactory.getBeanClassLoader();
Class aClass = ClassUtil.LoadClass("D:/classtest",beanClassLoader).get(0);
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(aClass);
beanFactory.registerBeanDefinition(aClass.getName(),beanDefinitionBuilder.getRawBeanDefinition());
Object aaa = (applicationContext.getBean(aClass.getName()));
Method m = null;
Method m2 = null;
try {
m2 = aClass.getMethod("setApplicationContext", ApplicationContext.class);
m = aClass.getMethod("getString", null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Object ret = null;
try {
m2.invoke(aaa,applicationContext);
ret = m.invoke(aaa, null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return ret.toString();
}


此处涉及一个知识点,如果取得ApplicationContext

@SuppressWarnings({ "rawtypes", "unchecked" })
@Component
public class CommonContextUtils implements ApplicationContextAware {

private static ApplicationContext applicationContext;

public void setApplicationContext(ApplicationContext arg0)
throws BeansException {
applicationContext = arg0;
}

/**
* 获取applicationContext对象
* @return
*/
public static ApplicationContext getApplicationContext(){
return applicationContext;
}

/**
* 根据bean的id来查找对象
* @param id
* @return
*/
public static Object getBeanById(String id){
return applicationContext.getBean(id);
}

/**
* 根据bean的class来查找对象
* @param c
* @return
*/
public static Object getBeanByClass(Class c){
return applicationContext.getBean(c);
}

/**
* 根据bean的class来查找所有的对象(包括子类)
* @param c
* @return
*/
public static Map getBeansByClass(Class c){
return applicationContext.getBeansOfType(c);
}
}


注:此时已经实现了class加载和bean的注册

bean的调用

Object aaa = (applicationContext.getBean(aClass.getName()));
Method m = null;
try {
m = aClass.getMethod("getString", null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Object ret = null;
try {
ret = m.invoke(aaa, null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return ret.toString();


注:此处的主要知识点是反射。

这样就实现了我们所要的功能。不重启服务,又能增加业务逻辑,是不是很牛。

原码下载地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: