功能简介
热加载starter启动器
该功能依托于框架自定义的 JarIndexClassLoader,以及 loveqq 框架支持ioc容器的全量刷新。 通过api接口上传 jar 包后,全量刷新ioc容器,从而实现热加载启动器,动态扩展功能。
@This 注解支持自调用
注解了 @This 的 bean,即使在内部使用 this.xxx() 调用方法,也可以使代理生效,再也不用担心自调用导致的事务失效场景。
@Async.Await 注解自动将异步转同步调用
注解了 Async.Await 的方法,在其方法体调用链路中,所有的异步方法都将自动转为同步调用,不仅仅是 @Async 注解的方法,返回值类型为 Future 的同样支持。
组合注解工具类
该工具类基于 @AliasFor 注解实现了注解组合,但是相对spring的实现更为简单,仅仅50行代码就实现了组合注解注解, 解析结果就是直接的注解实例,和普通的注解实例没有区别,结果直观,易于使用。
代码示例
热加载starter启动器
先写一个web服务:
@Slf4j
@EnableWebMvc
@RestController
@BootApplication
@RequestMapping(expose = true) // 自动暴露 public 方法为 POST http 接口
public class Main {
@Autowired
private Extension extension;
/**
* 测试接口
*/
@GetMapping
public String sayHello() {
return extension.getId();
}
/**
* 加载插件
*
* @param jar jar 包 启动器
* @return 上传后的 jar 包绝对路径,卸载启动器时需要提供该返回值
*/
public String loadPlugin(MultipartFile jar) throws Exception {
// 保存到本地
String filePath = "D:\\temp\\jar\\" + UUID.randomUUID().toString().replace("-", "") + "\\" + jar.getOriginalFilename();
File jarFile = new File(filePath);
jar.transferTo(jarFile);
// 添加到框架 ClassLoader
JarIndexClassLoader classLoader = (JarIndexClassLoader) IOC.class.getClassLoader();
classLoader.addJarIndex(Collections.singletonList(new JarFile(jarFile)));
// 刷新上下文
ContextRefresher.refresh(IOC.getApplicationContext());
return jarFile.getAbsolutePath();
}
/**
* 卸载启动器
*
* @param jarPath {@link #loadPlugin(MultipartFile)} 的返回值
*/
public String unloadPlugin(String jarPath) throws Exception {
// 构建 File 对象
File jarFile = new File(jarPath);
// 从框架 ClassLoader 移除
JarIndexClassLoader classLoader = (JarIndexClassLoader) IOC.class.getClassLoader();
classLoader.removeJarIndex(Collections.singletonList(new JarFile(jarFile)));
// 刷新上下文
ContextRefresher.refresh(IOC.getApplicationContext());
return "ok";
}
public static void main(String[] args) throws Exception {
K.run(Main.class, args);
}
/**
* 默认实现
*/
@Component
@ConditionalOnMissingBean(Extension.class)
public static class DefaultExtension implements Extension {
@Override
public String getId() {
return "default";
}
@Override
public boolean isCritical() {
return false;
}
@Override
public byte[] getValue() {
return new byte[0];
}
@Override
public void encode(OutputStream out) throws IOException {
}
}
}
然后,新建一个项目,添加如下类:
/**
* 动态加载示例实现
*/
@Component
public class ExampleExtension implements Extension {
@Override
public String getId() {
return "example";
}
@Override
public boolean isCritical() {
return false;
}
@Override
public byte[] getValue() {
return new byte[0];
}
@Override
public void encode(OutputStream out) throws IOException {
}
}
并在 k.factories 中添加:
com.kfyty.loveqq.framework.core.autoconfig.annotation.EnableAutoConfiguration=com.kfyty.graal.example.ExampleExtension
然后打成 jar 包,就是一个启动器了。
接着启动第一段代码的 main 方法后:
1、先访问:http://localhost:8080/sayHello,将返回 default
2、然后使用 postman 上传启动器 jar 包:http://127.0.0.1:8080/loadPlugin,此时将动态加载上传的启动器,并刷新 ioc 容器 3、然后再访问:http://localhost:8080/sayHello,将返回 example,原因是加载了新的启动器,条件注解生效,实现类变化了!
4、然后再访问:http://127.0.0.1:8080/unloadPlugin,将第二步的返回值作为入参传入,此时将卸载启动器,并刷新 ioc 容器 5、然后再访问:http://localhost:8080/sayHello,将返回 default,原因是卸载了之前加载的启动器,条件注解生效,实现类又变化了!
@Async.Await 注解自动将异步转同步调用
使用代码示例:
@Slf4j
@Async
@BootApplication
public class Main implements CommandLineRunner {
@Autowired
private Main main;
@Autowired
private AsyncService asyncService;
@Override
public void run(String... args) throws Exception {
// main.test();
main.testAwait();
}
/**
* 结果输出 0,因为内部调用的是异步方法
*/
public void test() {
asyncService.asyncProcess();
System.out.println(asyncService.getState());
}
/**
* 结果输出 1,因为内部调用异步方法自动转为了同步
*/
@Async.Await
public void testAwait() {
asyncService.asyncProcess();
System.out.println(asyncService.getState());
}
public static void main(String[] args) throws Exception {
K.run(Main.class, args);
}
@Async
@Component
public static class AsyncService {
@Getter
private int state = 0;
@Async
public void asyncProcess() {
// 模拟业务耗时
CommonUtil.sleep(500);
// 修改状态
this.state = 1;
}
}
}
示例中的 testAwait() 方法中,调用 asyncProcess() 时将自动异步转同步,为某些业务场景提供的简单易用的支持。
组合注解工具类
组合注解代码示例:
@Main.AAA("aaa")
public class Main {
public static void main(String[] args) throws Exception {
Annotation[] annotations = AnnotationUtil.findAnnotations(Main.class);
BBB bbb = AnnotationUtil.findAnnotation(Main.class, BBB.class);
System.out.println(CommonUtil.toString(annotations));
}
@BBB
@Retention(RetentionPolicy.RUNTIME)
public @interface AAA {
@AliasFor(value = "path", annotation = BBB.class)
String value() default "";
}
@AAA
@Retention(RetentionPolicy.RUNTIME)
public @interface BBB {
@AliasFor(value = "value", annotation = AAA.class)
String path() default "";
}
}
示例代码得到的 bbb 注解实例中,bbb.path() 属性直接就是 aaa,结果非常直观,没有那么多的繁杂的工厂类,合并类等。
有对 loveqq-framework 感兴趣的同学可以gitee/gitcode/github搜索尝鲜