java自定义类加载器、Springboot热部署原理
1、自定义类加载器实现热加载
2、aop拦截
3、文件监听
4、异常:bean方法无法调用,报空异常
1、配置Configuration
1.1 Configuration 类代码
@Configuration
@ComponentScan("com.xxx.common.extend.controller")
@EnableAspectJAutoProxy
//@EnableWebMvc
public class CommonControlConfig{// implements WebMvcConfigurer {
@Bean
public void testTestRpcTestImpl(){
try{
System.out.println("--------------Application-------------start----------------------");
Application.run();
System.out.println("--------------Application--------------end-----------------------");
}catch (Throwable e){
e.printStackTrace();
}
}
// @Bean
// public HandlerInterceptor getMyInterceptor(){
// return new UrlInterceptor();
// }
//
// @Override
// public void addInterceptors(InterceptorRegistry registry) {
// // addPathPatterns 用于添加拦截规则, 这里假设拦截 /url 后面的全部链接
// // excludePathPatterns 用户排除拦截
// registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
// //WebMvcConfigurer.super.addInterceptors(registry);
// }
}
1.2 spring.factories配置文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xxxxxxxxxx
2、代码实现
2.1 引入maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 aop拦截
1、aop拦截,定义拦截的包
@Aspect
@Component
public class AuthorityAspect {
/**
* 用于拦截类的切点
* 此处注意execution(*后面要有空格否则会报错type name pattern expected
*/
@Pointcut("execution (* com.xxx.xxx.xxx.xxx..*.*(..))")
//第一个..指api包下的所有类包括子包下的类启用拦截,如果改成.则不包括子包下的类;第二个..类中的所有方法启用拦截
private void cutTokens(){
}
@Around("cutTokens()")
public Object doToken(ProceedingJoinPoint joinPoint) {
Object out= null;
try{
String className=joinPoint.getSignature().getDeclaringTypeName();
String methodName=joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs() ;
//System.out.println("拦截了");
if(className.contains("ServiceImpl")){
Object[] joinPointArgs = joinPoint.getArgs();
if(joinPointArgs==null){
throw new RuntimeException("获取参数异常");
}
//int userId = (int)joinPointArgs[0];
//log.info("解析userId:"+userId);
//构造器会重新加载rootPath+packagePath下的class文件
//MyClassLoader myClassLoader = new MyClassLoader(Application.rootPath,Application.rootPath+Application.packagePath );
out=run( Application.myClassLoader, className, methodName, args);
}else if(className.contains("controller")){
System.out.println("controller拦截了");
Object[] joinPointArgs = joinPoint.getArgs();
if(joinPointArgs==null){
throw new RuntimeException("获取参数异常");
}
//int userId = (int)joinPointArgs[0];
//log.info("解析userId:"+userId);
//构造器会重新加载rootPath+packagePath下的class文件
//MyClassLoader myClassLoader = new MyClassLoader(Application.rootPath,Application.rootPath+Application.packagePath );
out=run( Application.myClassLoader, className, methodName, args);
}else{
return joinPoint.proceed();
}
}catch (Throwable e){
e.printStackTrace();
}
return out;
}
public Object run(MyClassLoader myClassLoader, String className, String methodName, Object[] args) throws Exception {
Class[] classs =new Class[args.length];
for(int i=0;i<args.length;i++){
classs[i]=args[i].getClass();
}
System.out.println("className:"+className+",methodName:"+methodName);
Class<?> aClass = myClassLoader.loadClass(className);
//Object out= aClass.getMethod(methodName,classs).invoke(aClass.newInstance(),args);
Object out= invokMethod( aClass, methodName, classs, args );
if(out==null){
out= aClass.getMethod(methodName,classs).invoke(aClass.newInstance(),args);
}
return out;
//不可以直接调用start,只能用自己的myClassLoader反射调用start,全盘委托机制
//start();
}
public Object invokMethod(Class<?> aClass,String methodName,Class[] classs,Object[] args ){
Object out=null;
try {
Object bean = SpringBeanUtils.getBean(aClass);
//ApplicationContext applicationContext=SpringBeanUtils.getApplicationContext();
//SpringBeanUtils.getApplicationContext().getAutowireCapableBeanFactory().autowireBean(bean);
//applicationContext.getAutowireCapableBeanFactory().autowireBean(applicationContext.getBean(aClass));
Method method =aClass.getDeclaredMethod(methodName,classs);
// 设置访问权限
method.setAccessible(true);
out= method.invoke(bean,args);
}catch (Throwable e){
e.printStackTrace();
}
return out;
}
2.3 自定义类加载器
public class MyClassLoader extends ClassLoader {
public String rootPath;
private Map<String, byte[]> clazzs = new HashMap<>();
public MyClassLoader(String rootPath, String... classPaths) throws Exception {
this.rootPath = rootPath;
//this.clazzs = new HashMap<>();
for (String classPath : classPaths) {
scanClassPath(new File(classPath));
}
}
private void scanClassPath(File file) {
try {
if (file.isDirectory()) {
for (File listFile : file.listFiles()) {
scanClassPath(listFile);
}
} else {
String fileName = file.getName();
//String filePath = file.getPath();
//String suffix = fileName.substring(fileName.lastIndexOf("."));
if (fileName.endsWith("jar")) {
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
if (name.endsWith("class")) {
try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
int pos = 0;
int len;
byte[] buff = new byte[inputStream.available()];
while ((len = inputStream.read(buff, pos, buff.length - pos)) > 0) {
pos += len;
}
String className = jarEntry.getName().replace("/", ".")
.replace(".class", "");
clazzs.put(className, buff);
} catch (Throwable e) {
System.out.println("scanClassPath获取失败:" + fileName);
}
}
}
//System.out.println("scanClassPath加载:" + fileName);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> loadClass = findLoadedClass(name);
if (loadClass == null) {
byte[] bytes = clazzs.get(name);
try {
if (bytes == null) {
loadClass = super.loadClass(name);
System.out.println("super加载:" + name);
} else {
loadClass = defineClass(name, bytes, 0, bytes.length);
System.out.println("自定义加载器加载:" + name);
}
} catch (Throwable e) {
System.out.println("加载失败:-------" + name);
//e.printStackTrace();
}
try {
if (loadClass == null) {
loadClass = findClass(name);
System.out.println("findClass加载:" + name);
}
} catch (Throwable e) {
System.out.println("findClass加载失败:-------" + name);
//e.printStackTrace();
}
if (loadClass == null) {
try {
loadClass = Thread.currentThread().getContextClassLoader().loadClass(name);
System.out.println("ContextClassLoader加载 :-------" + name);
} catch (Throwable e) {
System.out.println("ContextClassLoader加载失败:-------" + name);
}
}
}
return loadClass;
}
}
}
2.4 运行方法
1、run() 程序启动时启动监听,startFileListener方法启动监听
@Data
@AllArgsConstructor(access = AccessLevel.PUBLIC)
public class Application {
public static String rootPath;
public static String packagePath = "#################";//jar包对应路径
public static MyClassLoader myClassLoader;
public static void run() {
try{
String rootPath = MyClassLoader.class.getResource("/").getPath().replaceAll("%20", " ");
rootPath = new File(rootPath).getPath();
//处理路径
rootPath = CommonFun.pathHandle(rootPath);
Application.rootPath = rootPath;
MyClassLoader myClassLoader2=new MyClassLoader(rootPath,rootPath+packagePath);
startFileListener(rootPath+ packagePath);
myClassLoader=myClassLoader2;
}catch (Throwable e){
e.printStackTrace();
}
}
//监听文件改动,一旦文件发生改动,则回调FileListener的onFileChange
public static void startFileListener(String rootPath) {
try {
//String listenerPath =rootPath+"/apps/tbxm/tbxmmes";
// System.out.println("listenerPath:"+listenerPath);
FileAlterationObserver fileAlterationObserver = new FileAlterationObserver(rootPath);
fileAlterationObserver.addListener(new FileListener());
FileAlterationMonitor fileAlterationMonitor = new FileAlterationMonitor(1000);
fileAlterationMonitor.addObserver(fileAlterationObserver);
fileAlterationMonitor.start();
} catch (Throwable e) {
System.out.println("文件监听启动失败");
}
}
}
2.5 文件监听
1、文件监听方法 ,onFileChange 文件修改后,监听jar是否变化
public class FileListener extends FileAlterationListenerAdaptor {
@Override
public void onFileChange(File file) {
if (file.getName().endsWith("jar")){
System.out.println("文件监听,文件发生变化----"+file.getName());
try {
MyClassLoader myClassLoader =new MyClassLoader(Application.rootPath,file.getPath());
Application.myClassLoader=myClassLoader;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.6 方法类
1、用到的公共方法 pathHandle 处理 路径
public class CommonFun {
public static String pathHandle(String instr) {
String out = instr;
if (instr.contains("runt")) {
int index = instr.lastIndexOf("run");
out = instr.substring(0, index - 1);
}
return out;
}
}