利用自定义ClassLoader和接口逻辑后台可刷新缓存实现java-web项目的动态发布

1。原理

java的自定义classloader可以加载自定义的类,实现动态加载。

可以通过集成classloader来实现,如下;

public Class<?> loadClass(String name) throws ClassNotFoundException
会调用下面的loadClass方法

protected synchronized Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException

(resolve  java  Links the specified class.  执行static块) 


  protected Class<?> findClass(String name) throws ClassNotFoundException {
	throw new ClassNotFoundException(name);
    }  推荐使用

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
	throws ClassFormatError
自己实现字节码加载 因此可以 进行字节码 增删改


spring可以动态加载xml配置文件,结合weblogic的jndi数据源,可以动态配置数据源到项目中。

调用loadBean方法加载一个新的xml就可以刷新bean容器了

  public void loadBean(String configLocation) throws BeansException {
        XmlBeanDefinitionReader beanDefinitionReader = getXmlBeanDefinitionReader();
        beanDefinitionReader.setResourceLoader(getSpcxt());
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(getSpcxt()));
        try {
            beanDefinitionReader.loadBeanDefinitions(getSpcxt().getResources(configLocation));
            logger.debug("load bean:" + configLocation + " ok");
        } catch (BeansException e) {
            logger.warn("loadBean failed", e);
            throw e;
        } catch (IOException e) {
            logger.warn("loadBean failed", e);
            throw new RuntimeException(e.getMessage());
        }
    }

    public void loadBeans(String tempFilePath) throws BeansException {
        String beanLocations = tempFilePath + "/classes/spring";
        if (!Validate.isNull(beanLocations)) {
            File locationsFolder = new File(beanLocations);
            if (locationsFolder.isDirectory()) {
                for (File file : locationsFolder.listFiles()) {
                    loadBean("classpath:" + "spring/" + file.getName());
                }
            } else {
                for (String beanLocation : beanLocations.split(","))
                    loadBean(beanLocation);
            }
        }
    }

    private XmlBeanDefinitionReader xmlBeanDefinitionReader;

    private XmlBeanDefinitionReader getXmlBeanDefinitionReader() {
        if (xmlBeanDefinitionReader != null) {
            return xmlBeanDefinitionReader;
        }
        xmlBeanDefinitionReader = new XmlBeanDefinitionReader(
                (BeanDefinitionRegistry) ((AbstractApplicationContext) getSpcxt()).getBeanFactory());
        return xmlBeanDefinitionReader;
    }

2.实现方式

1)自定义CLassLoader的子类,实现读取jar的字节码,使用  
      java.util.jar.JarEntry;
      java.util.jar.JarInputStream;


2)使用开源的jyaml的jar,并且自定义chain方式来执行

下面是一个类似于流程控制的yml文件

start: RequestHandler.validateParams

error: sandbox.error.ftl

exception: ExceptionHandler.doException


chainMaps:
    RequestHandler.validateParams:
      ok: RequestHandler.getAppInfo
    
    RequestHandler.getAppInfo:
      ok:  RequestHandler.resolveParams
      
    RequestHandler.resolveParams:
       ok:   RequestHandler.doAppEncode
    
    RequestHandler.doAppEncode:
        ok: RequestHandler.getReqUrl
    
    RequestHandler.getReqUrl:
       ok:   RequestHandler.getApiIps
       
    RequestHandler.getApiIps:   
       ok:    RequestHandler.doApiEncode
    
    RequestHandler.doApiEncode:
       ok:    RequestHandler.getRequestParams
       
    RequestHandler.getRequestParams:
       ok:    RequestHandler.getTempCode
      
    RequestHandler.getTempCode:
       ok:    RequestHandler.getResult
    RequestHandler.getResult:
       ok:    sandbox.return.ftl
       
    ExceptionHandler.doException:
       ok:    sandbox.exception.ftl
       
classMap:
    RequestHandler:     com.yundaex.yml.chain.request.RequestHandler
    ExceptionHandler:   com.yundaex.yml.chain.common.ExceptionHandler
    ErrorHandler:       com.yundaex.yml.chain.common.ErrorHandler
configMap: 
    config1: 100000999999999
这里的classMap中value指向某个类,这个时候,可以通过自定义classLoader来加载

如classLoader.findClass

然后,将这些class文件通通丢进一个jar包中,而同样通过public InputStream getResourceAsStream(String path)方法将yml文件也丢进jar包中。

3)spring mvc启动时候,将这些需要交给自定义classLoader的jar进行加载,然后以yml文件作为key进行一个接口单元缓存。



4)而在controller层,就可以这样写。


5)网页后台管理

通过网页后台进行统一jar包管理和jndi数据源管理。



6)配置共享

利用数据库进行一些诸如message和url的配置,然后利用redis等缓存程序共享jar包之间的数据,当然jar包之间不能相互依赖,只能共享配置。



7)后台线程的开发

因为线程需要一个激活和销毁,否则就会导致weblogic上出现线程销毁不掉的情况。

package com.yundasys.mobi.common.thread;

import java.util.HashMap;
import java.util.Map;

import com.yundasys.mobi.common.logger.LoggerUtil;
import com.yundasys.mobi.common.logger.LoggerUtil.Level;

public class ThreadManager {
    private static Map<String, Thread> threads = new HashMap<String, Thread>();

    public static void registerRunnable(NamedPollingTask runnable) {
        if (threads.get(runnable.getName()) != null) {
            threads.get(runnable.getName()).interrupt();
        }
        Thread thread = new Thread(runnable, runnable.getName());
        threads.put(runnable.getName(), thread);
        LoggerUtil.log(Level.INFO, runnable.getName() + " start");
        thread.start();
    }

    public static void interruptAll() {
        for (Thread thread : threads.values()) {
            thread.interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        registerRunnable(new NamedPollingTask() {
            @Override
            public String getName() {
                return "Thread 1";
            }

            @Override
            public long getSleepMills() {
                return 500;
            }

            @Override
            public void polling() {
                System.out.println("Thread 1");
            }
        });

        Thread.sleep(2000);
        registerRunnable(new NamedPollingTask() {
            @Override
            public String getName() {
                return "Thread 1";
            }

            @Override
            public long getSleepMills() {
                return 500;
            }

            @Override
            public void polling() {
                System.out.println("Thread 111111");
            }
        });

    }

}

这是一个自定义的简单线程池。

线程池通过覆盖安装的方式进行线程start。

但是,由于利用classloader不能马上执行object的代码,所以,不能再后台上传jar的同时立马启动线程。

所以,权宜之计,将线程的注册,写到了static代码块中,等到发布的接口被第一次调用的时候,启动线程。

package com.yundasys.mobi.core.ydwechat.chain.common;

import com.yundasys.mobi.common.context.ComxContext;
import com.yundasys.mobi.common.logger.LoggerUtil;
import com.yundasys.mobi.common.logger.LoggerUtil.Level;
import com.yundasys.mobi.common.persist.ComxPagingDAO;
import com.yundasys.mobi.common.thread.ThreadManager;
import com.yundasys.mobi.core.ydwechat.thread.PushCacheMsgTask;
import com.yundasys.mobi.core.ydwechat.thread.PushWaybillTask;
import com.yundasys.mobi.core.ydwechat.thread.RemoveCacheMsgTask;
import com.yundasys.mobi.core.ydwechat.thread.UpdateAreaCodeTask;
import com.yundasys.mobi.core.ydwechat.thread.GetOpenBranchTask;
import com.yundasys.mobi.util.ydwechat.YdUtil;
import com.yundasys.yml.model.IHandler;

public class BaseHandler implements IHandler {
    protected static ComxPagingDAO cadao = ComxContext.getContext().getCadao();
    protected static ComxPagingDAO branchdao = ComxContext.getContext().getDao("branchdao");
    /**
     * 服务器启动时候的后台托管线程
     */
    static {
        LoggerUtil.log(Level.INFO, YdUtil.refresh(cadao, branchdao));
        // 推送物流信息的线程
        ThreadManager.registerRunnable(new PushWaybillTask());

        // 推送用户接收失败(连续48小时离线)的客服消息
        ThreadManager.registerRunnable(new PushCacheMsgTask());

        // 移除过期的推送失败消息缓存
        ThreadManager.registerRunnable(new RemoveCacheMsgTask());

        // 定时更新地址库
        ThreadManager.registerRunnable(new UpdateAreaCodeTask());
        
        // 定时更新开放网点
        ThreadManager.registerRunnable(new GetOpenBranchTask());

    }
}

这里也可以看出动态加载数据源的方式。


3性能测试

因为利用自定义classLoader可能会加载不少类,在后台管理jar的时候,因为更新jar的时候,可能会产生一些垃圾的class,需要进行性能测试,查看内存和线程的情况。

1)经过几个版本的更新,通过自定义线程池的方式,基本上自定义的线程没有重复出现,但是log4j的线程却因为weblogic的连续部署产生了,暂时不知道怎么解决。

2)对于垃圾的class,gc的效果还是比较理想的,而且值得一提的是,因为考虑到了提交的jar的不可运行性,如果jar中的yml配置有错,将仍然使用上次内存中的正确的jar,不会使用本次的jar,基本到达了事务的要求。

3)因为对yml文件进行了缓存处理,通过刷新yml缓存,来刷新接口,可以很方便的增删改接口,而且因为多是在后台管理界面上传的时候进行加载,虽然大量利用反射,性能的损失并不明显。

关于性能测试,可以参考 http://blog.csdn.net/namesliu/article/details/5830822


顺便上传了ymlchain的实现,有源代码和测试类,希望可以和大家一起多多交流。


猜你喜欢

转载自blog.csdn.net/u014112608/article/details/34541339
今日推荐