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();
注:此处的主要知识点是反射。
这样就实现了我们所要的功能。不重启服务,又能增加业务逻辑,是不是很牛。
原码下载地址
相关文章推荐
- (二)SpringBoot之springboot开发工具的使用以及springboot插件的功能
- springboot idea开发效率插件之禅
- eclipse开发springboot项目插件
- (二)SpringBoot之springboot开发工具的使用以及springboot插件的功能
- 全栈开发——动手打造属于自己的直播间(Vue+SpringBoot+Nginx)
- Java 开发基于Zookeeper,Spring,vue.js的高并发多用户模块化微信商城系统(四) Java微框架Spring Boot的应用
- 使用eclipse开发struts+spring+hibernate免费插件套装
- SpringBoot Rest-api开发
- Spring Boot——开发新一代Spring应用
- SpringBoot开发详解(三)--SpringBoot配置文件YML注意事项
- Springboot分模块开发详解(2):建立子工程
- SpringBoot开发微信点餐系统笔记--项目设计
- SpringBoot + MyBatis + MySQL + Maven在Intellij IDEA下开发环境的搭建
- 【脚手架-优雅开发Javaweb】springboot+gradle+tomcat
- SpringBoot开发微信公众号(五)
- 【HAVENT原创】Spring Boot + Kafka 消息日志开发
- IntelliJ IDEA 开发Spring-Boot之Hello World
- spring-boot使用教程之三:如何使用sts中的热部署来降低代码开发量
- SpringBoot开发详解(五)--Controller接收参数以及参数校验
- spring boot开发遇到坑之spring-boot-starter-web配置文件使用教程