NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

Spring Boot 深入浅出系列(二) - 用Gradle启动应用

| Comments

在前面介绍的《用Spring Boot开发Spring项目 快速上手》上介绍过:

“通过Spring Boot创建的Java应用可以直接通过java -jar启动(即便它是Web应用)。也就是说,在这里,首先运行gradle assemble,得到jar文件,然后运行java -jar gs-spring-boot-0.1.0.jar。”

但是,我们肯定不能每次这样去启动SpringBoot的应用。好在是,官方提供了与构建相关的插件Spring Boot Gradle plugin,插件中提供了对应的task给你使用:

Application tasks

bootRun - Run the project with support for auto-detecting main class and reloading static resources.
distTar - Bundles the project as a JVM application with libs and OS specific scripts.
distZip - Bundles the project as a JVM application with libs and OS specific scripts.
installApp - Installs the project as a JVM application along with libs and OS specific scripts.
run - Runs this project as a JVM application.

在构建中引入Spring Boot Gradle plugin

首先,你需要在构建中加入该插件:

1
2
3
4
5
6
buildscript {
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.4.RELEASE")
    }
}
apply plugin: 'spring-boot'

省略依赖的版本号

SpringBoot插件会注册一个定制的依赖解析策略,允许你省略对依赖版本的配置。

1
2
3
4
5
dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.thymeleaf:thymeleaf-spring4")
    compile("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect")
}

那么问题来了,这个省略的版本号从哪里来呢?它有Spring Boot Plugin的版本号决定,比如,当前定义的插件是org.springframework.boot:spring-boot-gradle-plugin:1.2.4.RELEASE,版本号是1.2.4,那么对应的spring-boot-starter-web的版本号,也是1.2.4。

当然,如果你想显示的指定版本号也是可以的:

1
2
3
dependencies {
    compile("org.thymeleaf:thymeleaf-spring4:2.1.1.RELEASE")
}

打包可执行的jar包或者war包

一旦使用了Spring boot插件,它就会用bootRepackage任务改写archive的过程。

你可以在配置选项中指定main class,或者在Manifest添加Main-Class,如果你不指定,它会去搜索含有public static void main(String[] args)方法的类。

1
2
3
bootRepackage {
    mainClass = 'demo.Application'
}

如果你想要打成War包,并部署到外部容器里面,除了要使用war插件,还需要将embedded container的依赖放在providedRuntime里。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
apply plugin: 'war'

war {
    baseName = 'myapp'
    version =  '0.5.0'
}

repositories {
    jcenter()
    maven { url "http://repo.spring.io/libs-snapshot" }
}

configurations {
    providedRuntime
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
    ...
}

参考资料:
1.http://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins-gradle-plugin.html

Spring Boot 深入浅出系列(一) - 习惯使用注解

| Comments

Spring Boot从一开始就告诉你,它更喜欢基于Java的配置,即注解的方式。所以它提供了你一大堆注解,并让你习惯使用注解。

@Bean

Indicates that a method produces a bean to be managed by the Spring container.

@Configuration

Indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime

@EnableAutoConfiguration

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined.

@ComponentScan

Configures component scanning directives for use with @Configuration classes. Provides support parallel with Spring XML’s <context:component-scan> element.

指定main application class的位置

SpringBoot建议你将主应用class(main application class)放在包根路径上,即其他子包之上。@EnableAutoConfiguration通常放在你的main class上,这样也隐含的指定了对某些配置项的搜索路径。比如,对@Entity的搜索。

在主应用class上指定@ComponentScan,同样也隐式的指定了扫描时basePackage的路径。

1
2
3
4
5
6
7
8
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
  public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
  }
}

如果你的main application class的位置确实在包的根路径上,上面的三个注解,可以用@SpringBootApplication这一个注解代替。

多种方式加载Bean

你必然不会在main application class定义很多的@Bean,Spring提供两种方式将定义在另外一个带有@Configuration的类中的Bean加载,第一种,在Application类中使用@Import指定该类,第二种,让@ComponentScan扫描到该类。大部分情况都会选择第二种。

加载XML的配置

如果你必须使用XML的配置,你可以使用@ImportResource来加载指定的XML配置。

Bean的自动配置

SpringBoot有一个非常神秘的注解@EnableAutoConfiguration,官方的解释已经在上面的部分给出,简单点说就是它会根据定义在classpath下的类,自动的给你生成一些Bean,并加载到Spring的Context中。

它的神秘之处,不在于它能做什么,而在于它会生成什么样的Bean对于开发人员是不可预知(或者说不容易预知)。举个例子:

要开发一个基于Spring JPA的应用,会涉及到下面三个Bean的配置,DataSource,EntityManagerFactory,PlatformTransactionManager。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
public class ApplicationConfig {
  @Bean
  public DataSource dataSource() {
      ...
  }

  @Bean
  public EntityManagerFactory entityManagerFactory() {
      ..
      factory.setDataSource(dataSource());
      return factory.getObject();
  }

  @Bean
  public PlatformTransactionManager transactionManager() {
      JpaTransactionManager txManager = new JpaTransactionManager();
      txManager.setEntityManagerFactory(entityManagerFactory());
      return txManager;
  }
}

@EnableJpaRepositories会查找满足作为Repository条件(继承父类或者使用注解)的类。

@EnableTransactionManagement的作用:Enables Spring’s annotation-driven transaction management capability, similar to the support found in Spring’s <tx:*> XML namespace。

但是,如果你使用了@EnableAutoConfiguration,那么上面三个Bean,你都不需要配置。在classpath下面只引入了MySQL的驱动和SpringJpa。

1
2
compile 'mysql:mysql-connector-java:5.1.18'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'

在Application类中写下下面这段代码,可以查看SpringBoot给你生成了这些Bean:

1
2
3
4
5
6
7
8
9
10
11
ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
System.out.println("Let's inspect the beans provided by Spring Boot:");

Object dataSource = ctx.getBean("dataSource");
Object transactionManager = ctx.getBean("transactionManager");
Object entityManagerFactory = ctx.getBean("entityManagerFactory");
System.out.println(dataSource);
System.out.println(entityManagerFactory);
System.out.println(transactionManager);
System.out.println(((JpaTransactionManager)transactionManager).getDataSource());
System.out.println(((JpaTransactionManager)transactionManager).getEntityManagerFactory());

输出如下:

1
2
3
4
5
6
7
8
9
org.apache.tomcat.jdbc.pool.DataSource@4f0e94db{ConnectionPool[defaultAutoCommit=null; defaultReadOnly=null; defaultTransactionIsolation=-1; defaultCatalog=null; driverClassName=com.mysql.jdbc.Driver; maxActive=100; maxIdle=100; minIdle=10; initialSize=10; maxWait=30000; testOnBorrow=true; testOnReturn=false; timeBetweenEvictionRunsMillis=5000; numTestsPerEvictionRun=0; minEvictableIdleTimeMillis=60000; testWhileIdle=false; testOnConnect=false; password=********; ... }

org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@5109d386

org.springframework.orm.jpa.JpaTransactionManager@5c1e2bfa

org.apache.tomcat.jdbc.pool.DataSource@4f0e94db{ConnectionPool[defaultAutoCommit=null; defaultReadOnly=null; defaultTransactionIsolation=-1; defaultCatalog=null; driverClassName=com.mysql.jdbc.Driver; maxActive=100; maxIdle=100; minIdle=10; initialSize=10; maxWait=30000; testOnBorrow=true; testOnReturn=false; timeBetweenEvictionRunsMillis=5000; numTestsPerEvictionRun=0; minEvictableIdleTimeMillis=60000; testWhileIdle=false; testOnConnect=false; password=********;... }

org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@5109d386

Bean中的URL,username和password是在属性文件中配置的:

1
2
3
4
#Database
spring.datasource.url=jdbc:mysql://localhost:3306/xxxx      
spring.datasource.username=root
spring.datasource.password=

Disable自动配置

如果你发现自动转配的Bean不是你想要的,你也可以disable它。比如说,我不想要自动装配Database的那些Bean

1
2
3
4
5
6
7
8
9
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class MyConfiguration {
  
}

此时,就会报下面的错了

1
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] found for dependency

习惯使用和正确使用上面这些注解,是正确使用Spring Boot的重要起步。

参考资料:
1.Spring Boot Reference
2.Spring JPA Reference