有谁知道一种在带有嵌入式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();
}
}
但是我收到以下错误:
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
最佳答案
最终,我设法在使用嵌入式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,此类创建一个org.apache.tomcat.InstanceManager
来替换servlet容器使用的标准InstanceManager
。
新的InstanceManager
是类org.jboss.weld.environment.tomcat.WeldForwardingInstanceManager
,此类具有一个方法:
private static StandardContext getStandardContext(ServletContext context)
它使用内省从传递的
StandardContext
的私有字段中获取ServletContext
。对于Tomcat Embedded,传递的ServletContext是org.apache.catalina.core.StandardContext.NoPluggabilityServletContext
的实例,该实例的no没有相同的字段,并且不是此方法所期望的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);
}
必须将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);
}
}
}
然后需要添加服务以在启动时加载EmbeddedTomcatContainer。这是通过将名称为
org.jboss.weld.environment.servlet.Container
的文件添加到应用程序services
文件夹的META-INF
文件夹中来完成的。该文件的内容是MyWeldForwardingInstanceManager类的完全限定名称。在这种情况下:mypackage.MyWeldForwardingInstanceManager
这些更改允许启动Weld,在Eclipse中运行应用程序没有问题,但是当尝试从使用
repackage
目标的spring-boot-maven-plugin
打包的jar文件中运行应用程序时失败。为了在使用打包的jar文件时使其工作,您必须更改两个Weld类。
第一个是
org.jboss.weld.environment.deployment.discovery.FileSystemBeanArchiveHandler
。包含类ZipFileEntry的getUrl()
方法中似乎存在一个错误,因为在为嵌入式jar文件构建URL时没有添加Jar分隔符。因此需要将其更改为:@Override
public URL getUrl() throws MalformedURLException
{
return new URL(archiveUrl + (archiveUrl.endsWith(".jar") ? JAR_URL_SEPARATOR : "") + name);
}
第二个是
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;
}
完成所有这些修改后,Weld毫无问题地开始,并且所有CDI注释都在Jar打包的Spring Boot应用程序中工作。
编辑:
为了避免使用Weld并在JSF启动时进行初始化的Omnifaces 2.x之类的库出现问题,最好使用servlet容器初始化器而不是omnifaces的作者@BalusC建议的Servlet上下文侦听器来初始化Weld。参见此answer。
关于tomcat - 在Spring Boot环境中进行Bootstrap Weld,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48054288/