技术上,一个模块将是一个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 /> ;.的重新定义.
/** * 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作为单例),也可能有更好的选择(尽管我没有找到它).
我的下一个目标是扫描一个不应该那么困难的模块上下文配置.