<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    BinCat V5 - 支持SpringBoot應用

    BinCat V5 - 支持SpringBoot應用

    時至今日(2020年9月),SpringBoot因其配置非常簡單功能強大,已經成為了絕大部分微服務項目的首選架構,Servlet 3+的新特性也為SpringBoot的便捷配置提供了非常大的幫助。我們將使用BinCat V5啟動并運行一個用SpringBoot實現的Blog應用,從而來學習Servlet容器的工作原理。

    創建基于SpringBoot的javasec-blog項目

    首先我們在javaweb-sec項目下創建一個javasec-test的模塊(用于存儲javasec文章所用到的測試項目),然后我們在javasec-test模塊中創建一個javasec-blog模塊(一個標準的SpringBoot項目),javasec-blog項目是一個用于演示的博客項目。

    image-20200917110907091

    javasec-blog War項目構建

    SpringBoot不但支持嵌入式部署也支持傳統的war包部署方式,但需要注意的是war包部署的時候需要做一些特殊的修改。BinCat目前只實現了基于war部署的方式,所以我們需要將javasec-blog打成一個war包。

    構建項目的時候可參考如下步驟(1-3步默認已修改,不需要關注):

    1. 修改pom.xml添加<packaging>war</packaging>,默認是jar

    2. 修改pom.xmlbuildplugins標簽,添加:

      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-war-plugin</artifactId>
          <version>${maven-deploy-plugin.version}</version>
          <configuration>
              <failOnMissingWebXml>false</failOnMissingWebXml>
          </configuration>
      </plugin>
    3. 修改SpringBoot啟動類代碼,示例中是:JavaWebBlogApplication。繼承org.springframework.boot.web.servlet.support.SpringBootServletInitializer,然后重寫configure方法。如下:

      package com.anbai.sec.blog.config;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.boot.builder.SpringApplicationBuilder;
      import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
      
      /**
       * @author yz
       */
      @SpringBootApplication(scanBasePackages = "com.anbai.sec.blog.*")
      public class JavaWebBlogApplication extends SpringBootServletInitializer {
      
         public static void main(String[] args) {
            SpringApplication.run(JavaWebBlogApplication.class, args);
         }
      
         @Override
         protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(JavaWebBlogApplication.class);
         }
      
      }
    4. 修改javaweb-sec-source/javasec-test/javasec-blog/src/main/resources/application.properties配置文件中的數據庫信息。

    5. 新建Mysql數據庫javaweb-blog,并導入javaweb-sec-source/javasec-test/javaweb-blog.sql

    6. 使用maven命令構建整個javaweb-sec項目(第二次構建的時候可以單獨build blog項目),在javaweb-sec根目錄執行:mvn clean install

    項目構建成功后結構如下:

    image-20200917143151435

    BinCat V5實現

    因為V5要實現正常的運行一個SpringBoot項目,所以我們需要寫一個支持單應用的Servlet容器,而且還需要實現之前版本未實現的其他Servlet接口。

    BinCatWebAppClassLoader實現

    為了實現加載Web應用的資源和類文件,我們需要實現一個類簡單的加載器,用于加載WEB-INF目錄下的classeslibs中的所有文件。

    BinCatWebAppClassLoader代碼:

    package com.anbai.sec.server.loader;
    
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class BinCatWebAppClassLoader extends URLClassLoader {
    
        public BinCatWebAppClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent);
        }
    
    }

    為了統一加載Web應用的資源文件,我們還需要將BinCatWebAppClassLoader的類示例設置為當前線程的類加載器,如下:

    // 定義需要加載的Web應用路徑,默認配置的路徑必須先使用maven編譯:javasec-blog項目
    String webAppFile = System.getProperty("user.dir") + 
      "/javaweb-sec-source/javasec-test/javasec-blog/target/javasec-blog-1.0.0/";
    
    // 創建BinCatWebAppClassLoader,加載Web應用
    BinCatWebAppClassLoader appClassLoader = BinCatConfig.createAppClassLoader(webAppFile);
    
    // 設置當前線程的上下文類加載器
    Thread.currentThread().setContextClassLoader(appClassLoader);

    這樣一來在整個Web應用(示例中指的是javasec-blog項目)默認將會使用BinCatWebAppClassLoader來實現資源文件和類文件的加載了。

    BinCatWebAppClassLoader初始化

    BinCatWebAppClassLoader在初始化的時候需要加載所有應用的WEB-INF目錄的類和資源文件,但由于V5版本我們只想實現單應用的部署,所以我們只需要加載我們預設好的Web應用目錄就好了。在初始化BinCatWebAppClassLoader的時候將WEB-INF/classes目錄和WEB-INF/libs目錄下的所有jar文件都添加到BinCatWebAppClassLoader當中。

    BinCatWebAppClassLoader創建并加載Web應用資源代碼:

    public static BinCatWebAppClassLoader createAppClassLoader(String webAppFile) throws IOException {
       File     webRoot      = new File(webAppFile);
       File     webInfoDir   = new File(webRoot, "WEB-INF");
       File     libDir       = new File(webInfoDir, "lib");
       File     classesDir   = new File(webInfoDir, "classes");
       Set<URL> classPathURL = new HashSet<>();
    
       File[] libs = libDir.listFiles(new FilenameFilter() {
          @Override
          public boolean accept(File dir, String name) {
             return name.endsWith(".jar");
          }
       });
    
       // 加載lib目錄下所有的jar文件
       for (File lib : libs) {
          classPathURL.add(lib.toURL());
       }
    
       // 加載classes目錄的所有資源文件
       classPathURL.add(classesDir.toURL());
    
       // 創建Web應用的類加載器
       return new BinCatWebAppClassLoader(
             classPathURL.toArray(new URL[classPathURL.size()]), BinCatConfig.class.getClassLoader()
       );
    }

    BinCatServletContext實現

    V4版本中我們雖然已經實現了一個BinCatServletContext,但是我們并沒有實現Servlet的動態注冊功能,而且V4實現的Servlet注冊和初始化過程都是靜態的,我們需要將整個過程升級為動態的。

    1. 為了能夠在BinCatServletContext中獲取到我們自定義的Web應用的類加載器,我們需要在創建BinCatServletContext的時候將將其緩存到類對象中。
    2. 創建一個Map<String, Servlet> servletMapSet<BinCatServletRegistrationDynamic> registrationDynamics對象用于緩存動態注冊的Servlet
    3. 創建一個Map<String, String> initParameterMap對象用于記錄ServletContext初始化參數。
    4. 創建一個Map<String, Object> attributeMap,用于記錄ServletContext中的屬性對象(attribute)。

    BinCatServletContext代碼:

    package com.anbai.sec.server.servlet;
    
    import com.anbai.sec.server.loader.BinCatWebAppClassLoader;
    
    import javax.servlet.*;
    import javax.servlet.descriptor.JspConfigDescriptor;
    import java.io.File;
    import java.io.InputStream;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.*;
    
    public class BinCatServletContext implements ServletContext {
    
        // 創建一個裝動態注冊的Servlet的Map
        private final Map<String, Servlet> servletMap = new HashMap<>();
    
        // 創建一個裝ServletContext初始化參數的Map
        private final Map<String, String> initParameterMap = new HashMap<>();
    
        // 創建一個裝ServletContext屬性對象的Map
        private final Map<String, Object> attributeMap = new HashMap<>();
    
        // 創建一個裝Servlet動態注冊的Set
        private final Set<BinCatServletRegistrationDynamic> registrationDynamics = new LinkedHashSet<>();
    
        // BinCatWebAppClassLoader,Web應用的類加載器
        private final BinCatWebAppClassLoader appClassLoader;
    
      // 此處省略ServletContext接口中的大部分方法,僅保留幾個實現了的示例方法...
    
        public BinCatServletContext(BinCatWebAppClassLoader appClassLoader) throws Exception {
            this.appClassLoader = appClassLoader;
        }
    
        @Override
        public Servlet getServlet(String name) throws ServletException {
            return servletMap.get(name);
        }
    
        @Override
        public Enumeration<Servlet> getServlets() {
            Set<Servlet> servlets = new HashSet<Servlet>(servletMap.values());
            return Collections.enumeration(servlets);
        }
    
        @Override
        public Enumeration<String> getServletNames() {
            Set<String> servlets = new HashSet<String>(servletMap.keySet());
            return Collections.enumeration(servlets);
        }
    
        @Override
        public String getRealPath(String path) {
            return new File(System.getProperty("user.dir"), path).getAbsolutePath();
        }
    
        public Map<String, String> getInitParameterMap() {
            return initParameterMap;
        }
    
        @Override
        public String getInitParameter(String name) {
            return initParameterMap.get(name);
        }
    
        @Override
        public Enumeration<String> getInitParameterNames() {
            return Collections.enumeration(initParameterMap.keySet());
        }
    
        @Override
        public boolean setInitParameter(String name, String value) {
            if (!initParameterMap.containsKey(name)) {
                initParameterMap.put(name, value);
    
                return true;
            }
    
            return false;
        }
    
        @Override
        public Object getAttribute(String name) {
            return attributeMap.get(name);
        }
    
        @Override
        public Enumeration<String> getAttributeNames() {
            return Collections.enumeration(attributeMap.keySet());
        }
    
        @Override
        public void setAttribute(String name, Object object) {
            attributeMap.put(name, object);
        }
    
        @Override
        public void removeAttribute(String name) {
            attributeMap.remove(name);
        }
    
        @Override
        public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
            servletMap.put(servletName, servlet);
    
            BinCatServletRegistrationDynamic dynamic = new BinCatServletRegistrationDynamic(servletName, servlet, this);
            registrationDynamics.add(dynamic);
    
            return dynamic;
        }
    
        @Override
        public ClassLoader getClassLoader() {
            return this.appClassLoader;
        }
    
        public Map<String, Servlet> getServletMap() {
            return servletMap;
        }
    
        public Set<BinCatServletRegistrationDynamic> getRegistrationDynamics() {
            return registrationDynamics;
        }
    
    }

    BinCatServletContext初始化

    在初始化BinCatServletContext時我們通過手動注冊Servlet的方式初始化了幾個容器內置的Servlet,并通過掃描初始化Servlet類注解的方式將ServletBinCatServletContext中注冊。

    BinCatServletContext初始化代碼:

    /**
        * 手動注冊Servlet并創建BinCatServletContext對象
        *
        * @param appClassLoader 應用的類加載器
        * @return ServletContext Servlet上下文對象
        */
       public static BinCatServletContext createServletContext(BinCatWebAppClassLoader appClassLoader) throws Exception {
          BinCatServletContext servletContext = new BinCatServletContext(appClassLoader);
    
          // 手動注冊Servlet類
          Class<Servlet>[] servletClass = new Class[]{
                TestServlet.class,
                CMDServlet.class,
                QuercusPHPServlet.class
          };
    
          for (Class<Servlet> clazz : servletClass) {
             Servlet    servlet    = clazz.newInstance();
             WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
    
             if (webServlet != null) {
                // 獲取WebInitParam配置
                WebInitParam[] webInitParam = webServlet.initParams();
    
                // 動態創建Servlet對象
                ServletRegistration.Dynamic dynamic = servletContext.addServlet(webServlet.name(), servlet);
    
                // 動態設置Servlet映射地址
                dynamic.addMapping(webServlet.urlPatterns());
    
                // 設置Servlet啟動參數
                for (WebInitParam initParam : webInitParam) {
                   dynamic.setInitParameter(initParam.name(), initParam.value());
                }
             }
          }
    
          // 創建ServletContext
          return servletContext;
       }

    BinCatServletRegistrationDynamic實現

    BinCatServletContext中核心的是addServlet方法的動態注冊Servlet,如下:

    @Override
    public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
      servletMap.put(servletName, servlet);
    
      BinCatServletRegistrationDynamic dynamic = new BinCatServletRegistrationDynamic(servletName, servlet, this);
      registrationDynamics.add(dynamic);
    
      return dynamic;
    }

    Web應用調用addServlet注冊一個Servlet對象時會返回一個ServletRegistration.Dynamic對象,通過這個Dynamic對象可以設置Servlet的映射信息和初始化信息。但因為ServletRegistration.Dynamic是一個接口,所以我們必須實現此接口的所有方法。

    BinCatServletRegistrationDynamic代碼:

    package com.anbai.sec.server.servlet;
    
    import javax.servlet.MultipartConfigElement;
    import javax.servlet.Servlet;
    import javax.servlet.ServletRegistration;
    import javax.servlet.ServletSecurityElement;
    import java.util.*;
    
    public class BinCatServletRegistrationDynamic implements ServletRegistration.Dynamic {
    
        private final String servletName;
    
        private final Set<String> servletMapping = new LinkedHashSet<>();
    
        private final Map<String, String> initParametersMap = new HashMap<>();
    
        private final Servlet servlet;
    
        private final BinCatServletContext servletContext;
    
        public BinCatServletRegistrationDynamic(String servletName, Servlet servlet, BinCatServletContext servletContext) {
            this.servletName = servletName;
            this.servlet = servlet;
            this.servletContext = servletContext;
        }
    
        @Override
        public void setLoadOnStartup(int loadOnStartup) {
    
        }
    
        @Override
        public Set<String> setServletSecurity(ServletSecurityElement constraint) {
            return null;
        }
    
        @Override
        public void setMultipartConfig(MultipartConfigElement multipartConfig) {
    
        }
    
        @Override
        public void setRunAsRole(String roleName) {
    
        }
    
        @Override
        public void setAsyncSupported(boolean isAsyncSupported) {
    
        }
    
        @Override
        public Set<String> addMapping(String... urlPatterns) {
            Collections.addAll(servletMapping, urlPatterns);
    
            return servletMapping;
        }
    
        @Override
        public Collection<String> getMappings() {
            return servletMapping;
        }
    
        @Override
        public String getRunAsRole() {
            return null;
        }
    
        @Override
        public String getName() {
            return servletName;
        }
    
        @Override
        public String getClassName() {
            return servlet.getClass().getName();
        }
    
        @Override
        public boolean setInitParameter(String name, String value) {
            if (!initParametersMap.containsKey(name)) {
                initParametersMap.put(name, value);
    
                return true;
            }
    
            return false;
        }
    
        @Override
        public String getInitParameter(String name) {
            return initParametersMap.get(name);
        }
    
        @Override
        public Set<String> setInitParameters(Map<String, String> initParameters) {
            initParametersMap.putAll(initParameters);
    
            return initParametersMap.keySet();
        }
    
        @Override
        public Map<String, String> getInitParameters() {
            return initParametersMap;
        }
    
        public Servlet getServlet() {
            return servlet;
        }
    
        public String getServletName() {
            return servletName;
        }
    
    }

    實現Web應用的啟動

    在啟動我們的示例應用之前我們已經創建了一個BinCatWebAppClassLoaderBinCatServletContext,其中BinCatWebAppClassLoader已經加載了javasec-blog項目的WEB-INF信息、BinCatServletContext也初始化了幾個BinCat內置的Servlet。萬事俱備,我們現在只欠如何啟動Servlet容器了。

    Servlet3.0開始,Servlet除了可以從web.xml啟動,還可以通過SPI機制動態獲取ServletContainerInitializer(簡稱SCI)并通過SCI啟動Servlet容器。因為V5并未打算實現傳統的web.xml啟動方式,而是只想實現SCI啟動方式(這也是SpringBootwar包部署的啟動方式)。

    ServletContainerInitializer是一個接口類,僅定義了一個叫onStartup的方法,所有實現了該接口的類只要重寫onStartup方法將可以實現Servlet容器的啟動。

    ServletContainerInitializer代碼:

    package javax.servlet;
    
    import java.util.Set;
    
    /**
     * Interface which allows a library/runtime to be notified of a web
     * application's startup phase and perform any required programmatic
     * registration of servlets, filters, and listeners in response to it.
     *
     * <p>Implementations of this interface may be annotated with
     * {@link javax.servlet.annotation.HandlesTypes HandlesTypes}, in order to
     * receive (at their {@link #onStartup} method) the Set of application
     * classes that implement, extend, or have been annotated with the class
     * types specified by the annotation.
     * 
     * <p>If an implementation of this interface does not use <tt>HandlesTypes</tt>
     * annotation, or none of the application classes match the ones specified
     * by the annotation, the container must pass a <tt>null</tt> Set of classes
     * to {@link #onStartup}.
     *
     * <p>When examining the classes of an application to see if they match
     * any of the criteria specified by the <tt>HandlesTypes</tt> annontation
     * of a <tt>ServletContainerInitializer</tt>, the container may run into
     * classloading problems if any of the application's optional JAR
     * files are missing. Because the container is not in a position to decide
     * whether these types of classloading failures will prevent
     * the application from working correctly, it must ignore them,
     * while at the same time providing a configuration option that would
     * log them. 
     *
     * <p>Implementations of this interface must be declared by a JAR file
     * resource located inside the <tt>META-INF/services</tt> directory and
     * named for the fully qualified class name of this interface, and will be 
     * discovered using the runtime's service provider lookup mechanism
     * or a container specific mechanism that is semantically equivalent to
     * it. In either case, <tt>ServletContainerInitializer</tt> services from web
     * fragment JAR files excluded from an absolute ordering must be ignored,
     * and the order in which these services are discovered must follow the
     * application's classloading delegation model.
     *
     * @see javax.servlet.annotation.HandlesTypes
     *
     * @since Servlet 3.0
     */
    public interface ServletContainerInitializer {
    
        /**
         * Notifies this <tt>ServletContainerInitializer</tt> of the startup
         * of the application represented by the given <tt>ServletContext</tt>.
         *
         * <p>If this <tt>ServletContainerInitializer</tt> is bundled in a JAR
         * file inside the <tt>WEB-INF/lib</tt> directory of an application,
         * its <tt>onStartup</tt> method will be invoked only once during the
         * startup of the bundling application. If this
         * <tt>ServletContainerInitializer</tt> is bundled inside a JAR file
         * outside of any <tt>WEB-INF/lib</tt> directory, but still
         * discoverable as described above, its <tt>onStartup</tt> method
         * will be invoked every time an application is started.
         *
         * @param c the Set of application classes that extend, implement, or
         * have been annotated with the class types specified by the 
         * {@link javax.servlet.annotation.HandlesTypes HandlesTypes} annotation,
         * or <tt>null</tt> if there are no matches, or this
         * <tt>ServletContainerInitializer</tt> has not been annotated with
         * <tt>HandlesTypes</tt>
         *
         * @param ctx the <tt>ServletContext</tt> of the web application that
         * is being started and in which the classes contained in <tt>c</tt>
         * were found
         *
         * @throws ServletException if an error has occurred
         */
        public void onStartup(Set<Class<?>> c, ServletContext ctx)
            throws ServletException; 
    }

    因為BinCatWebAppClassLoader已經加載了示例項目的所有資源文件,所以我們可以使用BinCatWebAppClassLoader來獲取到示例項目中的所有路徑為:META-INF/services/javax.servlet.ServletContainerInitializer的資源文件。

    BinCatWebAppClassLoader classLoader    = (BinCatWebAppClassLoader) servletContext.getClassLoader();
    String                  servletService = "META-INF/services/javax.servlet.ServletContainerInitializer";
    
    // 獲取當前ClassLoader中的所有ServletContainerInitializer配置
    Enumeration<URL> resources = classLoader.getResources(servletService);

    SCI資源文件中配置的是實現了SCI接口的Java類,一個SCI資源文件可能會配置多個實現類(多個類以換行分割)。讀取到SCI資源文件后我們將可以使用反射的方式去加載資源文件中配置的Java類了。

    讀取SCI配置并加載SCI實現類示例:

    image-20200917162508462

    上圖示例中可以看到我們通過讀取BinCatWebAppClassLoader中的SCI資源時獲取到了一個類名為:org.springframework.web.SpringServletContainerInitializer的類。SpringServletContainerInitializer實現了ServletContainerInitializer,所以想要啟動示例的SpringBoot項目僅需要調用該類的onStartup方法將可以了。onStartup方法需要兩個參數:Set<Class<?>> cServletContext ctx,即可SCI啟動類對象集合Servlet上下文。因為ServletContext我們可以輕易的獲取到,所以我們重點是如何獲取到SCI啟動類對象集合了。

    通過閱讀源碼不難發現,在實現了SCI的接口類類名上會有一個叫做@HandlesTypes的注解,讀取到這個注解的值將可以找到處理SCI啟動類對象集合的類對象,然后反向去找該類對象的實現類將可以找到所有需要被SCI啟動的類數組了。

    SpringServletContainerInitializer示例:

    image-20200917164429015

    創建SCI實現類示例和獲取該示例的HandlesTypes配置方式如下:

    Class<?>    initClass                        = Class.forName(className, true, classLoader);
    HandlesTypes    handlesTypes             = initClass.getAnnotation(HandlesTypes.class);
    ServletContainerInitializer sci = (ServletContainerInitializer) initClass.newInstance();
    
    sciClassMap.put(sci, new HashSet<Class<?>>());
    
    if (handlesTypes != null) {
      Class[] handlesClass = handlesTypes.value();
    
      handlesTypesMap.put(sci, handlesClass);
    }

    獲取到@HandlesTypes配置的類名后剩下的就是如何在示例項目中的所有類中找出@HandlesTypes配置的類實例了,如上圖中SpringServletContainerInitializer配置的@HandlesTypesWebApplicationInitializer,那么我們現在就必須通過掃包和類文件的方式找出所有WebApplicationInitializer的子類,然后再通過上一步創建出來的SCI實例調用onStartup方法完成Servlet容器啟動。

    為了掃描當前類加載所有的類對象,我們需要先獲取出當前類加載加載的所有的類名稱,然后再依次掃描這些類是否是@HandlesTypes中配置的類(如:WebApplicationInitializer)的子類。

    獲取BinCatWebAppClassLoader加載的所有的類名代碼:

    /**
     * 獲取BinCatWebAppClassLoader類加載器加載的所有class類名
     *
     * @param classLoader     類加載器
     * @param sciClassMap     SCI類對象
     * @param handlesTypesMap SCI類對象配置的HandlesTypes對象映射Map
     * @return
     * @throws Exception
     */
    private static void findInitializerClass(
      BinCatWebAppClassLoader classLoader,
      Map<ServletContainerInitializer, Set<Class<?>>> sciClassMap,
      Map<ServletContainerInitializer, Class<?>[]> handlesTypesMap) throws Exception {
    
      // 創建一個存儲所有被BinCatWebAppClassLoader加載的類名稱對象
      Set<String> classList = new HashSet<>();
    
      // 獲取BinCatWebAppClassLoader加載的所有URL地址
      URL[] urls = classLoader.getURLs();
    
      for (URL url : urls) {
        File file = new File(url.toURI());
    
        // 遍歷所有的jar文件
        if (file.isFile() && file.getName().endsWith(".jar")) {
          JarFile               jarFile  = new JarFile(file);
          Enumeration<JarEntry> jarEntry = jarFile.entries();
    
          while (jarEntry.hasMoreElements()) {
            JarEntry entry    = jarEntry.nextElement();
            String   fileName = entry.getName();
    
            // 遍歷jar文件中的所有class文件,并轉換成java類名格式,如com/anbai/Test.class會轉換成com.anbai.Test
            if (fileName.endsWith(".class")) {
              String className = fileName.replace(".class", "").replace("/", ".");
              classList.add(className);
            }
          }
        } else if (file.isDirectory()) {
          // 遍歷所有classes目錄下的.class文件,并轉換成java類名格式
          Collection<File> files = FileUtils.listFiles(file, new String[]{"class"}, true);
    
          for (File classFile : files) {
            String className = classFile.toString().substring(file.toString().length())
              .replace(".class", "").replaceAll("^/", "").replace("/", ".");
    
            classList.add(className);
          }
        }
      }
    
      // 通過ASM方式獲取所有Java類的繼承關系,并判斷是否是HandlesTypes配置中的類的子類
      for (String className : classList) {
        // 通過ASM的方式獲取當前類的所有父類(包括繼承和實現的所有類)
        Set<String> superClassList = ClassUtils.getSuperClassListByAsm(className, classLoader);
    
        // 遍歷所有HandlesTypes配置
        for (ServletContainerInitializer sci : handlesTypesMap.keySet()) {
          // 獲取HandlesTypes配置的類數組對象
          Class[] handlesTypesClass = handlesTypesMap.get(sci);
    
          // 遍歷所有HandlesTypes配置的類數組對象
          for (Class typesClass : handlesTypesClass) {
            // 獲取HandlesTypes配置的類名稱
            String typeClassName = typesClass.getName();
    
            // 檢測當前Java類是否是HandlesTypes配置的類的子類,如果是就記錄下來
            if (superClassList.contains(typeClassName) && !className.equals(typeClassName)) {
              // 獲取SCI啟動類對象集合
              Set<Class<?>> sciClass = sciClassMap.get(sci);
    
              // 反射加載當前類對象
              Class clazz = classLoader.loadClass(className);
    
              // 將找到的SCI啟動類添加到集合中
              sciClass.add(clazz);
            }
          }
        }
      }
    }

    整個掃包過程會比較緩慢,因為一次性掃描了1萬多個類,并且還是用ASM解析了這1萬多個類的繼承關系。最終我們將會得出一個Map<ServletContainerInitializer, Set<Class<?>>> sciClassMap對象,我們通過遍歷這個sciClassMap并依次調用onStartup方法即可實現Servlet容器的啟動。

    啟動所有SCI代碼:

    for (ServletContainerInitializer initializer : sciClassMap.keySet()) {
      Set<Class<?>> initClassSet = sciClassMap.get(initializer);
    
      // 調用Servlet容器初始化的onStartup方法,啟動容器
      initializer.onStartup(initClassSet, servletContext);
    }

    initializer.onStartup(initClassSet, servletContext);因為傳入了ServletContext,所以initializer很有可能會通過ServletContext動態注冊一些FilterServletListener,而這些FilterServletListener和我們BinCat的內置的Servlet都處于未初始化的狀態,這個時候我們就必須要做一些初始化工作(V5版本只支持Servlet,并不支持FilterListener)了。

    初始化ServletContext中的所有Servlet代碼:

    initServlet(servletContext);

    完成以上所有邏輯后我們的BinCat也就算啟動成功了,剩下的就是如何處理瀏覽器請求了。

    image-20200917173402207

    Servlet請求處理

    V5依舊是根據瀏覽器請求的URL地址調用對應的Servletservice方法處理Servlet請求,訪問javasec-blog首頁測試:http://localhost:8080/

    image-20200917174239016

    請求的/最終會調用SpringMVCorg.springframework.web.servlet.DispatcherServlet類處理請求,如下:

    image-20200917175431500

    dynamic.getServlet().service(req, resp)最終會調用SpringDispatcherServlet類的父類org.springframework.web.servlet.FrameworkServletservice方法,如下:

    image-20200917180111632

    訪問文章詳情頁測試:http://localhost:8080/?p=12

    image-20200917173917790

    至此,耗時大約一周時間,我們的BinCat從支持解析簡單的HelloWorld到如今已經實現了啟動單Web應用SpringBoot了,當然這里面充斥這各種各樣的Bug和安全問題,我們的目標并不是實現一個國產化Java中間件,而是將BinCat變成一個存在各種各樣漏洞的靶場。

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类