Spring Boot 简介

Spring Boot主要是设计用来简化spring开发的。 详细的可以看这里

入门: Hello World

下面我们一步一步的搭建一个spring boot应用, 并实现我们经典的 Hello World。

Maven 配置

pom.xml里, 添加

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

即引入了spring boot。 但是为了我们下面开发的Hello world应用, 还要添加其他依赖的jar:

<!--  配置用于web项目的starter -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<!-- 将应用打包为jar -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

到这里maven的配置已经完成了。 后面会说spring boot的各种starter。

编写代码

写一个最简单的rest接口:

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class Example {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

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

}

然后直接右键-运行, 然后访问http://localhost:8080, 即可看到页面输出了Hello World !

从这个例子可以看出来, 使用spring boot 确实可以大大简化我们的程序开发。 但是上面的例子很简单很简单, 仅仅为了让大家对boot的便利性有个直观的认识。
实际项目中我们会用到很多其他组件, 比如我们DatasourceVelocity, MybatisFilter, Listener,jsp/html页面等。 下面我们一一举例来说。

修改端口

修改端口比较简单, 我们在resources下面建一个config文件夹, 里面新建一个application.properties文件, 在里面即可配置端口

# change server port
server.port=9090

除了application.properties文件, spring boot可以使用yml配置文件, 我们在config目录下建一个application.yml文件,输入:

server:
  port: 9090

也可以改变端口。

添加DataSource

添加数据源也很简单, 首先我们需要添加mysql-connector以及boot-starter-jdbc的依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

然后要在application.properties文件里面添加以下配置:

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.MySQL.jdbc.Driver
spring.datasource.url=jdbc:MySQL://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456

Spring Boot 就会自动创建一个Datasource, 我们在需要的地方直接使用@Autowire 注解注入就可以了。

Spring Boot默认的数据源是使用的tomcat-jdbc, 如果想更换其他的数据源, 比如Druid数据源, 只需要加入Druid的依赖, 然后在application.properties中再添加一行:

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

就可以了。 其他的数据源的配置, 比如最大连接数什么的,
可以参考Spring Boot 所有配置

添加Mybatis

添加Mybatis相对来说复杂一点,Mybatis官方有对Spring Boot的支持, 源码可以在Github找到。
假如我们有一个city表, 有 id,name,postcode, province等属性。

我总结出来有三种方式可以添加Mybatis支持():

  1. 注解形式。这是一种比较简单的方式, 我们直接在接口上面添加@Mapper注解, 在方法上面配置@select即可, 示例如下:

     @Mapper
     public interface CityMapper {
    
         @Select("select * from city where id=#{id}")
         City findCity(@Param("id") String id);
    
     }
    

    然后在对应的service里面直接注入CityMapper即可.

  2. xml配置方式。
    相对于上面的方式,这种方式更加灵活, 毕竟我们使用mybatis主要就是为了将SQL与代码分离以及动态SQL特性,如果使用注解的话这两个优势都没了,还不如用JPA。
    所以我们需要自定义mybatis-config.xml, 自定义CityMapper.xml等配置文件。

    首先我们先看一下用这种方法, Mapper 应该怎么写:

     @Mapper
     public interface CityMapper {
    
         City findCity(@Param("id") String id);
    
     }
    

    可以看到与第一种方式相比, 仅仅移除了@select注解, 以分离SQL与代码。

    然后我们在resources目录下创建mybatis-config.xml, 内容如下:

     <?xml version="1.0" encoding="UTF-8" ?>
     <!DOCTYPE configuration
             PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-config.dtd">
     <configuration>
         <mappers>
             <mapper resource="mybatis/mappers/CityMapper.xml"/>
         </mappers>
     </configuration>
    

    然后在application.properties配置文件添加一行:

     mybatis.config-location=classpath:mybatis-config.xml
    

    然后在resources目录下创建新目录mybatis/mappers用于存放mybatis的mapper文件。 然后再此目录下创建CityMapper.xml, 内容如下:

     <?xml version="1.0" encoding="UTF-8" ?>
     <!DOCTYPE mapper
             PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     <mapper namespace="com.qunar.adam.boot.mybatis.xml.CityMapper">
         <select id="findCity" resultType="com.qunar.adam.boot.mybatis.City">
             select * from city where id = #{id}
         </select>
     </mapper>
    

    到这里mybatis也就添加完成了, 在需要的地方注入CityMapper即可。

  3. 代码配置方式。 上面的两种方式都是mybatis官网给出的方案。 详见这里
    第一种方式, 抹杀了mybatis的最主要的优势, 显然我们不会用它。 第二种方式, 虽然灵活了, 但是还是有点繁琐。 每次增加一个Mapper,
    都需要将xml所在路径添加到mybatis-config.xml。 那么能不能像普通的spring与mybatis结合的那种方式, 只要配置一下mybatis的xml所在目录就可以呢?
    答案也是肯定的, 这个时候就需要用代码配置了。

    我们回忆一下在普通的spring应用中是怎么集成mybatis的: 首先我们配置一个SqlSessionFactoryBean, 给它赋予datasource和mapperLocations属性就可以了, 就像这样:

     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
         <property name="dataSource" ref="dataSource"/>
         <property name="mapperLocations">
             <list>
                 <value>classpath*:mybatis/mappers/*.xml</value>
             </list>
         </property>
         <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
     </bean>
    

    同样, 我们在spring boot里面也可以这么干, 只不过是用代码的形式:

     @Configuration
     @EnableTransactionManagement
     public class MybatisConfig {
         private static final Logger logger = LoggerFactory.getLogger(MybatisConfig.class);
    
         /**
          * xml 配置文件的位置
          */
         @Value("${boot.mybatis.mapperfile.location}")
         private String mapperFolder;
    
         @Bean
         public MybatisResource mybatisResource() throws IOException {
             ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
             String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + mapperFolder + "/**/*.xml";
             Resource[] resources = resourcePatternResolver.getResources(pattern);
             return new MybatisResource(resources);
         }
    
         @Autowired
         @Bean
         public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource, MybatisResource resource){
             SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
             sqlSessionFactoryBean.setDataSource(dataSource);
             sqlSessionFactoryBean.setMapperLocations(resource.getMapperResources());
             return sqlSessionFactoryBean;
         }
     }
    

    我们来分析一下上面的代码, 首先我们在spring context里面注册了一个MybatisResource来指示mybatis配置文件的目录, 然后手动创建了SqlSessionFactoryBean并将他注册到spring中,
    这样一来我们就又可以像以前那样使用mybatis了。 其中MybatisResource是自定义的一个类。 源码如下:

     public class MybatisResource {
    
         private Resource[] mapperResources;
    
         public MybatisResource(){}
    
         public MybatisResource(Resource[] mapperResources){
             this.mapperResources = mapperResources;
         }
    
         public Resource[] getMapperResources() {
             return mapperResources;
         }
    
         public void setMapperResources(Resource[] mapperResources) {
             this.mapperResources = mapperResources;
         }
     }
    

    当然, 别忘了在Mapper的interface上面加上@Mapper注解。

添加Servlet,Filter与Listener

在普通web项目中很容易就能添加filter以及listener, 那么在spring boot中应该怎么操作呢? 其实也简单, spring boot有3种方式来添加。

  1. Spring Boot 自动添加, 我们只需要正常实现一个FilterListener, 然后给这个实现类加上@Service注解交给spring就可以了。
    对于Filter, boot会自动匹配路径/*.

  2. 如果想要更灵活一点的, 可以使用ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean来通过代码注册。 这种方式稍微繁琐一些
    不过灵活。 首先我们肯定还是先正常实现Servlet,Filter,Listener, 然后新建一个WebConfig的类(类名随意), 代码如下:

     @Configuration
     public class WebConfig {
    
         /**
          * 注册Servlet
          * @return
          */
         @Bean
         public ServletRegistrationBean servletRegistrationBean() {
             ServletRegistrationBean bean = new ServletRegistrationBean();
             bean.setName("mine");//设置servlet名称
             bean.setLoadOnStartup(1);//
             bean.setServlet(new MyServlet1());//servlet的实现
             bean.setUrlMappings(Collections.singletonList("/mine"));//url mapping
             return bean;
         }
    
         /**
          * 注册Filter
          * @return
          */
         @Bean
         public FilterRegistrationBean filterRegistrationBean() {
             FilterRegistrationBean bean = new FilterRegistrationBean();
             bean.setName("mine-filter");//filter 名称
             bean.setFilter(new MyFilter());//filter的实现
             bean.setUrlPatterns(Collections.singletonList("/mine"));//url mapping
             return bean;
         }
     }
    

    这样就注册了Servlet,FilterListener.

  3. 使用注解来注册。 Spring Boot 提供了@WebServlet, @WebFilter, and @WebListener来注册。 这种方式要求我们在启动类上面加上@ServletComponentScan注解。
    首先还是正常实现Servlet,FilterListener, 然后我们在Servlet上面添加注解@WebServlet(urlPatterns = "/mine"),
    Filter 上面添加@WebFilter(urlPatterns = "/mine"), 在Listener上面添加@WebListener, 最后在启动类上面添加@ServletComponentScan 即可。 既方便又灵活。

关于JSP

Spring Boot 是不推荐使用JSP的, 因为内嵌的tomcat对JSP支持并不好, 有一些已知的限制,
这其中就提到了如果使用内嵌的tomcat,并且将spring boot打成jar包运行, 是不支持jsp的, 因为tomcat内部存在文件路径匹配的硬编码。 而如果把spring boot打成war包,
运行在tomcat容器内,则是支持jsp的。 关于如何设置JSP, 参考这里的例子, 就不展开了。

关于静态内容

默认情况下, Spring Boot 将classpath下面的/static or /public or /resources or /META-INF/resources 作为静态资源目录, 我们可以在Maven项目中的resources目录下
创建上面的目录存放静态资源。 例如我们的目录结构如下:

|src/main/
|-  java/
|-  resources/
|-      static/
|-          html/
|-              index.html
|-          js/
|-              hello.js

那么要访问index.html的地址就是http://localhost:8080/html/index.html, 也就是说静态资源目录是相对于http访问的根/路径, 不过可以通过配置修改,
application.properties添加如下配置

spring.mvc.static-path-pattern=/mystatic/**

那么访问地址就对应的变成了http://localhost:8080/mystatic/html/index.html

添加Redis

在Maven中添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后在application.properties添加配置:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123

如果是sentinel模式, 则添加如下配置:

spring.redis.sentinel.master= # Name of Redis server.
spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs.

其他redis配置信息可以参考SpringBoot所有配置

配置完成后即可在需要的地方注入RedisTemplate来操作Redis。

其他如集成Mongodb,ES,AMQP 与此差不多。

集成Dubbo

下面重点来了, 上面说的其实大部分官方文档都有。 那么dubbo作为一个国产组件, spring boot是没有做对它的支持的, 那么应该怎么集成到boot里面呢?

我们知道dubbo支持xml配置、注解配置、API配置。 所以第一种思路就是使用dubbo的API配置结合Sprring Boot的@Configuration代码配置。 不过这种方式比较繁琐, Service多了很麻烦。

用简化一些的方法, github上面有个同学实现了一个spring-boot-starter-dubbo, 代码在这里
代码实现比较简单,用起来也很简单,具体的使用方法可以看这个文档写的很清晰, 这里就不重复了。

关于 spring boot starters

上面我们也用到了一些starters, 比如spring-boot-starter-web,spring-boot-starter-jdbc
大家应该也能大概感觉到这个starter是个什么东西。 其实starter是一个空jar包, 仅仅就是一组依赖的集合,用于方便简单的为spring boot应用添加功能组件。 比如starter-jdbc
为应用添加访问数据库的能力, starter-data-redis提供了访问redis的能力, starter-data-web提供了web组件,作为web应用运行。 关于所有的sttarter,
可以参考这里的官方文档

编写自己的AutoConfiguration

我们上面介绍的各种starter, 其实它们都有一个auto-configuration的实现, 比如redis有RedisAutoConfiguration, mongodb有MongoAutoConfiguration...
有了这些auto-configuration, spring boot就有了自动配置的能力。 那么我们怎么写自己的auto-configuration呢?

本质上来说, auto-configuration 还是实现了标准的@Configuration, 然后@Conditional用于约束自动配置是否应该生效, 什么情况下生效。 一般来说, auto-configuration常用的
注解是@ConditionalOnClass@ConditionalOnMissingBean。 第一个注解的意思是只有classpath下存在此Class的时候才生效, 第二个注解表示没有此Class的实例的时候才生效。
这样就保证了能创建bean但不会重复创建, 所以用户可以重写默认的自动配置。

  1. 注册auto-configuration。

    spring boot 通过检测jar包内的META-INF/spring.factories文件来检测auto-configuration,
    自定义的auto-configuration应该配置在org.springframework.boot.autoconfigure.EnableAutoConfiguration下面:

     org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
     com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
     com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
    

    另外还可以使用@AutoConfigureAfter or @AutoConfigureBefore 来指定在某个类之前或者之后配置。

  2. 使用@Conditional进行约束。

    1. 检测类是否存在的@ConditionalOnClass@ConditionalOnMissingClass。 这两种注解可以在value属性传入目标Class对象。 由于注解
      是使用ASM(字节码库)来解析的, 所以即使实际运行的classpath不存在目标Class对象也可以正常工作。

    2. 检测bean是否存在的@ConditionalOnBean@ConditionalOnMissingBean。 这两个注解用于检测spring是否存在指定的bean, 可以用value属性按照type识别或者使用name属性
      按照bean name识别。

      使用这两个注解需要注意, bean是否存在是在当前执行的节点判断的, 也就是有顺序问题。 所以推荐的做法是, 把这两个注解仅仅用在auto-configuration上面。

    3. 检测Spring上下文环境是否存在指定属性的@ConditionalOnProperty注解。默认情况下只要属性存在并且不是false都会认为存在。

    4. 检测资源是否存在的@ConditionalOnResource

    5. 检测是否为web项目的@ConditionalOnWebApplication@ConditionalOnNotWebApplication。 如果一个应用使用了WebApplicationContext,
      或者定义了StandardServletEnvironment就被认为是web项目。

    6. 检测SpEL表达式的@ConditionalOnExpression注解。

  3. 创建自己的starter

    一个完整的Spring Boot starter包含两个模块:

    1. autoconfigure模块, 主要包含auto-configure的代码。
    2. starter模块, 主要是定义一些依赖关系。

    当然两个模块也可以合并为一个。

  4. 例子。 spring-boot-starter-dubbospring-boot-starter-mybatis

部署

jar包部署

打成jar包比较简单, 在maven中添加依赖:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>com.test.adam.boot.filter3.Application</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

其中configuration 标签可以省略如果项目中只有一个main方法。 然后使用mvn package -DskipTests 即可打成可运行的jar, 使用java -jar -server xxxx.jar 运行就行了。

war包部署

除了支持打成可运行jar包之外, Spring Boot还支持构建war包。 详细步骤请参考官方文档

关于Profile

Spring Boot配置文件支持profile, 我们可以创建三个application-dev.properties,application-test.properties,application-pro.properties来分别对应本地、
测试环境、线上三个profile, 其中跟环境有关的比如数据源、redis等信息分别配置在各个环境对应的配置文件里。 把公用的配置写在application.properties文件里。 启动jar的时候,
通过参数 -Dspring.profiles.active=dev来启用对应的配置文件。

原创文章,转载请注明出处。 如发现文章有误, 请联系作者

Q.E.D.