Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 基于Spring的模块化应用_Java_Spring_Jsp_Spring Mvc - Fatal编程技术网

Java 基于Spring的模块化应用

Java 基于Spring的模块化应用,java,spring,jsp,spring-mvc,Java,Spring,Jsp,Spring Mvc,我希望允许用户在主项目中添加/刷新/更新/删除模块,而无需重新启动或重新部署。用户将能够编写自己的模块并将其添加到主项目中 从技术上讲,模块将是一个JAR,它可能是“热启动”的,并且可能包含: 弹簧控制器 服务,EJB 资源(JSP、css、图像、javascripts…) 因此,当用户添加模块时,应用程序必须按照预期注册控制器、服务、EJB和映射资源。当他删除时,应用程序将卸载它们 说起来容易。实际上似乎要困难得多 目前。主要问题是每次更新模块时都必须重新部署。我需要避免这种情况 我读了一

我希望允许用户在主项目中添加/刷新/更新/删除模块,而无需重新启动或重新部署。用户将能够编写自己的模块并将其添加到主项目中

从技术上讲,模块将是一个JAR,它可能是“热启动”的,并且可能包含:

  • 弹簧控制器
  • 服务,EJB
  • 资源(JSP、css、图像、javascripts…)
因此,当用户添加模块时,应用程序必须按照预期注册控制器、服务、EJB和映射资源。当他删除时,应用程序将卸载它们

说起来容易。实际上似乎要困难得多

目前。主要问题是每次更新模块时都必须重新部署。我需要避免这种情况

我读了一些关于OSGi的文档,但我不明白如何将它与我的项目联系起来,也不明白它如何按需加载/卸载

有人能给我一个解决方案或想法吗

我使用的内容:

  • 玻璃鱼3.1.2
  • Spring MVC 3.1.3
  • Spring Security 3.1.3
谢谢


编辑:

我现在可以说这是可能的。我会这样做:

添加模块:

  • 上传module.jar
  • 处理文件,在模块文件夹中展开
  • 关闭Spring应用程序上下文
  • 在自定义类加载器中加载JAR,其中父级为WebappClassLoader
  • 在主项目中复制资源(我希望可能会找到替代方案,但目前应该可以)
  • 刷新Spring应用程序上下文
  • 删除模块:

  • 关闭Spring应用程序上下文
  • 解除自定义类加载器的绑定并将其转到GC
  • 删除资源
  • 从模块文件夹+jar中删除文件(如果保留)
  • 刷新Spring应用程序上下文
  • 对于每个文件夹,Spring都必须扫描另一个文件夹,而不是

    domains/domain1/project/WEB-INF/classes
    domains/domain1/project/WEB-INF/lib
    domains/domain1/lib/classes
    
    这就是我当前的问题

    从技术上讲,我发现涉及到
    路径匹配源模式解析程序
    类路径扫描候选组件提供程序
    。现在我需要告诉他们扫描特定的文件夹/类

    对于其余部分,我已经做了一些测试,它应该可以正常工作

    有一点是不可能的:jar中的ejb


    当我做了一些有用的事情时,我会发布一些资源。

    好的,我做了,但我有太多的资源,无法在这里发布。我将一步一步地解释我是如何做到的,但不会发布类加载部分,这对于一般熟练的开发人员来说很简单

    我的代码目前不支持的一件事是上下文配置扫描

    首先,下面的解释取决于您的需要以及您的应用服务器。我使用Glassfish 3.1.2,但没有找到如何配置自定义类路径:

    • 不再支持类路径前缀/后缀
    • -classpath
      域的java配置上的参数不起作用
    • 类路径环境也不起作用
    因此,GF3类路径中唯一可用的路径是:WEB-INF/classes、WEB-INF/lib。。。如果您找到了在应用服务器上执行此操作的方法,则可以跳过前4个步骤

    我知道这在Tomcat身上是可能的

    步骤1:创建自定义命名空间处理程序 使用XSD、spring.handlers和spring.schema创建一个自定义的
    NamespaceHandlerSupport
    。此命名空间处理程序将包含对
    的重新定义

    XSD只包含
    组件扫描
    元素,它是Spring的完美副本

    弹簧处理器

    spring.schemas

    N.B.:由于项目名称等问题,我没有覆盖Spring默认名称空间处理程序,因为项目名称需要大于“S”的字母。我想避免这种情况,所以我创建了自己的名称空间

    步骤2:创建解析器 这将由上面创建的命名空间处理程序初始化

    /**
     * Parser for the {@code <module-context:component-scan/>} element.
     * @author Ludovic Guillaume
     */
    public class ModuleComponentScanBeanDefinitionParser extends ComponentScanBeanDefinitionParser {
        @Override
        protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
            return new ModuleBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters);
        }
    }
    
    步骤4:创建自定义资源缓存实现 这将允许Spring在类路径之外解析您的模块类

    public class ModuleCachingMetadataReaderFactory extends CachingMetadataReaderFactory {
        private Log logger = LogFactory.getLog(ModuleCachingMetadataReaderFactory.class);
    
        @Override
        public MetadataReader getMetadataReader(String className) throws IOException {
            List<Module> modules = ModuleManager.getStartedModules();
    
            logger.debug("Checking if " + className + " is contained in loaded modules");
    
            for (Module module : modules) {
                if (className.startsWith(module.getPackageName())) {
                    String resourcePath = module.getExpandedJarFolder().getAbsolutePath() + "/" + ClassUtils.convertClassNameToResourcePath(className) + ".class";
    
                    File file = new File(resourcePath);
    
                    if (file.exists()) {
                        logger.debug("Yes it is, returning MetadataReader of this class");
    
                        return getMetadataReader(getResourceLoader().getResource("file:" + resourcePath));
                    }
                }
            }
    
            return super.getMetadataReader(className);
        }
    }
    
    步骤7:定义自定义上下文加载程序侦听器 web.xml

    web.xml

    
    调度员
    com.yourpackage.module.spring.web.servlet.ModuleDispatcherServlet
    上下文配置位置
    /WEB-INF/dispatcher-servlet.xml
    1.
    
    步骤9:定义自定义Jstl视图 这一部分是“可选的”,但它在控制器实现中带来了一些清晰和干净

    /**
     * Used to handle module {@link ModelAndView}.<br/><br/>
     * <b>Usage:</b><br/>{@code new ModuleAndView("module:MODULE_NAME.jar:LOCATION");}<br/><br/>
     * <b>Example:</b><br/>{@code new ModuleAndView("module:test-module.jar:views/testModule");}
     * @see JstlView
     * @author Ludovic Guillaume
     */
    public class ModuleJstlView extends JstlView {
        @Override
        protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) throws Exception {
            String beanName = getBeanName();
    
            // checks if it starts 
            if (beanName.startsWith("module:")) {
                String[] values = beanName.split(":");
    
                String location = String.format("/%s%s/WEB-INF/%s", ModuleManager.CONTEXT_ROOT_MODULES_FOLDER, values[1], values[2]);
    
                setUrl(getUrl().replaceAll(beanName, location));
            }
    
            return super.prepareForRendering(request, response);
        }
    }
    
    /**
    *用于处理模块{@link ModelAndView}。

    *用法:
    {@code new moduleadview(“module:module_NAME.jar:LOCATION”);}

    *示例:
    {@code新模块adview(“module:test module.jar:views/testModule”);} *@see JstlView *@作者卢多维奇·纪尧姆 */ 公共类ModuleJstlView扩展了JstlView{ @凌驾 受保护的字符串prepareForRendering(HttpServletRequest请求、HttpServletResponse响应)引发异常{ 字符串beanName=getBeanName(); //检查它是否启动 if(beanName.startsWith(“模块:”){ 字符串[]值=beanName.split(“:”); String location=String.format(“/%s%s/WEB-INF/%s”,ModuleManager.CONTEXT_ROOT_MODULES_文件夹,值[1],值[2]); setUrl(getUrl().replaceAll(beanName,location)); } 返回super.prepareForRendering(请求、响应); } }
    在bean配置中定义它:

    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:viewClass="com.yourpackage.module.spring.web.servlet.view.ModuleJstlView"
          p:prefix="/WEB-INF/"
          p:suffix=".jsp"/>
    
    
    
    最后一步 现在您只需要创建一个模块,将其与
    ModuleManager
    接口,并在WEB-INF/文件夹中添加资源

    之后,您可以调用load/start/stop/unload。每次操作后,我都会对上下文进行个性化刷新,加载除外

    代码可能是可优化的(
    ModuleManager
    如singleton),可能还有更好的选择(尽管我没有找到)

    我的下一个目标是扫描一个模块上下文配置,其中
    /**
     * Custom scanner that detects bean candidates on the classpath (through {@link ClassPathBeanDefinitionScanner} and on the module folder.
     * @author Ludovic Guillaume
     */
    public class ModuleBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
        private ResourcePatternResolver resourcePatternResolver;
        private MetadataReaderFactory metadataReaderFactory;
    
        /**
         * @see {@link ClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner(BeanDefinitionRegistry, boolean)}
         * @param registry
         * @param useDefaultFilters
         */
        public ModuleBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
            super(registry, useDefaultFilters);
    
            try {
                // get parent class variable
                resourcePatternResolver = (ResourcePatternResolver)getResourceLoader();
    
                // not defined as protected and no getter... so reflection to get it
                Field field = ClassPathScanningCandidateComponentProvider.class.getDeclaredField("metadataReaderFactory");
                field.setAccessible(true);
                metadataReaderFactory = (MetadataReaderFactory)field.get(this);
                field.setAccessible(false);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Scan the class path for candidate components.<br/>
         * Include the expanded modules folder {@link ModuleManager#getExpandedModulesFolder()}.
         * @param basePackage the package to check for annotated classes
         * @return a corresponding Set of autodetected bean definitions
         */
        @Override
        public Set<BeanDefinition> findCandidateComponents(String basePackage) {
            Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(super.findCandidateComponents(basePackage));
    
            logger.debug("Scanning for candidates in module path");
    
            try {
                String packageSearchPath = "file:" + ModuleManager.getExpandedModulesFolder() + "/**/*.class";
    
                Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
                boolean traceEnabled = logger.isTraceEnabled();
                boolean debugEnabled = logger.isDebugEnabled();
    
                for (Resource resource : resources) {
                    if (traceEnabled) {
                        logger.trace("Scanning " + resource);
                    }
                    if (resource.isReadable()) {
                        try {
                            MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
    
                            if (isCandidateComponent(metadataReader)) {
                                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                                sbd.setResource(resource);
                                sbd.setSource(resource);
    
                                if (isCandidateComponent(sbd)) {
                                    if (debugEnabled) {
                                        logger.debug("Identified candidate component class: " + resource);
                                    }
                                    candidates.add(sbd);
                                }
                                else {
                                    if (debugEnabled) {
                                        logger.debug("Ignored because not a concrete top-level class: " + resource);
                                    }
                                }
                            }
                            else {
                                if (traceEnabled) {
                                    logger.trace("Ignored because not matching any filter: " + resource);
                                }
                            }
                        }
                        catch (Throwable ex) {
                            throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
                        }
                    }
                    else {
                        if (traceEnabled) {
                            logger.trace("Ignored because not readable: " + resource);
                        }
                    }
                }
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
            }
    
            return candidates;
        }
    }
    
    public class ModuleCachingMetadataReaderFactory extends CachingMetadataReaderFactory {
        private Log logger = LogFactory.getLog(ModuleCachingMetadataReaderFactory.class);
    
        @Override
        public MetadataReader getMetadataReader(String className) throws IOException {
            List<Module> modules = ModuleManager.getStartedModules();
    
            logger.debug("Checking if " + className + " is contained in loaded modules");
    
            for (Module module : modules) {
                if (className.startsWith(module.getPackageName())) {
                    String resourcePath = module.getExpandedJarFolder().getAbsolutePath() + "/" + ClassUtils.convertClassNameToResourcePath(className) + ".class";
    
                    File file = new File(resourcePath);
    
                    if (file.exists()) {
                        logger.debug("Yes it is, returning MetadataReader of this class");
    
                        return getMetadataReader(getResourceLoader().getResource("file:" + resourcePath));
                    }
                }
            }
    
            return super.getMetadataReader(className);
        }
    }
    
    <bean id="customCachingMetadataReaderFactory" class="com.yourpackage.module.spring.core.type.classreading.ModuleCachingMetadataReaderFactory"/>
    
    <bean name="org.springframework.context.annotation.internalConfigurationAnnotationProcessor"
          class="org.springframework.context.annotation.ConfigurationClassPostProcessor">
          <property name="metadataReaderFactory" ref="customCachingMetadataReaderFactory"/>
    </bean>
    
    /**
     * Refresh {@link DispatcherServlet}
     * @return true if refreshed, false if not
     * @throws RuntimeException
     */
    private static boolean refreshDispatcherServlet() throws RuntimeException {
        if (dispatcherServlet != null) {
            dispatcherServlet.refresh();
            return true;
        }
    
        return false;
    }
    
    /**
     * Refresh the given {@link XmlWebApplicationContext}.<br>
     * Call {@link Module#onStarted()} after context refreshed.<br>
     * Unload started modules on {@link RuntimeException}.
     * @param context Application context
     * @param startedModules Started modules
     * @throws RuntimeException
     */
    public static void refreshContext(XmlWebApplicationContext context, Module[] startedModules) throws RuntimeException {
        try {
            logger.debug("Closing web application context");
            context.stop();
            context.close();
    
            AppClassLoader.destroyInstance();
    
            setCurrentClassLoader(context);
    
            logger.debug("Refreshing web application context");
            context.refresh();
    
            setCurrentClassLoader(context);
    
            AppClassLoader.setThreadsToNewClassLoader();
    
            refreshDispatcherServlet();
    
            if (startedModules != null) {
                for (Module module : startedModules) {
                    module.onStarted();
                }
            }
        }
        catch (RuntimeException e) {
            for (Module module : startedModules) {
                try {
                    ModuleManager.stopModule(module.getId());
                }
                catch (IOException e2) {
                    e.printStackTrace();
                }
            }
    
            throw e;
        }
    }
    
    /**
     * Set the current classloader to the {@link XmlWebApplicationContext} and {@link Thread#currentThread()}.
     * @param context ApplicationContext
     */
    public static void setCurrentClassLoader(XmlWebApplicationContext context) {
        context.setClassLoader(AppClassLoader.getInstance());
        Thread.currentThread().setContextClassLoader(AppClassLoader.getInstance());
    }
    
    /**
     * Initialize/destroy ModuleManager on context init/destroy
     * @see {@link ContextLoaderListener}
     * @author Ludovic Guillaume
     */
    public class ModuleContextLoaderListener extends ContextLoaderListener {
        public ModuleContextLoaderListener() {
            super();
        }
    
        @Override
        public void contextInitialized(ServletContextEvent event) {
            // initialize ModuleManager, which will scan the given folder
            // TODO: param in web.xml
            ModuleManager.init(event.getServletContext().getRealPath("WEB-INF"), "/dev/temp/modules");
    
            super.contextInitialized(event);
        }
    
        @Override
        protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
            XmlWebApplicationContext context = (XmlWebApplicationContext)super.createWebApplicationContext(sc);
    
            // set the current classloader
            WebApplicationUtils.setCurrentClassLoader(context);
    
            return context;
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent event) {
            super.contextDestroyed(event);
    
            // destroy ModuleManager, dispose every module classloaders
            ModuleManager.destroy();
        }
    }
    
    <listener>
        <listener-class>com.yourpackage.module.spring.context.ModuleContextLoaderListener</listener-class>
    </listener>
    
    /**
     * Only used to keep the {@link DispatcherServlet} easily accessible by {@link WebApplicationUtils}.
     * @author Ludovic Guillaume
     */
    public class ModuleDispatcherServlet extends DispatcherServlet {
        private static final long serialVersionUID = 1L;
    
        public ModuleDispatcherServlet() {
            WebApplicationUtils.setDispatcherServlet(this);
        }
    }
    
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>com.yourpackage.module.spring.web.servlet.ModuleDispatcherServlet</servlet-class>
    
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>
    
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    /**
     * Used to handle module {@link ModelAndView}.<br/><br/>
     * <b>Usage:</b><br/>{@code new ModuleAndView("module:MODULE_NAME.jar:LOCATION");}<br/><br/>
     * <b>Example:</b><br/>{@code new ModuleAndView("module:test-module.jar:views/testModule");}
     * @see JstlView
     * @author Ludovic Guillaume
     */
    public class ModuleJstlView extends JstlView {
        @Override
        protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) throws Exception {
            String beanName = getBeanName();
    
            // checks if it starts 
            if (beanName.startsWith("module:")) {
                String[] values = beanName.split(":");
    
                String location = String.format("/%s%s/WEB-INF/%s", ModuleManager.CONTEXT_ROOT_MODULES_FOLDER, values[1], values[2]);
    
                setUrl(getUrl().replaceAll(beanName, location));
            }
    
            return super.prepareForRendering(request, response);
        }
    }
    
    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:viewClass="com.yourpackage.module.spring.web.servlet.view.ModuleJstlView"
          p:prefix="/WEB-INF/"
          p:suffix=".jsp"/>