Spring Boot 1.2 中“漂亮”的 Java EE 支持

工程 | Josh Long | 2014年11月23日 | ...

在这篇博客中,我想探讨并演示 Spring Boot 1.2 中的众多新特性,这些特性让来自 Java EE 或在其基础上进行构建的开发者受益匪浅。

值得一提的是,Spring 之前就已经提供了很多这样的支持,但是现在有了 Spring Boot 1.2,它变得非常简单易用!

首先,这是一个带有注释的示例程序。


package demo;

import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.core.JmsTemplate;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import javax.jms.JMSException;
import javax.persistence.*;
import javax.transaction.Transactional;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.io.Serializable;
import java.util.Collection;
import java.util.logging.Logger;

@SpringBootApplication
public class Application {

    @Named
    public static class JerseyConfig extends ResourceConfig {

        public JerseyConfig() {
            this.register(GreetingEndpoint.class);
            this.register(JacksonFeature.class);
        }
    }

    @Named
    @Transactional
    public static class GreetingService {

        @Inject
        private JmsTemplate jmsTemplate;

        @PersistenceContext
        private EntityManager entityManager;

        public void createGreeting(String name, boolean fail) {
            Greeting greeting = new Greeting(name);
            this.entityManager.persist(greeting);
            this.jmsTemplate.convertAndSend("greetings", greeting);
            if (fail) {
                throw new RuntimeException("simulated error");
            }
        }

        public void createGreeting(String name) {
            this.createGreeting(name, false);
        }

        public Collection<Greeting> findAll() {
            return this.entityManager
                    .createQuery("select g from " + Greeting.class.getName() + " g", Greeting.class)
                    .getResultList();
        }

        public Greeting find(Long id) {
            return this.entityManager.find(Greeting.class, id);
        }
    }

    @Named
    @Path("/hello")
    @Produces({MediaType.APPLICATION_JSON})
    public static class GreetingEndpoint {

        @Inject
        private GreetingService greetingService;

        @POST
        public void post(@QueryParam("name") String name) {
            this.greetingService.createGreeting(name);
        }

        @GET
        @Path("/{id}")
        public Greeting get(@PathParam("id") Long id) {
            return this.greetingService.find(id);
        }
    }

    @Entity
    public static class Greeting implements Serializable {

        @Id
        @GeneratedValue
        private Long id;

        @Override
        public String toString() {
            return "Greeting{" +
                    "id=" + id +
                    ", message='" + message + '\'' +
                    '}';
        }

        private String message;

        public String getMessage() {
            return message;
        }

        public Greeting(String name) {
            this.message = "Hi, " + name + "!";
        }

        Greeting() {
        }
    }

    @Named
    public static class GreetingServiceClient {

        @Inject
        private GreetingService greetingService;

        @PostConstruct
        public void afterPropertiesSet() throws Exception {
            greetingService.createGreeting("Phil");
            greetingService.createGreeting("Dave");
            try {
                greetingService.createGreeting("Josh", true);
            } catch (RuntimeException re) {
                Logger.getLogger(Application.class.getName()).info("caught exception...");
            }
            greetingService.findAll().forEach(System.out::println);
        }
    }

    @Named
    public static class GreetingMessageProcessor {

        @JmsListener(destination = "greetings")
        public void processGreeting(Greeting greeting) throws JMSException {
            System.out.println("received message: " + greeting);
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

完整的代码清单,包括非常精简的 application.propertiesMaven 构建文件,都可以在网上找到。

使用 Jersey 的 JAX-RS

该示例 演示了 Boot 新的 JAX-RS 自动配置(在本例中,使用 Jersey 2.x)在 GreetingEndpoint 中。请注意,它的工作原理多么方便!唯一需要注意的是,您需要指定一个 ResourceConfig 子类来让 Jersey 知道要注册哪些组件。

使用 JTA 的全局事务

它演示了使用 新的自动配置的 JTA 支持 的全局事务。JTA 是用于 X/Open XA 协议的 Java API,它允许多个兼容的事务资源(例如消息队列和数据库)参与单个事务。为此,我们使用了 Atomikos 独立 JTA 提供程序。我们也可以轻松使用 Bitronix;如果您使用相应的启动器,这两个程序都会自动配置。在本例中,在 GreetingService 中,JMS 和 JPA 工作作为全局事务的一部分完成。我们通过创建 3 个事务并在第三个事务上模拟回滚来演示这一点。您应该会看到控制台打印出 JDBC javax.sql.DataSource 数据源返回的两条记录和从嵌入式 JMS javax.jms.Destination 目标接收到的两条记录。

Undertow 嵌入式 Web 服务器

此示例还使用来自 RedHat 的 Wildfly 应用服务器的出色 Undertow 嵌入式 HTTP 服务器,而不是(默认的)Apache Tomcat。使用 Undertow 与使用 Jetty 或 Tomcat 一样简单——只需排除 org.springframework.boot:spring-boot-starter-tomcat 并添加 org.springframework.boot:spring-boot-starter-undertow!此贡献源于一个第三方 PR——感谢 Ivan Sopov!太棒了。

其他细节

为了一致性,该示例还使用了 JSR 330。JSR 330 描述了一组注释,您可以在专有应用服务器(如 WebLogic)以及在可移植方式的依赖注入容器(如 Google Guice 或 Spring)中使用这些注释。我还使用 JSR 250 注释(定义为 Java EE 5 的一部分)来演示生命周期钩子。

此示例依赖于 Spring Boot 自动配置和嵌入式 内存 H2 javax.sql.DataSource 和 Spring Boot 自动配置和嵌入式 内存 HornetQ javax.jms.ConnectionFactory。如果您想连接到传统非嵌入式实例,则可以直接在 application.ymlapplication.properties 中指定属性,例如 spring.hornetq.host,或者简单地定义适当类型的 @Bean

此示例还使用了新的 @SpringBootApplication 注释,它组合了 @Configuration@EnableAutoConfiguration@ComponentScan。不错!

部署

虽然此示例使用了许多相当熟悉的 Java EE API,但这仍然是典型的 Spring Boot,因此默认情况下,您可以使用 java -jar ee.jar 运行此应用程序,或者轻松地将其部署到以流程为中心的 平台即服务 产品,例如 Heroku 或 Cloud Foundry。如果您想将其部署到独立的应用服务器(例如 Apache Tomcat、Websphere 或介于两者之间的任何服务器),则可以轻松地将构建转换为 .war 并相应地将其部署到任何 Servlet 3 容器。

如果您将应用程序部署到更经典的应用程序服务器,Spring Boot 可以利用 AS 的功能。例如,使用 JNDI 绑定 JMS ConnectionFactoryJDBC DataSourceJTA UserTransaction 非常简单。

Spring Boot 1.2:选择与强大功能

我个人会质疑很多这些 API。你真的需要分布式、多资源事务吗?在当今 分布式世界中,全局事务管理器被认为是一种架构上的气味。当 Spring 提供更丰富、集成的基于 Spring MVC 的堆栈(包括 MVC、REST、HATEOAS、OAuth 和 Websocket 支持)时,你真的需要使用 JAX-RS 吗?JPA 是一个与基于 SQL 的 javax.sql.DataSource 交互的不错的 API,但是 Spring Data 存储库(当然包括对 JPA 的支持,但也支持 Cassandra、MongoDB、Redis、CouchBase 和越来越多的替代技术)将许多样板代码简化为常见情况下的简单接口定义。那么,你真的需要所有这些吗?你很可能需要,并且——一如既往——选择权在你手中。这就是这个版本如此酷的原因!更强大的功能,更多的选择。

还有什么?

实际上,很多。有很多新特性。我甚至无法在这里全部介绍它们。因此我不会尝试。查看 发行说明 以了解全部内容!

Spring Boot 1.2 正式版即将发布,现在是试用、测试提交问题提问的好时机!

获取Spring通讯

关注Spring通讯

订阅

抢先一步

VMware 提供培训和认证,助您快速提升技能。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区即将举行的所有活动。

查看全部