首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Spring引导环境中的Bootstrap Weld

Spring引导环境中的Bootstrap Weld
EN

Stack Overflow用户
提问于 2018-01-02 08:09:11
回答 2查看 2K关注 0票数 1

有谁知道在嵌入Tomcat的Spring Boot Jar应用程序中引导Weld的方法。

我曾尝试将org.jboss.weld.environment.servlet.Listener与

代码语言:javascript
复制
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();
  }
}

但我得到以下错误:

代码语言:javascript
复制
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
EN

回答 2

Stack Overflow用户

发布于 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容器使用的标准InstanceManagerorg.apache.tomcat.InstanceManager

新的InstanceManager是类org.jboss.weld.environment.tomcat.WeldForwardingInstanceManager,该类有一个方法:

代码语言:javascript
复制
private static StandardContext getStandardContext(ServletContext context)

它使用内省从传递的ServletContext的私有字段中获取StandardContext。在嵌入Tomcat的情况下,传递的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)方法:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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类的完全限定名。在这种情况下:

代码语言:javascript
复制
mypackage.MyWeldForwardingInstanceManager

这些更改允许引导Weld,在Eclipse中运行应用程序没有问题,但当尝试从使用spring-boot-maven-pluginrepackage目标打包的jar文件运行应用程序时失败。

要使它在使用打包的jar文件时工作,您必须更改Weld的两个类。

第一个是org.jboss.weld.environment.deployment.discovery.FileSystemBeanArchiveHandler。似乎在include类ZipFileEntry的getUrl()方法中有一个bug,因为它在构建嵌入式Jar文件的URL时没有添加jar分隔符。因此需要将其更改为:

代码语言:javascript
复制
@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从文件夹中加载它们,但是焊接代码认为这些类是从根加载的。修改后的方法如下所示:

代码语言:javascript
复制
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

票数 4
EN

Stack Overflow用户

发布于 2018-02-17 12:57:03

这并不是那么复杂。通过大量的试验和错误,只需要包含javassist.jar,并将我所有的应用程序文件放在一个jar中,这样启动-INF/classes中就不会有任何东西为我工作。我用的是weld 3.0.1

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48054288

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档