【问题标题】:How to configure Jersey with Spring using only annotations如何仅使用注释使用 Spring 配置 Jersey
【发布时间】:2013-01-21 05:39:16
【问题描述】:

我有一个同时使用 Spring 和 Jersey 的 Servlet 3.0 Web 应用程序。我目前使用在 web.xml 中配置为过滤器的 SpringServlet 设置它,并且使用 @Path@Component 注释的资源类。这是 web.xml sn-p:

<filter>
    <filter-name>jersey-serlvet</filter-name>
    <filter-class>
        com.sun.jersey.spi.spring.container.servlet.SpringServlet
    </filter-class>
    <init-param>
        <param-name>
            com.sun.jersey.config.property.packages
        </param-name>
        <param-value>com.foo;com.bar</param-value>
    </init-param>
    <init-param>
        <param-name>com.sun.jersey.config.feature.FilterForwardOn404</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>jersey-serlvet</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

此设置有效,但我真的希望仅使用注释进行设置 - 没有 web.xml 配置。我的第一次尝试是删除上面的 SpringServlet 配置并创建一个扩展 Application 的类。这是其中的一个sn-p:

@ApplicationPath("/*")
public class MyApplication extends PackagesResourceConfig {

    public MyApplication() {
        super("com.foo;com.bar");

        HashMap<String, Object> settings = new HashMap<String, Object>(1);
        settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
        this.setPropertiesAndFeatures(settings);
    }
}

这是因为 JAX-RS 资源已注册,我可以在它们的 URL 上点击它们,但是当它们尝试使用它们的自动装配属性时会抛出 NullPointerExceptions ......这是有道理的,因为我猜测资源现在由 Jersey 加载并且不是 Spring 管理的 bean,因此没有自动装配。

尽管进行了相当多的搜索,但我找不到任何将 Jersey 资源加载为仅带有注释的 Spring bean 的方法。 有这样的方法吗?我真的不想为资源编写一堆代码来手动获取 Spring 上下文并调用 DI,如果我能提供帮助的话。

如果仅注释不起作用,那么如果我可以指定要加载的 Application 类而不是要扫描的包列表,那么我可以使用 web.xml 中的过滤器配置。如果我可以摆脱那里的包列表并只指定一个Application 类实例,那么我会很满足。

显然,如果有人对我有明确的答案,那就太好了,但我也很感激任何关于我可以在哪里寻找或尝试的建议或提示。

谢谢, 马特

【问题讨论】:

    标签: spring jersey


    【解决方案1】:

    下面是我的应用程序的一部分,它使用 Servlet 3.0、Spring、Jersey 1.8,它没有 web.xml:

    public class WebAppInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setConfigLocation("com.myapp.config");
    
        final FilterRegistration.Dynamic characterEncodingFilter = servletContext.addFilter("characterEncodingFilter", new CharacterEncodingFilter());
        characterEncodingFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
        characterEncodingFilter.setInitParameter("encoding", "UTF-8");
        characterEncodingFilter.setInitParameter("forceEncoding", "true");
    
        final FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
        springSecurityFilterChain.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
    
        servletContext.addListener(new ContextLoaderListener(context));
        servletContext.setInitParameter("spring.profiles.default", "production");
    
        final SpringServlet servlet = new SpringServlet();
    
        final ServletRegistration.Dynamic appServlet = servletContext.addServlet("appServlet", servlet);
        appServlet.setInitParameter("com.sun.jersey.config.property.packages", "com.myapp.api");
        appServlet.setInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", "com.myapp.api.SizeLimitFilter");
        appServlet.setLoadOnStartup(1);
    
        final Set<String> mappingConflicts = appServlet.addMapping("/api/*");
    
        if (!mappingConflicts.isEmpty()) {
            throw new IllegalStateException("'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
        }
    }
    

    }

    【讨论】:

      【解决方案2】:

      我无法获得理想的结果,但我已经取得了一些进展,所以我会在这里发帖以防它帮助其他人。我能够使用 Spring Servlet 来指定我的应用程序类,从而从 web.xml 中删除包列表。

      所需的 web.xml 更改在 init 参数中(未显示过滤器映射,但仍然需要):

      <filter>
          <filter-name>jersey-serlvet</filter-name>
          <filter-class>
              com.sun.jersey.spi.spring.container.servlet.SpringServlet
          </filter-class>
          <init-param>
              <param-name>javax.ws.rs.Application</param-name> <!-- Specify application class here -->
              <param-value>com.foo.MyApplication</param-value>
          </init-param>
      </filter>
      

      然后在应用程序类中我不得不稍微改变调用超级构造函数的方式:

      public MyApplication() {
          super("com.foo", "com.bar"); // Pass in packages as separate params
      
          HashMap<String, Object> settings = new HashMap<String, Object>(1);
          settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
          this.setPropertiesAndFeatures(settings);
      }
      

      仍然不完全是我所追求的,但至少这会在 Java 代码和 web.xml 中引入更多配置,这对我来说很重要,因为我试图隐藏这个细节。

      【讨论】:

        【解决方案3】:

        我想到了两个选项(没有双关语)。

        1. 也许您可以使用自己的类扩展 SpringServlet 并向其添加适当的 servlet 3.0 注释。
        2. 随着您从SpringServlet 切换到Application 类的方法,您可以通过启用Spring 构建时或加载时字节码编织来解决无自动装配问题。这使 Spring 能够注入由任何地方实例化的对象,而不仅仅是由 Spring 创建的对象。见"Using AspectJ to dependency inject domain objects with Spring"

        【讨论】:

          【解决方案4】:

          首先,在 servlet 3.0 容器中,您实际上并不需要 web.xml

          但是使用 Jersey 2.0,您可以设置一个标志来扫描整个 Web 应用程序以查找带注释的资源:

          <listener>
              <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
          </listener>
          
          <servlet>
              <servlet-name>jersey</servlet-name>
              <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
              <init-param>
                  <param-name>jersey.config.servlet.provider.webapp</param-name>
                  <param-value>true</param-value>
              </init-param>
              <load-on-startup>1</load-on-startup>
          </servlet>
          

          如果您包含此 jar,Spring 将自动启用:

              <dependency>
                  <groupId>org.glassfish.jersey.ext</groupId>
                  <artifactId>jersey-spring3</artifactId>
                  <version>2.3.1</version>
              </dependency>
          

          【讨论】:

          • 感谢您的回答,但我使用的是 Jersey 1,加载和运行 Jersey 资源没有任何问题。问题是我希望它们由 Spring 管理,所以我可以自动装配。
          • 好的,在这种情况下,这不是您的解决方案。但要明确一点:这确实启用了 Spring 自动装配。
          【解决方案5】:

          我在之前使用 SpringMVC 制作的项目中使用了 Jersey。我的代码基于Spring's official documentation

          public class WebAppInitializer implements WebApplicationInitializer {
          
          @Override
          public void onStartup(ServletContext servletContext) {
              // Don't create the Listener that Jersey uses to create. 
              // There can only be one linstener
              servletContext.setInitParameter("contextConfigLocation", "<NONE>");
              AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
          
              // Add app config packages
              context.setConfigLocation("config.package");
          
              // Add listener to the context
              servletContext.addListener(new ContextLoaderListener(context));
          
              // Replacing:
              //      <servlet-name>ServletName</servlet-name>
              //      <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
              //      <init-param>
              //          <param-name>com.sun.jersey.config.property.packages</param-name>
              //          <param-value>webservices.packages</param-value>
              //      </init-param>
              //      <load-on-startup>1</load-on-startup>
              AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
          
              ServletRegistration.Dynamic appServlet = servletContext.addServlet("ServletName", new DispatcherServlet(dispatcherContext));
              appServlet.setInitParameter("com.sun.jersey.config.property.packages", "org.sunnycake.aton.controller");
              appServlet.setLoadOnStartup(1);
              appServlet.addMapping("/RootApp");
          
          }
          }
          

          config.package中的配置类有:

          // Specifies that there will be bean methods annotated with @Bean tag
          // and will be managed by Spring
          @Configuration
          
          // Equivalent to context:component-scan base-package="..." in the xml, states
          // where to find the beans controlled by Spring
          @ComponentScan(basePackages = "config.package")
          public class AppConfig {
          
              /**
               * Where will the project views be.
               *
               * @return ViewResolver como el XML
               */
              @Bean
              public ViewResolver viewResolver() {
                  InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
                  return viewResolver;
              }
          
          }
          

          休眠配置

          // Specifies that there will be bean methods annotated with @Bean tag
          // and will be managed by Spring
          @Configuration
          // Equivalent to Spring's tx in the xml
          @EnableTransactionManagement
          
          // Equivalent to context:component-scan base-package="..." in the xml, states
          // where to find the beans controlled by Spring
          @ComponentScan({"config.package"})
          
          // Here it can be stated some Spring properties with a properties file
          @PropertySource(value = {"classpath:aplicacion.properties"})
          public class HibernateConfig {
          
              /**
               * Inyected by Spring based on the .properties file in the
               * \@PropertySource tag.
               */
              @Autowired
              private Environment environment;
          
              /**
               * Here it's created a Session Factory, equivalent to the Spring's config file one.
               *
               * @return Spring Session factory
               */
              @Bean
              public LocalSessionFactoryBean sessionFactory() {
                  LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
          
                  // Uses the datasource
                  sessionFactory.setDataSource(dataSource());
          
                  // Indicates where are the POJOs (DTO)
                  sessionFactory.setPackagesToScan(new String[]{"dto.package"});
                  // Se asignan las propiedades de Hibernate
                  sessionFactory.setHibernateProperties(hibernateProperties());
          
                  return sessionFactory;
              }
          
              /**
               * Propiedades de la base de datos (Según environment)
               *
               * @return Nuevo DataSource (Configuración de la base de datos)
               */
              @Bean
              public DataSource dataSource() {
                  DriverManagerDataSource dataSource = new DriverManagerDataSource();
                  dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
                  dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
                  dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
                  dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
                  return dataSource;
              }
          
              /**
               * Hibernate properties
               *
               * @return Properties set with the configuration
               */
              private Properties hibernateProperties() {
                  Properties properties = new Properties();
                  // Dialect (Mysql, postgresql, ...)
                  properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
                  // Show SQL query
                  properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
                  properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
                  return properties;
              }
          
              /**
               * Inyected by sessionFactory
               */
              @Bean
              @Autowired
              public HibernateTransactionManager transactionManager(SessionFactory s) {
                  HibernateTransactionManager txManager = new HibernateTransactionManager();
                  txManager.setSessionFactory(s);
                  return txManager;
              }
          }
          

          【讨论】:

            【解决方案6】:

            这是一个完整的例子。首先 - 不要使用任何 web.xml。仅使用下面的代码。

            休息资源:

            @Path("hello")
            public class HelloResource {
            
                @GET
                @Produces(MediaType.TEXT_PLAIN)
                public Response hello() {
                    String output = "Hello World!";
                    return Response.status(200).entity(output).build();
                }
            }
            

            Rest 应用程序(注意包名中的“core”)

            @ApplicationPath("rest")
            public class RestApplication extends javax.ws.rs.core.Application {
            
                public RestApplication() {
                }
            
                @Override public Set<Class<?>> getClasses() {
                    return Set.of(
                        HelloResource.class
                    );
                }
            }
            

            Spring 网络配置。

            @Configuration
            @EnableWebMvc
            @ComponentScan(basePackageClasses = {
                
            })
            public class WebConfig implements WebMvcConfigurer {
            
                private static final Logger logger = LoggerFactory.getLogger(WebConfig.class);
            
                @Autowired
                private ApplicationContext applicationContext;
            
                public WebConfig() {
                }
            }
            

            弹簧初始化器

            //This @Order is required!!!
            @Order(Ordered.HIGHEST_PRECEDENCE)
            public class MyWebInitializer implements WebApplicationInitializer {
            
                @Override
                public void onStartup(ServletContext servletContext) throws ServletException {
                    var ctx = new AnnotationConfigWebApplicationContext();
                    //spring WebMvcConfigurer
                    ctx.register(WebConfig.class);
                    ctx.setServletContext(servletContext);
            
                    //Spring servlet
                    var servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
                    servlet.setLoadOnStartup(1);
                    servlet.addMapping("/");
            
                    // Register Jersey 2.0 servlet
                    ServletRegistration.Dynamic jerseyServlet = servletContext.addServlet("jerseyServlet",
                        "org.glassfish.jersey.servlet.ServletContainer");
                    //note "javax.ws.rs.Application" doesn't have "core"
                    jerseyServlet.setInitParameter("javax.ws.rs.Application", RestApplication.class.getName());
                    jerseyServlet.addMapping("/rest/*");
                    jerseyServlet.setLoadOnStartup(1);
                }
            }
            

            它必须工作,例如,http://127.0.0.1:8080/rest/hello

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-03-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-23
              • 2018-10-27
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多