精华内容
下载资源
问答
  • Tomcat实现部署

    2021-01-10 06:50:48
    热部署概念 热部署是指在你对JSP或JAVA类进行了修改在不重启WEB服务器前提下能让修改生效,...-- 实现tomcat热部署和自定义ContextPath--> docBase: webapps下的你项目的包名 path:项目访问路径 reloadable: 是
  • 主要介绍了Tomcat 部署的实现原理详解的相关资料,需要的朋友可以参考下
  • 实现Tomcat热更新部署

    千次阅读 2017-08-08 15:41:23
    插件式的架构可以为系统带来极高的扩展性。...关于ClassLoader的更详尽的讲解可以参考这篇博文: 深入分析Java ClassLoader原理 动态加载类文件需要使用URLClassLoader类的loadClass方法,其原型为: public Class

    插件式的架构可以为系统带来极高的扩展性。典型的一个例子就是eclipse。我们可以下载各种各样的插件来不断丰富eclipse的功能,而eclipse本身却不需要作任何改动。那么在Java中如何实现插件机制呢?

    动态加载

    实现插件式系统的基础是动态加载机制。所谓动态加载是指系统所要用到的字节码文件不需要添加classpath目录下,而在运行时由程序本身根据需要加载到jvm中。这种情况下相应的jar包可以放在任意位置,甚至从网络上获取。jdk中的ClassLoader类为我们提供了这一强大的特性。我们可以自定义一个子类来继承ClassLoader类,从而实现一些自定义的需求,但不要轻易就重写ClassLoader的方法,除非你对ClassLoader非常熟悉,并且有非这样做不可的需求。在本文,我们可以直接使用了jdk自带的URLClassLoader类来实现插件的动态加载。

    URLClassLoader的构造函数原型为:

    public URLClassLoader(URL[] urls, ClassLoader parent)
    1
    1
    第一个参数为class文件或jar包的URL列表,大致可以理解为class文件的地址,该地址可以是本地磁盘地址也可以是网络地址。第二个参数为父级ClassLoader,该参数我们一般都赋值为调用者本身的ClassLoader。此处需要非常注意的是java中的类都是由classloader加载的,如果同一个class文件由不同的classloader加载,则被认为是两个class类型,他们的实例间也不能强制转换。关于ClassLoader的更详尽的讲解可以参考这篇博文: 深入分析Java ClassLoader原理

    动态加载类文件需要使用URLClassLoader类的loadClass方法,其原型为:

    public Class

    展开全文
  • Tomcat热加载原理剖析

    2021-05-13 15:50:33
    二、加载原理剖析 开启加载在<Context标签中配置reloadable="true" 1、StandardEngine容器启动startInternal(),内部会调用父类ContainerBase的startInternal() protected synchronized void startInternal...

    一、热加载时序图

    在这里插入图片描述

    二、热加载原理剖析

    开启热加载在<Context标签中配置reloadable="true"
    

    1、StandardEngine容器启动startInternal(),内部会调用父类ContainerBase的startInternal()

        protected synchronized void startInternal() throws LifecycleException {
    
            // Log our server identification information
            if(log.isInfoEnabled())
                log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
    
            // Standard container startup
            // 调用ContainerBase的startInternal,以异步的方式启动子容器
            super.startInternal();
        }
    

    2、进入ContainerBase startInternal(),注意方法最后threadStart()中会开启一个后台线程

     protected synchronized void startInternal() throws LifecycleException {
    
            // Start our subordinate components, if any
            // 启动下级组件,如果有的话
    
            // 容器的类加载器
            Loader loader = getLoaderInternal();
            if ((loader != null) && (loader instanceof Lifecycle))
                ((Lifecycle) loader).start();
    
           /*此处移除部分代码
           */
            //开启管道、也是层层调用开启
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).start();
            }
    
            // 这个时候会触发START_EVENT事件,会进行deployApps
            setState(LifecycleState.STARTING);
            // Start our thread
            // Engine容器启动一个background线程
            threadStart();
        }
    

    3、进入threadStart()

        protected void threadStart() {
    
            if (thread != null)
                return;
    
    //        System.out.println(this.getInfo() + "的backgroundProcessorDelay等于=" + backgroundProcessorDelay);
            // 默认情况下只有Engine的backgroundProcessorDelay大于0,为10,
            // 也就是说,虽然每个容器在启动的时候都会走到当前方法,但是只有Engine能继续往下面去执行
            // 但是其他容器是可以配置backgroundProcessorDelay属性的,只要配置了大于0,那么这个容器也会单独开启一个backgroundProcessor线程
    
            if (backgroundProcessorDelay <= 0)
                return;
    
            threadDone = false;
            String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
            // ContainerBackgroundProcessor线程每隔一段时间会调用容器内的backgroundProcess方法,并且会调用子容器的backgroundProcess方法
            thread = new Thread(new ContainerBackgroundProcessor(), threadName);
            thread.setDaemon(true);
            thread.start();
    
        }
    

    4、进入后台ContainerBackgroundProcessor线程中
    threadDone为false,这个后台线程一直会运行,主要作用遍历StandardEngine容器中子容器并调用其backgroundProcess()

            public void run() {
                Throwable t = null;
                String unexpectedDeathMessage = sm.getString(
                        "containerBase.backgroundProcess.unexpectedThreadDeath",
                        Thread.currentThread().getName());
                try {
                    while (!threadDone) {
                        try {
                            Thread.sleep(backgroundProcessorDelay * 1000L);
                        } catch (InterruptedException e) {
                            // Ignore
                        }
                        if (!threadDone) {
                            // 获取当前的容器
                            Container parent = (Container) getMappingObject();
                            ClassLoader cl =
                                Thread.currentThread().getContextClassLoader();
    //                        System.out.println("ContainerBackgroundProcessor在运行"+ parent.getName());
                            if (parent.getLoader() != null) {
                                System.out.println(parent.getName() + "有loader");
                                cl = parent.getLoader().getClassLoader();
                            }
                            // 执行子容器的background
                            processChildren(parent, cl);
                        }
                    }
    

    5、进入processChildren(),此方法是父类ContainerBase的即每个容器都会走,每个容器都会调用backgroundProcess(),大致流程engine、host、context、wrapper,总之最终会走到context的backgroundProcess

            protected void processChildren(Container container, ClassLoader cl) {
                try {
                    if (container.getLoader() != null) {
                        Thread.currentThread().setContextClassLoader
                            (container.getLoader().getClassLoader());
                    }
                    //
                    container.backgroundProcess();
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error("Exception invoking periodic operation: ", t);
                } finally {
                    Thread.currentThread().setContextClassLoader(cl);
                }
                Container[] children = container.findChildren();
                for (int i = 0; i < children.length; i++) {
                    if (children[i].getBackgroundProcessorDelay() <= 0) {
                        // 调用子容器的backgroundProcess方法, delay小于0才调用,如果大于0,则该容器会有自己单独的background线程
                        processChildren(children[i], cl);
                    }
                }
            }
        }
    

    6、进入StandardContext的backgroundProcess(),此方法还是会调用父类ContainerBase的backgroundProcess()

    public void backgroundProcess() {
    		//省去多余代码,获取context中webapploader
            // 热加载
                 // 热加载
            Loader loader = getLoaderInternal();  // Context.webapploader
            if (loader != null) {
                try {
                    loader.backgroundProcess();
                } catch (Exception e) {
                    log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
                }
            }
        }
    

    7、进入webapploader的backgroundProcess(),可以看到会重新加载Context

        public void backgroundProcess() {
            if (reloadable && modified()) {
                System.out.println(container.getInfo()+"触发了热加载");
                try {
                    Thread.currentThread().setContextClassLoader
                        (WebappLoader.class.getClassLoader());
                    if (container instanceof StandardContext) {
                    	//重新reload
                        ((StandardContext) container).reload();
                    }
                } finally {
                    if (container.getLoader() != null) {
                        Thread.currentThread().setContextClassLoader
                            (container.getLoader().getClassLoader());
                    }
                }
            } else {
                closeJARs(false);
            }
        }
    
    8、进入StandardContext的reload()、其实主要做了如下四步
    
    public synchronized void reload() {
    
    
            // Stop accepting requests temporarily.
            setPaused(true);//
           
                stop();
    
       
                start();
    
    
            setPaused(false);
    
    
    
        }
    

    9、进入Context的start()中也就进入类似重新开启context,重新加载context中的类,最终会调用WebAppClassLoader的loadClass()
    主要几个步骤:
    1、先去tomcat自己维护的map中去找有没有加载
    2、再去jvm中找有没有加载
    3、先尝试通过系统类加载器去加载(主要是加载jdk核心类)
    4、然后根据delegateLoad,是否委托父类去加载,如果没有委托,自己先去加载、加载不到再让父类加载。如果委托,先让父类加载器加载、自己再加载,就是先后顺序

      public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
            synchronized (getClassLoadingLockInternal(name)) {
                if (log.isDebugEnabled())
                    log.debug("loadClass(" + name + ", " + resolve + ")");
                Class<?> clazz = null;
    
                // Log access to stopped classloader
                if (!started) {
                    try {
                        throw new IllegalStateException();
                    } catch (IllegalStateException e) {
                        log.info(sm.getString("webappClassLoader.stopped", name), e);
                    }
                }
    
                // (0) Check our previously loaded local class cache
                // 先检查该类是否已经被Webapp类加载器加载。
                clazz = findLoadedClass0(name); // map
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Returning class from cache");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
    
                // (0.1) Check our previously loaded class cache
                // 该方法直接调用findLoadedClass0本地方法,findLoadedClass0方法会检查JVM缓存中是否加载过此类
                clazz = findLoadedClass(name);   // jvm 内存
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Returning class from cache");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
    
                // (0.2) Try loading the class with the system class loader, to prevent
                //       the webapp from overriding J2SE classes
                // 尝试通过系统类加载器(AppClassLoader)加载类,防止webapp重写JDK中的类
                // 假设,webapp想自己去加载一个java.lang.String的类,这是不允许的,必须在这里进行预防。
                try {
                    clazz = j2seClassLoader.loadClass(name);    // java.lang.Object
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
    
                // (0.5) Permission to access this class when using a SecurityManager
                if (securityManager != null) {
                    int i = name.lastIndexOf('.');
                    if (i >= 0) {
                        try {
                            securityManager.checkPackageAccess(name.substring(0,i));
                        } catch (SecurityException se) {
                            String error = "Security Violation, attempt to use " +
                                "Restricted Class: " + name;
                            if (name.endsWith("BeanInfo")) {
                                // BZ 57906: suppress logging for calls from
                                // java.beans.Introspector.findExplicitBeanInfo()
                                log.debug(error, se);
                            } else {
                                log.info(error, se);
                            }
                            throw new ClassNotFoundException(error, se);
                        }
                    }
                }
    
                boolean delegateLoad = delegate || filter(name); // 委托--true
    
                // (1) Delegate to our parent if requested
                // 是否委派给父类去加载
                if (delegateLoad) {
                    if (log.isDebugEnabled())
                        log.debug("  Delegating to parent classloader1 " + parent);
                    try {
                        clazz = Class.forName(name, false, parent);
                        if (clazz != null) {
                            if (log.isDebugEnabled())
                                log.debug("  Loading class from parent");
                            if (resolve)
                                resolveClass(clazz);
                            return (clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
    
                // (2) Search local repositories
                // 从webapp应用内部进行加载
                if (log.isDebugEnabled())
                    log.debug("  Searching local repositories");
                try {
                    clazz = findClass(name);  // classes,lib
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from local repository");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
    
                // (3) Delegate to parent unconditionally
                // 如果webapp应用内部没有加载到类,那么无条件委托给父类进行加载
                if (!delegateLoad) {
                    if (log.isDebugEnabled())
                        log.debug("  Delegating to parent classloader at end: " + parent);
                    try {
                        clazz = Class.forName(name, false, parent);
                        if (clazz != null) {
                            if (log.isDebugEnabled())
                                log.debug("  Loading class from parent");
                            if (resolve)
                                resolveClass(clazz);
                            return (clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
            }
    
            throw new ClassNotFoundException(name);
        }
    
    展开全文
  • Tomcat热部署机制 对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都...

    Tomcat热部署机制

    对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序。

    我们知道,现在大多数的web服务器都支持热部署,而对于热部署的实现机制,网上讲的却不够完善,下面我们就Tomcat的热部署实现机制,讲解一下它是如何实现的:

    Tomcat的容器实现热部署使用了两种机制:

    Classloader重写,通过自定义classloader加载相应的jsp编译后的class到JVM中。 通过动态修改内存中的字节码,将修改过的class再次装载到JVM中。

    Classloader实现jsp的重新加载

    Tomcat通过org.apache.jasper.servlet.JasperLoader实现了对jsp的加载,下面做个测试: 1. 新建一个web工程,并编写一个jsp页面,在jsp页面中输出该页面的classloader,<%System.out.print(this.getClass().getClassLoader());%>.

    2. 启动web服务器,打开jsp页面,我们可以看到后台输出,该jsp的classloader是JasperLoader的一个实例。

    3. 修改jsp,保存并刷新jsp页面,再次查看后台输出,此classloader实例已经不是刚才那个了,也就是说tomcat通过一个新的classloader再次装载了该jsp。

    4. 其实,对于每个jsp页面tomcat都使用了一个独立的classloader来装载,每次修改完jsp后,tomcat都将使用一个新的classloader来装载它。

    关于如何使用自定义classloader来装载一个class这里就不说了,相信网上都能找到,JSP属于一次性消费,每次调用容器将创建一个新的实例,属于用完就扔的那种,但是对于这种实现方式却很难用于其它情况下,如现在我们工程中很多都使用了单例,尤其是spring工程,在这种情况下使用新的classloader来加载修改后的类是不现实的,单例类将在内存中产生多个实例,而且这种方式无法改变当前内存中已有实例的行为,当然,tomcat也没通过该方式实现class文件的重新加载。

    通过代理修改内存中class的字节码

    Tomcat中的class文件是通过org.apache.catalina.loader. WebappClassLoader装载的,同样我们可以做个测试,测试过程与jsp测试类似,测试步骤就不说了,只说一下结果:

    在热部署的情况下,对于被该classloader 加载的class文件,它的classloader始终是同一个WebappClassLoader,除非容器重启了,相信做完这个实验你就不会再认为tomcat是使用一个新的classloader来加载修改过的class了,而且对于有状态的实例,之前该实例拥有的属性和状态都将保存,并在下次执行时拥有了新的class的逻辑,这就是热部署的神秘之处(其实每个实例只是保存了该实例的状态属性,我们通过序列化对象就能看到对象中包含的状态,最终的逻辑还是存在于class文件中)。

    下面的class重定义是通过:java.lang.instrument实现的,具体可参考相关文档。

    下面我们看一下如何通过代理修改内存中的class字节码:

    以下是一个简单的热部署代理实现类(代码比较粗糙,也没什么判断):

    复制代码
    package agent;
    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.Instrumentation;
    import java.util.Set;
    import java.util.Timer;
    import java.util.TreeSet;
    public  class  HotAgent {
     
        protected  static  Set<String>  clsnames=new TreeSet<String>();
     
        public  static  void  premain(String  agentArgs, Instrumentation  inst)  throws Exception {
            ClassFileTransformer  transformer =new ClassTransform(inst);
            inst.addTransformer(transformer);
            System.out.println("是否支持类的重定义:"+inst.isRedefineClassesSupported());
            Timer  timer=new  Timer();
            timer.schedule(new ReloadTask(inst),2000,2000);
        }
    }
    
    package agent;
    import java.lang.instrument.ClassFileTransformer;
    importjava.lang.instrument.IllegalClassFormatException;
    import java.lang.instrument.Instrumentation;
    import java.security.ProtectionDomain;
     
    public  class  ClassTransform.  implements ClassFileTransformer {
        private  Instrumentation  inst;
     
        protected  ClassTransform(Instrumentation  inst){
            this.inst=inst;
        }
     
        /**
         * 此方法在redefineClasses时或者初次加载时会调用,也就是说在class被再次加载时会被调用,
         * 并且我们通过此方法可以动态修改class字节码,实现类似代理之类的功能,具体方法可使用ASM或者javasist,
         * 如果对字节码很熟悉的话可以直接修改字节码。
         */
        public  byte[]  transform(ClassLoader  loader, String  className,
               Class<?>  classBeingRedefined, ProtectionDomain  protectionDomain,
               byte[]  classfileBuffer)throws IllegalClassFormatException {
            byte[]  transformed = null;
            HotAgent.clsnames.add(className);
            return  null;
        }
    }
    
    package agent;
    import java.io.InputStream;
    import java.lang.instrument.ClassDefinition;
    import java.lang.instrument.Instrumentation;
    import java.util.TimerTask;
     
    public  class  ReloadTask  extends  TimerTask {
        private  Instrumentation  inst;
     
        protected  ReloadTask(Instrumentation  inst){
            this.inst=inst;
        }
     
        @Override
        public  void  run() {
           try{
               ClassDefinition[]  cd=new ClassDefinition[1];
               Class[]  classes=inst.getAllLoadedClasses();
               for(Class  cls:classes){
                    if(cls.getClassLoader()==null||!cls.getClassLoader().getClass().getName().equals("sun.misc.Launcher$AppClassLoader"))
                        continue;
                    String  name=cls.getName().replaceAll("\\.","/");
                    cd[0]=new ClassDefinition(cls,loadClassBytes(cls,name+".class"));
                    inst.redefineClasses(cd);
               }
           }catch(Exception ex){
                ex.printStackTrace();
           }
        }
     
        private  byte[]  loadClassBytes(Class  cls,String  clsname) throws  Exception{
            System.out.println(clsname+":"+cls);
            InputStream  is=cls.getClassLoader().getSystemClassLoader().getResourceAsStream(clsname);
            if(is==null)return  null;
            byte[]  bt=new  byte[is.available()];
            is.read(bt);
            is.close();
            return  bt;
        }
    }
    复制代码

     

    以上是基本实现代码,需要组件为: 1.HotAgent(预加载) 2.ClassTransform(在加载class的时候可以修改class的字节码),本例中没用到 3.ReloadTask(class定时加载器,以上代码仅供参考) 4.META-INF/MANIFEST.MF内容为:(参数一:支持class重定义;参数二:预加载类)

    Can-Redefine-Classes: true Premain-Class: agent.HotAgent

    5.将以上组件打包成jar文件(到此,组件已经完成,下面为编写测试类文件)。 6.新建一个java工程,编写一个java逻辑类,并编写一个Test类,在该测试类中调用逻辑类的方法,下面看下测试类代码:

     

    复制代码
    package test.redefine;
     
    public  class  Bean1 {
        public  void  test1(){
          System.out.println("============================");
        }
    }
    
    package test.redefine;
     
    public  class  Test {
        public  static  void  main(String[] args)throws  InterruptedException {
     
           Bean1  c1=new  Bean1();
           while(true){
               c1.test1();
               Thread.sleep(5000);
           }
        }
    }
    复制代码

    运行测试类:

    java –javaagent:agent.jar test.redefine.Test

    在测试类中,我们使用了一个死循环,定时调用逻辑类的方法。我们可以修改Bean1中的方法实现,将在不同时间看到不同的输出结果,关于技术细节也没什么好讲的了,相信大家都能明白。

    Tomcat 热部署配置

     

    复制代码
    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">  
        <Context docBase="CPCWeb" path="/CPCWeb" reloadable="true" source="org.<span class="wp_keywordlink"><a href="http://res.importnew.com/eclipse" title="Eclipse ImportNew主页" target="_blank">Eclipse</a></span>.jst.j2ee.server:CPCWeb"/>
    </Host>
    
    
    
    复制代码

    autoDeploy=”true” — 自动部署 reloadable=”true” — 自动加载

     



    转载自:http://www.cnblogs.com/freemanabc/p/5618180.html

    展开全文
  • 要在运行的过程中升级Web应用,如果你不想重启系统,实现的方式有两种:加载和部署。 如何实现部署、加载? ...Tomcat实现加载、部署 Tomcat通过开启后台线程,使得各个层次的容器组件

    想实现不重启系统,而在系统运行过程中升级Web应用,有两种方案:

    • 热加载
    • 热部署

    实现原理

    跟类加载机制有关。

    热加载

    实现方式是Web容器启动一个后台线程,定期检测类文件变化。
    若有变化,就重新加载类,在这个过程中不会清空Session ,一般用在开发环境。

    热部署

    类似地,也由后台线程定时检测Web应用变化,但它会重新加载整个Web应用。这会清空Session,比热加载更干净、彻底,一般用在生产环境。

    Tomcat实现热加载、热部署

    Tomcat通过开启后台线程,使得各个层次的容器组件都有机会完成一些周期性任务。

    实际开发往往也需要执行一些周期性任务,比如监控程序周期性拉取系统健康状态。

    Tomcat后台线程

    开启后台线程做周期性任务,最常见的就是线程池的ScheduledThreadPoolExecutor,没错,Tomcat就是通过它来开启后台线程:

    backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor()
    			               .scheduleWithFixedDelay(
    			                // 要周期性执行的Runnable
    			                new ContainerBackgroundProcessor(),
    			                //第一次执行延迟多久
    			                backgroundProcessorDelay,
    			                // 之后每次执行间隔多久
    			                backgroundProcessorDelay,
    			                // 时间单位
    			                TimeUnit.SECONDS);
    

    任务类ContainerBackgroundProcessor是ContainerBase的内部类,ContainerBase是所有容器组件的基类。

    ContainerBackgroundProcessor

    protected class ContainerBackgroundProcessor implements Runnable {
    
        @Override
        public void run() {
            // 入参"宿主类"实例
            processChildren(ContainerBase.this);
        }
    
        protected void processChildren(Container container) {
            try {
                // 1. 调用当前容器的backgroundProcess
                container.backgroundProcess();
                
                // 2. 遍历所有子容器,递归调用processChildren
                // 这样当前容器的子孙都会被处理            
                Container[] children = container.findChildren();
                for (int i = 0; i < children.length; i++) {
    	            // 容器基类有个变量叫做backgroundProcessorDelay
    	            // 如果大于0,表明子容器有自己的后台线程
    	            // 无需父容器来调用它的processChildren方法
                    if (children[i].getBackgroundProcessorDelay() <= 0) {
                        processChildren(children[i]);
                    }
                }
            } catch (Throwable t) { ... }
    

    processChildren把“宿主类”,即ContainerBase的类实例当成参数传给了run方法。

    而在processChildren方法里,就做了两步:调用当前容器的backgroundProcess方法,以及递归调用子孙的backgroundProcess方法。请你注意backgroundProcess是Container接口中的方法,也就是说所有类型的容器都可以实现这个方法,在这个方法里完成需要周期性执行的任务。

    这样只需在顶层容器Engine中启动一个后台线程,则该线程不但会执行Engine容器的周期性任务,还会执行所有子容器的周期性任务。

    backgroundProcess方法

    上述代码都是在基类ContainerBase实现,具体容器类需要做什么呢?

    • 若有周期性任务,就实现backgroundProcess
    • 若没有,则复用基类ContainerBase的方法

    ContainerBase#backgroundProcess

    public void backgroundProcess() {
    
        // 1.执行容器中Cluster组件的周期性任务
        Cluster cluster = getClusterInternal();
        if (cluster != null) {
            cluster.backgroundProcess();
        }
        
        // 2.执行容器中Realm组件的周期性任务
        Realm realm = getRealmInternal();
        if (realm != null) {
            realm.backgroundProcess();
       }
       
       // 3.执行容器中Valve组件的周期性任务
        Valve current = pipeline.getFirst();
        while (current != null) {
           current.backgroundProcess();
           current = current.getNext();
        }
        
        // 4. 触发容器的"周期事件",Host容器的监听器HostConfig就靠它来调用
        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
    }
    

    不仅每个容器可以有周期性任务,每个容器中的其他通用组件,比如跟集群管理有关的Cluster组件、跟安全管理有关的Realm组件都可以有自己的周期性任务。

    容器之间的链式调用是通过Pipeline-Valve机制实现的,容器中的Valve也可以有周期性任务,并且被ContainerBase统一处理。

    在backgroundProcess方法的最后,还触发了容器的“周期事件”。“周期事件”是什么呢?
    它跟生命周期事件一样,是一种扩展机制,可以这样理解:
    又一段时间过去了,容器还活着,你想做点什么吗?
    如果你想做点什么,就创建一个监听器来监听这个“周期事件”,事件到了我负责调用你的方法。

    总之,有了ContainerBase中的后台线程和backgroundProcess方法,各种子容器和通用组件不需要各自弄一个后台线程来处理周期性任务。

    Tomcat热加载

    有了ContainerBase的周期性任务处理“框架”,具体容器子类只需实现自己的周期性任务。
    Tomcat的热加载,就实现在Context容器。

    Context#backgroundProcess

    StandardContext实现类中:

    @Override
    public void backgroundProcess() {
    
        if (!getState().isAvailable())
            return;
    
        // WebappLoader周期性检查
        // WEB-INF/classes、WEB-INF/lib 目录下的类文件是否有更新
        Loader loader = getLoader();
        if (loader != null) {
            loader.backgroundProcess();        
        }
        
        // Session管理器周期性检查是否有Session过期
        Manager manager = getManager();
        if (manager != null) {
            manager.backgroundProcess();
        }
        
        // 周期性检查静态资源是否有更新
        WebResourceRoot resources = getResources();
        if (resources != null) {
            resources.backgroundProcess();
        }
        super.backgroundProcess();
    }
    

    WebappLoader是如何实现热加载的呢?
    关键是调用Context#reload方法:

    • 停止和销毁Context容器及其所有子容器(Wrapper),即Wrapper里的Servlet实例也被销毁
    • 停止和销毁Context容器关联的Listener和Filter
    • 停止和销毁Context下的Pipeline和各种Valve
    • 停止和销毁Context的类加载器,以及类加载器加载的类文件资源

    启动Context容器,在这个过程中会重新创建前面四步被销毁的资源。
    在这个过程中,类加载器发挥着关键作用。一个Context容器对应一个类加载器,类加载器在销毁的过程中会把它加载的所有类也全部销毁。Context容器在启动过程中,会创建一个新的类加载器来加载新的类文件。

    在Context的reload方法里,并没有调用Session管理器的destroy方法,也就是说这个Context关联的Session是没有销毁的。

    Tomcat热加载默认是关闭的,需在conf目录下的context.xml文件中设置reloadable参数开启:

    <Context reloadable="true"/>
    

    Tomcat热部署

    跟热加载的本质区别是:
    热部署会重新部署Web应用,原Context对象会被整个被销毁,因此该Context所关联一切资源都会被销毁,包括Session。

    Tomcat热部署由哪个容器实现呢?
    不是由Context,因为热部署过程中Context容器被销毁了,所以就是Host,Context的父容器。

    Host容器并未在backgroundProcess中实现周期性检测,而是通过监听器HostConfig实现。
    HostConfig就是前面提到的“周期事件”的监听器,“周期事件”达到时,HostConfig会做什么呢?

    public void lifecycleEvent(LifecycleEvent event) {
        // 执行check
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } 
    }
    
    protected void check() {
    
        if (host.getAutoDeploy()) {
            // 检查这个Host下所有已经部署的Web应用
            DeployedApplication[] apps =
                deployed.values().toArray(new DeployedApplication[0]);
                
            for (int i = 0; i < apps.length; i++) {
                //检查Web应用目录是否有变化
                checkResources(apps[i], false);
            }
    
            // 执行部署
            deployApps();
        }
    }
    

    HostConfig会检查webapps目录下的所有Web应用:

    • 如果原来Web应用目录被删掉了,就把相应Context容器整个销毁掉
    • 是否有新的Web应用目录放进来了,或者有新的WAR包放进来了,就部署相应的Web应用

    因此HostConfig做的事情都是比较“宏观”的,它不会去检查具体类文件或者资源文件是否有变化,而是检查Web应用目录级别变化。

    展开全文
  • 部署:就是已经运行了项目,更改之后,不需要重新tomcat,但是会清空内存,重新打包,重新解压war包运行,可能好处是一个tomcat多个项目,不必因为tomcat停止而停止其他的项目。直接重新加载整个应用; 加载:是基于...
  • Tomcat热部署实现原理

    2019-10-09 03:25:20
    浅谈Tomcat热部署原理 tomcat的热部署实现原理:tomcat启动的时候会有启动一个线程每隔一段时间会去判断应用中加载的类是否发生变法(类总数的变化,类的修改),如果发生了变化就会把应用的启动的线程停止掉,清除...
  • 一篇文章彻底搞懂Tomcat热部署与热加载

    千次阅读 热门讨论 2020-10-21 15:41:11
    部署和加载是类似的,都是在不重启Tomcat的情况下,使得应用的最新代码生效。 部署表示重新部署应用,它的执行主体是Host,表示主机。 加载表示重新加载class,它的执行主体是Context,表示应用。 Tomcat中...
  • Tomcat中jsp部署实现原理

    千次阅读 2017-03-27 20:50:27
    转载只为学习!... 我们知道在开发工程的时候jsp文件是即修改即生效的,由于比较...Tomcat jsp部署的实现原理大体是这样的,每个JSP页面从上次访问到下次访问总是有默认几秒的缓存时间的,也就说并不是严格的即修改
  • tomcat热部署的实现原理

    千次阅读 2012-02-01 10:16:53
    对于Java应用程序来说,部署就是在运行时更新Java类文件。在基于Java的应用服务器实现部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持部署。类装入...
  • 部署与加载基本原理配置方式 基本原理 在应用运行的时候升级软件,无需重新启动的方式有两种,部署和加载。 它们之间的区别是: 部署方式: 部署在服务器运行时重新部署项目。 加载在运行时重新...
  • Tomcat部署和加载机制 说到tomcat部署和加载,首先要明确什么是部署?...部署:署原理类似,也是由后台线程定时检测 Web 应用的 变化,但它会重新加载整个 Web 应用。这种方式会...
  • Tomcat热部署原理

    2012-05-19 20:49:10
    对于Java应用程序来说,部署就是在运行时更新Java类文件。在基于Java的应用服务器实现部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持部署。类装入...
  • tomcat热启动热部署

    千次阅读 2018-11-26 19:22:03
    加载部署启动。。。。。 共分两类: A. tomcat 的conf/server.xml 中有一段是关于自动加载有变化的内容的 &lt;Context docBase="XXAPP" path="/XXAPP" reloadable="true&...
  • Tomcat热部署机制 对于Java应用程序来说,热部署就是在运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,...
  • 部署原理类似,也是由后台线程定时检测 Web 应用的变化,但它会重新加载整个 Web 应用。这种方式会清空 Session,比加载更加干净、彻底,一般用在生产环境。 tomcat定时任务是怎么实现的? tomcat加载和...
  • tomcat + spring mvc原理(六):tomcat WAR包的部署与加载前言监控的启动原理状态监听部署项目 前言     单独部署的tomcat服务器在运行中,当开发人员或者运维人员将开发工程的war包部署到服务...
  • Tomcat热部署和热加载的方法

    千次阅读 2018-10-18 15:00:35
    有两种方式部署 和加载: 加载 在server.xml -&gt; context 属性中 设置 reloadable=“true” 部署 在server.xml -&gt; context 属性中 设置 autoDeploy=“true” 同样可以: &lt;Host name...
  • 在前面的文章中,我们分析了 tomcat 类加载器的相关源码,也了解了 tomcat 支持类的加载,意味着 tomcat 要涉及类的重复卸装/装载过程,这个过程是比较敏感的,一旦处理不当,可能会引起内存泄露 卸载类 我们...
  • Tomcat 的使用及原理分析(IDEA版)

    千次阅读 2020-02-26 20:30:21
    本篇文章系统的介绍 Tomcat 的使用及原理分析,针对 IDEA 用户给出了 IDEA 整合 Tomcat 的实例,让用户可以从零开始使用 Tomcat 并且不至于一头雾水。包括: Tomcat简介 IDEA集成Tomcat Tomcat原理
  • tomcat部署实现原理:tomcat启动的时候会有启动一个线程每隔一段时间会去判断应用中加载的类是否发生变法(类总数的变化,类的修改),如果发生了变化就会把应用的启动的线程停止掉,清除引用,并且把加载该应用的...
  • Tomcat热部署与热加载

    2018-10-12 16:04:43
    (2)、实现原理 部署直接重新加载整个应用,这种方式会释放内存,比加载更加干净,但是它比加载更加的浪费时间。 加载在运行时重新加载class,主要使用的是类加载机制,在实现方式上就是在容器启动的时候起...
  • Tomcat热加载Jrebel

    2016-10-26 13:10:54
    Tomcat热加载
  • 使用Tomcat插件实现WEB项目热部署 Tomcat热部署可以通过网页操作进行,也可以通过指定插件...文章目录使用Tomcat插件实现WEB项目热部署何为Tomcat热部署配置tomcat-users.xml文件配置项目pom文件Tomcat热部署原理...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,346
精华内容 4,138
关键字:

tomcat热更新原理