“Bootiful” 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.properties 文件和 Maven 构建,都可以在线获取。

使用 Jersey 集成 JAX-RS

该示例展示了 Boot 新的 JAX-RS 自动配置(此处使用 Jersey 2.x),体现在 GreetingEndpoint 中。注意让这一切工作起来是多么方便!唯一需要留意的是,你需要指定一个 ResourceConfig 的子类来告诉 Jersey 注册哪些组件。

使用 JTA 的全局事务

它展示了如何使用新的自动配置的 JTA 支持来实现全局事务。JTA 是一个用于 X/Open XA 协议的 Java API,它允许多个兼容的事务资源(如消息队列和数据库)参与到同一个事务中。为此,我们使用了 Atomikos 独立的 JTA 提供者。使用 Bitronix 也同样容易;如果你引入了相应的 Starter,两者都会被自动配置。在此示例中,在 GreetingService 中,JMS 和 JPA 工作作为全局事务的一部分执行。我们通过创建 3 个事务并模拟对第三个事务进行回滚来演示这一点。你应该在控制台看到,从 JDBC javax.sql.DataSource 数据源返回了两条记录,并从嵌入式 JMS javax.jms.Destination 目标接收到了两条记录。

Undertow 嵌入式 Web 服务器

此示例还使用了 Wildfly(来自 RedHat)应用服务器的出色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 运行此应用,或者轻松将其部署到以进程为中心的平台即服务 (PaaS) 产品上,例如 Heroku 或 Cloud Foundry。如果你想将其部署到独立的应用程序服务器(如 Apache Tomcat、Websphere 或介于两者之间的任何服务器),可以很直接地将构建转换为 .war 文件,并相应地部署到任何 Servlet 3 容器中。

如果你将应用部署到更传统的应用服务器,Spring Boot 可以利用应用服务器提供的功能。例如,使用 JNDI 绑定的JMS ConnectionFactoryJDBC DataSourceJTA UserTransaction 非常简单。

Spring Boot 1.2:选择与强大

我个人会对许多这些 API 提出疑问。你真的需要分布式、多资源事务吗?在当今的分布式世界中,考虑全局事务管理器是一种架构异味。当 Spring 提供了更丰富、集成的基于 Spring MVC 的技术栈,包含了 MVC、REST、HATEOAS、OAuth 和 websockets 支持时,你真的需要使用 JAX-RS 吗?JPA 是一个很好的 API,用于与基于 SQL 的 javax.sql.DataSource 进行交互,但 Spring Data Repository(当然包含对 JPA 的支持,但支持 Cassandra、MongoDB、Redis、CouchBase 以及越来越多的其他技术)将大部分样板代码简化为常见情况下的简单接口定义。所以,你真的需要这一切吗?很可能你需要,而且 - 一如既往 - 选择权在你。这就是为什么这个版本如此出色!更强的能力,更多的选择。

还有什么?

实际上,内容非常多。有大量新特性。我甚至无法在这里一一涵盖。所以我不会尝试。请查阅发布说明以获取完整详情!

Spring Boot 1.2 即将正式发布 (GA),现在是试用、体验提交问题提问的好时机!

订阅 Spring 新闻通讯

订阅 Spring 新闻通讯,随时了解动态

订阅

提升自己

VMware 提供培训和认证,加速你的发展。

了解更多

获取支持

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

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部