“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 社群的所有近期活動。

檢視全部