⾃⼰动⼿实现springboot运⾏时执⾏java源码(运⾏时编译、加
载、注册bean、调⽤)
看来断点、单步调试还不够硬核,根本没多少⼈看,这次再来个硬核的。依然是由于apaas平台越来越流⾏了,如果apaas平台选择了java语⾔作为平台内的业务代码,那么不仅仅⾯临着IDE外的断点、单步调试,还⾯临着为了实现预览效果,需要将写好的java源码动态的装载到spring容器中然后调⽤源码内的某个⽅法。这篇⽂章主要就是实现spring/springboot运⾏时将源码先编译成class字节码数组,然后字节码数组再经过⾃定义类加载器变成Class对象,接着Class对象注册到spring容器成为BeanDefinition,再接着直接获取到对象,最后调⽤对象中指定⽅法。相信在⽹上其他地⽅已经不到类似的实现了,毕竟像我这样专门做这种别⼈没有的原创的很少很少,⼤多都是转载下别⼈的,或者写些⽹上⼀⼤堆的知识点,哈哈!
个⼈认为分析复杂问题常见思维⽅式可以类⽐软件领域的分治思想,将复杂问题分解成⼀个个⼩问题去解决。或者是使⽤减治思想,将复杂问题每次解决⼀⼩部分,留下的问题继续解决⼀个⼩部分,这样循环直到问题全部解决。所以软件世界和现实世界确实是想通的,很多思想都可以启迪我们的⽣活,所以我⼀直认为⼀个很会⽣活的程序员,⼀个把⽣活中出现的问题都解决的很好的程序员⼀定是个好程序员,表⽰很羡慕这种程序员。
那么我们先分解下这个复杂问题,我们要将⼀个java类的源码直接加载到spring容器中调⽤,⼤致要经历的过程如下:
1、先将java类源码动态编译成字节数组。这⼀点在java的tools.jar已经有⼯具可以实现,其实tools.jar⼯具包真的是⼀个很好的东西,往往你⾛投⽆路不知道怎么实现的功能在tools.jar都有⼯具,⽐如断点调试,⽐如运⾏时编译,呵呵
2、拿到动态编译的字节码数组后,就需要将字节码加载到虚拟机,⽣成Class对象。这⾥应该不难,直接通过⾃定义⼀个类加载器就可以搞定
3、拿到Class对象后,再将Class转成Spring的Bean模板对象BeanDefinition。这⾥可能需要⼀点spring的知识随便看⼀点spring启动那⾥的源码就懂了。
4、使⽤spring的应⽤上下⽂对象ApplicationContext的getBean拿到真正的对象。这个应该⽤过spring的都知道
5、调⽤对象的指定⽅法。这⾥为了不需要⽤反射,⼀般⽣成的对象都继承⼀个明确的基类或者实现⼀个明确的接⼝,这样就可以由多肽机制,通过接⼝去接收实现类的引⽤,然后直接调⽤指定⽅法。
下⾯先看看动态编译的实现,核⼼源码如下
/**
* 动态编译java源码类
* @author rongdi
* @date 2021-01-06
*/
public class DynamicCompiler {
/**
* 编译指定java源代码
* @param javaSrc java源代码
* @return返回类的全限定名和编译后的class字节码字节数组的映射
*/
public static Map<String, byte[]> compile(String javaSrc) {
Pattern pattern = Patternpile("public\\s+class\\s+(\\w+)");
Matcher matcher = pattern.matcher(javaSrc);
if (matcher.find()) {
return up(1) + ".java", javaSrc);
}
return null;
}
/**
* 编译指定java源代码
* @param javaName java⽂件名
* @param javaSrc java源码内容
* @return返回类的全限定名和编译后的class字节码字节数组的映射
*/
public static Map<String, byte[]> compile(String javaName, String javaSrc) {
JavaCompiler compiler = SystemJavaCompiler();
StandardJavaFileManager stdManager = StandardFileManager(null, null, null);
try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
JavaFileObject javaFileObject = manager.makeStringSource(javaName, javaSrc);
JavaCompiler.CompilationTask task = Task(null, manager, null, null, null, Arrays.asList(javaFileObject));
if (task.call()) {
ClassBytes();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
然后就是⾃定义类加载器的实现了
/**
* ⾃定义动态类加载器
* @author rongdi
* @date 2021-01-06
*/
public class DynamicClassLoader extends URLClassLoader {
Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
public DynamicClassLoader(Map<String, byte[]> classBytes) {
super(new URL[0], ClassLoader());
this.classBytes.putAll(classBytes);
}
/**
* 对外提供的⼯具⽅法,加载指定的java源码,得到Class对象
* @param javaSrc java源码
* @return
*/
public static Class<?> load(String javaSrc) throws ClassNotFoundException {
/**
* 先试⽤动态编译⼯具,编译java源码,得到类的全限定名和class字节码的字节数组信息
*/
Map<String, byte[]> bytecode = DynamicCompilerpile(javaSrc);
if(bytecode != null) {
/**
* 传⼊动态类加载器
*/
DynamicClassLoader classLoader = new DynamicClassLoader(bytecode);
/**
* 加载得到Class对象
*/
return classLoader.loadClass(bytecode.keySet().iterator().next());
} else {
throw new ClassNotFoundException("can not found class");
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = (name);
if (buf == null) {
return super.findClass(name);
}
return defineClass(name, buf, 0, buf.length);
}
}
接下来就是将源码编译、加载、放⼊spring容器的⼯具了
package utils;
import piler.DynamicClassLoader;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import t.ApplicationContext;
import t.ConfigurableApplicationContext;
/**
* 基于spring的应⽤上下⽂提供⼀些⼯具⽅法
* @author rongdi
* @date 2021-02-06
*/
public class ApplicationUtil {
/**
* 注册java源码代表的类到spring容器中
* @param applicationContext
* @param src
*/
public static void register(ApplicationContext applicationContext, String src) throws ClassNotFoundException { register(applicationContext, null, src);
}
/**
* 注册java源码代表的类到spring容器中
* @param applicationContext
* @param beanName
* @param src
*/
public static void register(ApplicationContext applicationContext, String beanName, String src) throws ClassNotFoundException {
/**
* 使⽤动态类加载器载⼊java源码得到Class对象
*/
Class<?> clazz = DynamicClassLoader.load(src);
/**
* 如果beanName传null,则赋值类的全限定名
*/
if(beanName == null) {
beanName = Name();
}
/**
* 将applicationContext转换为ConfigurableApplicationContext
*/
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
/**
* 获取bean⼯⼚并转换为DefaultListableBeanFactory
*/
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) BeanFactory();
/**
* 万⼀已经有了这个BeanDefinition了,先remove掉,不然⼀次容器启动没法多次调⽤,这⾥千万别⽤成
* defaultListableBeanFactory.destroySingleton()了,BeanDefinition的注册只是放在了beanDefinitionMap中,还没有
* 放⼊到singletonObjects这个map中,所以不能⽤destroySingleton(),这个是没效果的
*/
if (ainsBeanDefinition(beanName)) {
}
/**
* 使⽤spring的BeanDefinitionBuilder将Class对象转成BeanDefinition
*/
BeanDefinitionBuilder beanDefinitionBuilder = icBeanDefinition(clazz);
/**
* 以指定beanName注册上⾯⽣成的BeanDefinition
*/
}
/**
* 使⽤spring上下⽂拿到指定beanName的对象
*/
public static <T> T getBean(ApplicationContext applicationContext, String beanName) {
return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(beanName);
}
/**
* 使⽤spring上下⽂拿到指定类型的对象
*/
public static <T> T getBean(ApplicationContext applicationContext, Class<T> clazz) {
return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(clazz);
}
}
再给出⼀些必要的测试类
package dao;
import org.springframework.stereotype.Component;
/**
* 模拟⼀个简单的dao实现
* @author rongdi
* @date 2021-01-06
*/
@Component
public class TestDao {
public String query(String msg) {
return "msg:"+msg;
}
}
package service;
import dao.TestDao;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 模拟⼀个简单的service抽象类,其实也可以是接⼝,主要是为了把dao带进去,
* 所以就搞了个抽象类在这⾥
* @author rongdi
* @date 2021-01-06
*/
public abstract class TestService {
@Autowired
protected TestDao dao;
public abstract String sayHello(String msg);
}
最后就是测试的⼊⼝类了
package ontroller;
import service.TestService;
import utils.ApplicationUtil;
import org.springframework.beans.BeansException;
import t.ApplicationContext;
import t.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 测试⼊⼝类
* @author rongdi
* @date 2021-01-06
*/
@Controller
public class DemoController implements ApplicationContextAware {
private static String javaSrc = "package com;" +
"public class TestClass extends service.TestService{" +
" public String sayHello(String msg) {" +
" return \"我查到了数据,\"+dao.query(msg);" +
" }" +
java源代码加密"}";
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 测试接⼝,实际上就是完成动态编译java源码、加载字节码变成Class,装载Class到spring容器,
* 获取对象,调⽤对象的测试
* @return
* @throws Exception
*/
@RequestMapping("/test")
@ResponseBody
public String test() throws Exception {
/**
* 美滋滋的注册源码到spring容器得到⼀个对象
* ister(applicationContext, javaSrc);
*/
/**
* 从spring上下⽂中拿到指定beanName的对象
* 也可以 TestService testService = Bean(applicationContext,TestService.class);
*/
TestService testService = Bean(applicationContext,"testClass");
/**
* 直接调⽤
*/
return testService.sayHello("haha");
}
}
想想应该有点激动了,使⽤这套代码⾄少可以实现如下风骚的效果
1、开放⼀个动态执⾏代码的⼊⼝,将这个代码内容放在⼀个post接⼝⾥提交过去,然后直接执⾏返回结果
2、现在你有⼀个apaas平台,⾥⾯的业务逻辑使⽤java代码实现,写好保存后,直接放⼊spring容器,⾄于执⾏不执⾏看你⾃⼰业务了
3、结合上⼀篇⽂章的断点调试,你现在已经可以实现在⾃⼰平台使⽤java代码写逻辑,并且⽀持断点和单步调试你的java代码了
好了,这次的主题⼜接近尾声了,如果对我的⽂章感兴趣或者需要详细源码,请⽀持⼀下我的同名,⽅便⼤家可以第⼀时间收到⽂章更新,同时也让我有更⼤的动⼒继续保持强劲的热情,替⼤家解决⼀些⽹上搜索不到的问题,当然如果有啥想让我研究的,也可以⽂章留⾔或者发送信息。如果有必要,我会花时间替⼤家研究研究。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论