有谁知道在嵌入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发布于 2018-01-08 02:06:55
最后,我设法在使用嵌入式Tomcat的Boot Spring应用程序中引导Weld。
Weld使用的Tomcat容器和Boot Spring生成的jar中BOOT-INF条目的管理都存在一些问题。有些问题看起来像是bug,另一些问题则与Spring Boot生成应用程序jar文件的方式有关。
Weld使用Java Services注册一个扩展org.jboss.weld.environment.servlet.AbstractContainer的类,该类用于将Weld注解处理器附加到相应的servlet容器。在Tomcat中,这个类创建一个替换servlet容器使用的标准InstanceManager的org.apache.tomcat.InstanceManager。
新的InstanceManager是类org.jboss.weld.environment.tomcat.WeldForwardingInstanceManager,该类有一个方法:
private static StandardContext getStandardContext(ServletContext context)它使用内省从传递的ServletContext的私有字段中获取StandardContext。在嵌入Tomcat的情况下,传递的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的文件添加到应用程序的META-INF文件夹的services文件夹中即可完成此操作。此文件的内容是MyWeldForwardingInstanceManager类的完全限定名。在这种情况下:
mypackage.MyWeldForwardingInstanceManager这些更改允许引导Weld,在Eclipse中运行应用程序没有问题,但当尝试从使用spring-boot-maven-plugin的repackage目标打包的jar文件运行应用程序时失败。
要使它在使用打包的jar文件时工作,您必须更改Weld的两个类。
第一个是org.jboss.weld.environment.deployment.discovery.FileSystemBeanArchiveHandler。似乎在include类ZipFileEntry的getUrl()方法中有一个bug,因为它在构建嵌入式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从文件夹中加载它们,但是焊接代码认为这些类是从根加载的。修改后的方法如下所示:
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应用程序中工作。
编辑
为了避免像Omnifaces 2.x这样使用Weld并在JSF启动时初始化的库出现问题,最好使用servlet容器初始化器来初始化Weld,而不是像Omnifaces的作者@BalusC所建议的那样使用servlet上下文侦听器来初始化Weld。请参阅此answer。
发布于 2018-02-17 12:57:03
这并不是那么复杂。通过大量的试验和错误,只需要包含javassist.jar,并将我所有的应用程序文件放在一个jar中,这样启动-INF/classes中就不会有任何东西为我工作。我用的是weld 3.0.1
https://stackoverflow.com/questions/48054288
复制相似问题