Jos*_*eta 1 tomcat cdi weld spring-boot
有谁知道在带有嵌入式 Tomcat 的 Spring Boot Jar 应用程序中引导 Weld 的方法。
我尝试将 org.jboss.weld.environment.servlet.Listener 与
import org.jboss.weld.environment.servlet.Listener;
@SpringBootApplication
public class MyApplication
{
public static void main(String[] args)
{
SpringApplication.run(MyApplication.class, args);
}
@Bean
public Listener weldListener()
{
return new Listener();
}
}
Run Code Online (Sandbox Code Playgroud)
但我收到以下错误:
java.lang.RuntimeException: WELD-ENV-001104: Cannot get StandardContext from ServletContext.
at org.jboss.weld.environment.tomcat.WeldForwardingInstanceManager.getStandardContext(WeldForwardingInstanceManager.java:104) ~[weld-servlet-2.4.6.Final.jar:2.4.6.Final]
...
Caused by: java.lang.ClassCastException: org.apache.catalina.core.StandardContext$NoPluggabilityServletContext cannot be cast to org.apache.catalina.core.ApplicationContextFacade
at org.jboss.weld.environment.tomcat.WeldForwardingInstanceManager.getStandardContext(WeldForwardingInstanceManager.java:101) ~[weld-servlet-2.4.6.Final.jar:2.4.6.Final]
... 13 common frames omitted
Run Code Online (Sandbox Code Playgroud)
最后,我设法在使用嵌入式 Tomcat 的 Boot Spring 应用程序中引导 Weld。
存在一些与 Weld 使用的 Tomcat 容器以及 Boot Spring 生成的 jar 中的 BOOT-INF 条目的管理相关的问题。有些问题看起来像是错误,其他问题则与 Spring Boot 生成应用程序 jar 文件的方式有关。
Weld 使用 Java 服务注册一个扩展类,org.jboss.weld.environment.servlet.AbstractContainer该类用于将 Weld 注释处理器附加到相应的 servlet 容器。对于 Tomcat,此类创建一个替代servlet 容器使用的org.apache.tomcat.InstanceManager标准的类。InstanceManager
新的InstanceManager是类org.jboss.weld.environment.tomcat.WeldForwardingInstanceManager,这个类有一个方法:
private static StandardContext getStandardContext(ServletContext context)
Run Code Online (Sandbox Code Playgroud)
StandardContext使用内省从传递的私有字段中获取 a ServletContext。在 Tomcat Embedded 的情况下,传递的 ServletContext 是一个org.apache.catalina.core.StandardContext.NoPluggabilityServletContext没有相同字段的实例,并且不是ApplicationContextFacade此方法所期望的。
我修改了这个方法来应对这种情况。所以我编写了一个新WeldForwardingInstanceManager类,MyWeldForwardingInstanceManager更改了getStandardContext(ServletContext context)方法并添加了两个方法getContextFieldValue(E obj)来getContextFieldValue(String fieldname, E obj)代替现有getContextFieldValue(E obj, Class<E> clazz)方法:
private static StandardContext getStandardContext(ServletContext context)
{
try
{
// Hack into Tomcat to replace the InstanceManager using
// reflection to access private fields
try
{
ApplicationContext appContext = (ApplicationContext)getContextFieldValue(context);
return (StandardContext)getContextFieldValue(appContext);
}
catch (NoSuchFieldException e)
{
ServletContext servletContext = (ServletContext)getContextFieldValue("sc", context);
ApplicationContext appContext = (ApplicationContext)getContextFieldValue(servletContext);
return (StandardContext)getContextFieldValue(appContext);
}
}
catch (Exception e)
{
throw TomcatLogger.LOG.cannotGetStandardContext(e);
}
}
private static <E> Object getContextFieldValue(E obj) throws NoSuchFieldException, IllegalAccessException
{
return getContextFieldValue(CONTEXT_FIELD_NAME, obj);
}
private static <E> Object getContextFieldValue(String fieldname, E obj) throws NoSuchFieldException, IllegalAccessException
{
Field field = SecurityActions.lookupField(obj.getClass(), fieldname);
SecurityActions.ensureAccessible(field);
return field.get(obj);
}
Run Code Online (Sandbox Code Playgroud)
SecurityAction 类必须复制到同一个包中,因为它具有包可见性。
并且需要一个新的 TomcatContainer:
package mypackage;
import org.jboss.weld.environment.servlet.*;
import org.jboss.weld.environment.servlet.logging.TomcatLogger;
public class EmbeddedTomcatContainer extends AbstractContainer
{
public static final Container INSTANCE = new EmbeddedTomcatContainer();
private static final String TOMCAT_REQUIRED_CLASS_NAME = "org.apache.catalina.connector.Request";
@Override
protected String classToCheck()
{
return TOMCAT_REQUIRED_CLASS_NAME;
}
@Override
public void initialize(ContainerContext context)
{
try
{
MyWeldForwardingInstanceManager.replaceInstanceManager(context.getServletContext(), context.getManager());
if (Boolean.TRUE.equals(context.getServletContext().getAttribute(EnhancedListener.ENHANCED_LISTENER_USED_ATTRIBUTE_NAME)))
{
TomcatLogger.LOG.allInjectionsAvailable();
}
else
{
TomcatLogger.LOG.listenersInjectionsNotAvailable();
}
}
catch (Exception e)
{
TomcatLogger.LOG.unableToReplaceTomcat(e);
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后需要添加一个服务来在启动时加载EmbeddedTomcatContainer。这是通过将一个具有名称的文件添加org.jboss.weld.environment.servlet.Container到应用程序文件夹services的文件夹中来完成的。META-INF该文件的内容是 MyWeldForwardingInstanceManager 类的完全限定名称。在这种情况下:
mypackage.MyWeldForwardingInstanceManager
Run Code Online (Sandbox Code Playgroud)
这些更改允许引导 Weld,在 Eclipse 中运行该应用程序没有任何问题,但在尝试从repackage使用spring-boot-maven-plugin.
为了使其在使用打包的 jar 文件时工作,您必须更改 Weld 的两个类。
第一个是org.jboss.weld.environment.deployment.discovery.FileSystemBeanArchiveHandler。getUrl()include 类 ZipFileEntry 的方法似乎存在错误,因为它在构建嵌入式 jar 文件的 URL 时没有添加 Jar 分隔符。所以需要将其更改为:
@Override
public URL getUrl() throws MalformedURLException
{
return new URL(archiveUrl + (archiveUrl.endsWith(".jar") ? JAR_URL_SEPARATOR : "") + name);
}
Run Code Online (Sandbox Code Playgroud)
第二个是org.jboss.weld.environment.util.Files,这个类有一个方法filenameToClassname必须修改,以考虑到 Spring Boot 项目的类放置在该文件夹内BOOT-INF/classes,并且 Boot Spring 从该文件夹中加载它们,但 Weld 代码认为这些类是从根加载。修改后,该方法如下所示:
public static String filenameToClassname(String filename)
{
filename = filename.substring(0, filename.lastIndexOf(CLASS_FILE_EXTENSION)).replace('/', '.').replace('\\', '.');
if (filename.startsWith("BOOT-INF.classes."))
filename = filename.substring(BOOT_INF_CLASSES.length());
return filename;
}
Run Code Online (Sandbox Code Playgroud)
完成所有这些修改后,Weld 启动没有问题,并且所有 CDI 注释都在 Jar 打包的 Spring Boot 应用程序中工作。
编辑:
为了避免像 Omnifaces 2.x 这样使用 Weld 并在 JSF 启动时初始化的库出现问题,最好使用 servlet 容器初始化程序而不是 servlet 上下文侦听器来初始化 Weld,如 Omnifaces 作者 @BalusC 所建议的那样。看到这个答案。
| 归档时间: |
|
| 查看次数: |
2701 次 |
| 最近记录: |