在Spring Boot环境中进行Bootstrap Weld [英] Bootstrap Weld in a Spring Boot environment

查看:153
本文介绍了在Spring Boot环境中进行Bootstrap Weld的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人知道一种在带有嵌入式Tomcat的Spring Boot Jar应用程序中引导Weld的方法吗。

Does anyone know a way to bootstrap Weld in a Spring Boot Jar application with embedded Tomcat.

我尝试使用org.jboss.weld.environment.servlet带有

I have tried to use org.jboss.weld.environment.servlet.Listener with

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。

Finally I managed to bootstrap Weld in a Boot Spring application that uses an embedded Tomcat.

存在一些与Weld使用的Tomcat容器以及Boot Spring生成的jar中BOOT-INF条目的管理有关的问题。其中一些问题似乎是错误,而其他问题则与Spring Boot生成应用程序jar文件的方式有关。

There are some problems related to both the Tomcat container that Weld uses and the management of the BOOT-INF entries in the Boot Spring generated jar. Some of the problems seem bugs and the other ones are related to the way Spring Boot generates the application jar file.

Weld使用Java服务来注册扩展 org.jboss.weld.environment.servlet.AbstractContainer 用于将Weld注释处理器附加到相应的servlet容器。对于Tomcat,此类创建一个 org.apache.tomcat.InstanceManager 来替换该servlet的标准 InstanceManager

Weld uses Java Services to register a class that extends org.jboss.weld.environment.servlet.AbstractContainer that is used to attach the Weld annotations processor to the corresponding servlet container. In the case of Tomcat this class creates a org.apache.tomcat.InstanceManager that replaces the standard InstanceManager that the servlet container uses.

新的 InstanceManager 是类 org.jboss.weld.environment .tomcat.WeldForwardingInstanceManager ,此类具有一种方法:

The new InstanceManager is the class org.jboss.weld.environment.tomcat.WeldForwardingInstanceManager, this class has a method:

private static StandardContext getStandardContext(ServletContext context)

使用自省功能从中获取 StandardContext 传递的 ServletContext 的私有字段。对于Tomcat Embedded,传递的ServletContext是 org.apache.catalina.core.StandardContext.NoPluggabilityServletContext 的实例,其中no具有相同的字段,并且不是 ApplicationContextFacade 就是这种方法的预期。

that uses introspection to obtain a StandardContext from a private field of the passed ServletContext. In the case of Tomcat Embedded the passed ServletContext is an instance of org.apache.catalina.core.StandardContext.NoPluggabilityServletContext that no has the same field and is not an ApplicationContextFacade that is what this method expect.

我修改了此方法来处理这种情况。所以我写了一个新的 WeldForwardingInstanceManager 类, MyWeldForwardingInstanceManager ,更改了 getStandardContext(ServletContext context)方法并添加两个方法 getContextFieldValue(E obj) getContextFieldValue(String fieldname,E obj)而不是现有的 getContextFieldValue(E obj,Class< E> clazz)方法:

I modified this method to deal with this situation. So I wrote a new WeldForwardingInstanceManagerclass, MyWeldForwardingInstanceManager, changing the getStandardContext(ServletContext context) method and adding two methods getContextFieldValue(E obj) and getContextFieldValue(String fieldname, E obj) instead of the existing getContextFieldValue(E obj, Class<E> clazz) method:

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类必须复制到同一软件包中,因为它具有软件包可见性

The SecurityAction class has to be copied to the same package because it has package visibility.

,并且需要一个新的TomcatContainer:

And a new TomcatContainer is needed:

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类的完全限定名称。在这种情况下:

And then is necessary to add a Service to load the EmbeddedTomcatContainer at startup. This is done adding a file with name org.jboss.weld.environment.servlet.Container to the services folder of the META-INF folder of your applicaction. The contents of this file is the fully qualified name of the MyWeldForwardingInstanceManager class. In this case:

mypackage.MyWeldForwardingInstanceManager

这些更改允许启动Weld,在Eclipse中运行应用程序没有问题,但是尝试从使用 repackage打包的jar文件中运行应用程序时失败。 / code> spring-boot-maven-plugin 的目标。

These changes allow to bootstrap Weld, running the application inside Eclipse works with no problem, but fails when trying to run he application from a jar file packaged using the repackage goal of the spring-boot-maven-plugin.

要使其在使用打包的jar文件,您必须更改两类Weld。

To make it work when using the packaged jar file you have to change two classes of Weld.

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

The first one is org.jboss.weld.environment.deployment.discovery.FileSystemBeanArchiveHandler. It seems that there is a bug in the getUrl() method of the include class ZipFileEntry because it no adds a Jar separator when building the URL for an embedded jar file. So its needed to change it to:

@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代码认为这些类是从根加载的。修改后,方法如下:

The second one is org.jboss.weld.environment.util.Files, this class has a method filenameToClassname that has to be modified to taking into account that the classes of the Spring Boot project are placed inside the folder BOOT-INF/classes and the Boot Spring loads them from it, but the Weld code thinks that these classes are loaded from the root. Once modified the method looks like:

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应用程序中起作用。

After all these modifications have been done Weld starts with no problem and all the CDI annotations work in a Jar packaged Spring Boot application.

编辑

在为了避免使用Weld并在JSF启动时进行初始化的Omnifaces 2.x之类的库出现问题,最好使用servlet容器初始化器而不是omnifaces的作者@BalusC建议的Servlet上下文侦听器来初始化Weld。请参阅此 answer

In order to avoid problems with libraries like Omnifaces 2.x that use Weld and are initialized when JSF starts, is better to initialize Weld using a servlet container initializer instead of a servlet context listener as suggested by @BalusC the author of Omnifaces. See this answer.

这篇关于在Spring Boot环境中进行Bootstrap Weld的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆