加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

java – 基于Spring的应用程序

发布时间:2020-12-14 16:44:16 所属栏目:Java 来源:网络整理
导读:我想允许用户在主项目中添加/刷新/更新/删除模块,而不需要重新启动或重新部署.用户将能够编写自己的模块并将其添加到主项目中. 技术上,一个模块将是一个JAR,可能是“热启动”,可能包含: 弹簧控制器 服务,ejbs … 资源(jsps,css,图像,javascripts …) 因此,
我想允许用户在主项目中添加/刷新/更新/删除模块,而不需要重新启动或重新部署.用户将能够编写自己的模块并将其添加到主项目中.

技术上,一个模块将是一个JAR,可能是“热启动”,可能包含:

弹簧控制器
>服务,ejbs …
>资源(jsps,css,图像,javascripts …)

因此,当用户添加模块时,应用程序必须按照意图注册控制器,服务,ejbs和映射资源.当他移除时,应用程序卸载它们.

容易说其实似乎更难做.

目前,I did it using Servlet 3.0 and web-fragment.xml.主要的问题是每次更新一个模块时都要重新部署.我需要避免.

我阅读了关于OSGi的一些文档,但是我不明白我如何可以将其与我的项目链接,既不能按需加载/卸载.

有人能帮我解决一个问题吗?

我用什么

> Glassfish 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

这实际上是我目前的问题.

技术上,我发现PathMatchingResourcePatternResolver和ClassPathScanningCandidateComponentProvider涉及到.现在我需要告诉他们扫描具体的文件夹/类.

对于其余的,我已经做了一些测试,它应该按预期工作.

一点不可能:ejbs在罐子里.

当我做一些可用的东西时,我会发布一些资料.

解决方法

好的,我做了,但我真的有太多的资料来发布在这里.我将逐步解释我是怎么做的,但不会发布简单的平均熟练的开发人员的类加载部分.

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

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

> classpath前缀/后缀不再支持
在域的java-config上的-classpath参数不起作用
> CLASSPATH环境也无效

因此,GF3的classpath中唯一可用的路径是:WEB-INF / classes,WEB-INF / lib …如果您在应用程序服务器上找到方法,可以跳过前4个步骤.

我知道这是可能的与Tomcat.

步骤1:创建自定义命名空间处理程序

使用XSD,spring.handlers和spring.schemas创建自定义NamespaceHandlerSupport.该命名空间处理程序将包含< context:component-scan /&gt ;.的重新定义.

/**
* Redefine {@code component-scan} to scan the module folder in addition to classpath
* @author Ludovic Guillaume
*/
public class ModuleContextNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("component-scan",new ModuleComponentScanBeanDefinitionParser());
    }
}

XSD只包含组件扫描元素,它是Spring的完美拷贝.

spring.handlers

http://www.yourwebsite.com/schema/context=com.yourpackage.module.spring.context.config.ModuleContextNamespaceHandler

spring.schemas

http://www.yourwebsite.com/schema/context/module-context.xsd=com/yourpackage/module/xsd/module-context.xsd

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);
    }
}

步骤3:创建扫描仪

这是使用与ClassPathBeanDefinitionScanner相同的代码的自定义扫描程序.唯一的代码是String packageSearchPath =“file:”ModuleManager.getExpandedModulesFolder()“/**/*.class”;.

ModuleManager.getExpandedModulesFolder()包含一个绝对的URL.例如:C:/< project> / modules /.

/**
 * 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;
    }
}

步骤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);
    }
}

并在bean配置中定义它:

<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>

步骤5:创建自定义根类加载器,模块类加载器和模块管理器

这是我不会上课的部分.所有类加载器都扩展URLClassLoader.

根类加载器

我做了我的单身,所以它可以:

>自己初始化
>摧毁
> loadClass(模块类,父类,自类)

最重要的部分是loadClass,它将允许上下文在使用setCurrentClassLoader(XmlWebApplicationContext)之后加载模块类(参见下一步的底部).简而言之,这种方法将扫描孩子的类加载器(我的个人资料存储在我的模块管理器中),如果没有找到,它将扫描父/自己的类.

模块类加载器

这个类加载器只是将module.jar和.jar添加为url.

模块经理

该类可以加载/启动/停止/卸载模块.我这样做:

> load:存储一个Module类,代表module.jar(包含id,name,description,file …)
> start:展开jar,创建模块类加载器并将其分配给Module类
> stop:删除扩展的jar,处理classloader
>卸载:处理模块类

步骤6:定义一个有助于进行上下文刷新的类

我命名这个类WebApplicationUtils.它包含对调度程序servlet的引用(请参阅步骤7).如你所见,在AppClassLoader上的refreshContext调用方法实际上是我的根类加载器.

/**
 * 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());
}

步骤7:定义一个自定义上下文加载器侦听器

/**
 * 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();
    }
}

web.xml中

<listener>
    <listener-class>com.yourpackage.module.spring.context.ModuleContextLoaderListener</listener-class>
</listener>

步骤8:定义一个自定义分派器servlet

/**
 * 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);
    }
}

web.xml中

<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>

步骤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);
    }
}

在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作为单例),也可能有更好的选择(尽管我没有找到它).

我的下一个目标是扫描一个不应该那么困难的模块上下文配置.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读