有谁知道一种在带有嵌入式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

我修改了此方法来处理这种情况。因此,我编写了一个新的WeldForwardingInstanceManagerMyWeldForwardingInstanceManager,更改了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/

10-10 00:09