Spring Boot 1.2 中對 Java EE 的「Bootiful」支援

工程 | 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 伺服器

此範例還使用了 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 執行此應用程式,或輕鬆地將其部署到以流程為中心的 平台即服務 產品 (例如 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 即將正式發布 (GA),現在正是試用、體驗提交問題提問的好時機!

取得 Spring 電子報

隨時掌握 Spring 電子報的最新資訊

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進程。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進制檔案。

了解更多

即將到來的活動

查看 Spring 社群中所有即將舉行的活動。

查看全部