最近接触spring boot,网上火了好一阵,一开始对这个并不感冒,个人也不是很喜欢将配置写进代码中,所以也就没有去研究它。最近一个项目需要用到spring boot,学习了各路大神的心得后,结合以前的springMVC+spring+mybatis流程,做了一个简单的转换和对比。spring boot这么火热是有道理的,虽然部分配置写进代码,但是非常集中,再也不用在几个配置文件中改来改去,没有一大坨xml配置,jar包运行简直爽。开发热部署极大地提高了开发效率。

maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<properties>
<!--指定jdk和Tomcat版本-->
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<!-- boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.6.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--其它-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc</artifactId>
<version>12c</version>
</dependency>
.....
<!--开启热更新,需要安装下面的插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.6.RELEASE</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency>
</dependencies>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>

上面是spring boot需要的依赖,比传统方式少了很多。

application.yml文件

spring boot的配置文件可以是properties文件,也可以是yml文件,推荐使用yml文件,结构比较清晰。spring boot通过profile可以指定开发模式、发布模式和测试模式下的配置文件,具体结构如下:

1
2
3
4
5
|--resources:
|---application.yml
|---application-dev.yml
|---application-test.yml
|---application-release.yml

application-dev.yml、application-test.yml和application-release.yml分别对应开发模式、测试模式和发布模式的配置文件,这三个文件里面放一些不同环境下配置不同的参数,比如数据库连接等信息。application.yml则放一些三个环境都相同的配置。其中application.yml第一句需要一个配置指定当前的环境

1
2
3
spring:
profiles:
active: dev

如上,指定为开发模式,则application-dev.yml配置文件生效。

spring boot可以通过配置文件来注入属性或者修改默认的配置,也可以通过命令行载入,它支持多种外部配置方式,这些优先级顺序如下(由高到低):

  1. 命令行参数
  2. 来自java:comp/env的JNDI属性
  3. Java系统属性(System.getProperties())
  4. 操作系统环境变量
  5. RandomValuePropertySource配置的random.*属性值
  6. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
  7. jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
  8. jar包外部的application.properties或application.yml(不带spring.profile)配置文件
  9. jar包内部的application.properties或application.yml(不带spring.profile)配置文件
  10. @Configuration注解类上的@PropertySource
  11. 通过SpringApplication.setDefaultProperties指定的默认属性

spring boot内嵌Tomcat,关于Tomcat的配置也可以在配置文件指定,一个包含简单运行环境的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server:
port: ${port:8081}
context-path: /springboot-demo
tomcat:
uri-encoding: UTF-8
spring:
http:
encoding:
charset: UTF-8
enabled: true
force: true
multipart:
enabled: true
max-file-size: 100MB #没个文件大小
max-request-size: 100MB #总上传大小
messages:
encoding: UTF-8

基础配置一些编码格式。context-path:/springboot-demo是配置上下文,默认是/,访问时需要加上下文localhost:8081//springboot-demo/xxx
port: ${port:8081}表示Tomcat默认在端口8081运行,可以通过命令行的方式重置java -jar xxx.jar --port 8084。这是一种简化的命令行配置方式。假如yml的配置是

1
2
server:
port: 8081

需要在命令行重置端口,需要执行java -jar xxx.jar --server.port 8084,如果要修改一个比较长的参数,输入就比较麻烦了。

运行spring boot

1
2
3
4
5
6
7
8
9
10
11
12
@EnableAutoConfiguration
@Configuration
@ComponentScan
@ServletComponentScan
@EnableTransactionManagement
public class Application {
public static void main(String[] args){
SpringApplication app = new SpringApplication(Application.class);
app.setWebEnvironment(true);
app.run(args);
}
}

包含main方法的类必须放在根包下。

Tomcat 相关

filter

在传统方式中,filter是在web.xml文件配置的,在spring boot中简化为注解配置。

1
2
3
public class ParamFilter implements Filter {
xxx
}

传统方式配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<filter>
<filter-name>paramFilter</filter-name>
<filter-class>com.xxx.ParamFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>/res/*,*.html,*.jsp,*.css,*.js,*.png,*.jpg</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>paramFilter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>paramFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>

spring boot

1
2
3
4
5
6
7
8
9
10
11
@WebFilter(
urlPatterns = {"*.action","*.jsp"},
filterName = "paramFilter",
initParams = {
@WebInitParam(name="exclusions",value = "/res/*,*.html,*.jsp,*.css,*.js,*.png,*.jpg")
}
)
@Order(1)
public class ParamFilter implements Filter {
xxx
}

@Order(1)注解是设定filter的执行顺序,越小越先执行,web.xml方式是按照定义的顺序执行。

listener

1
2
3
public class ApplicationServletContextListener implements ServletContextListener {
xxx
}

传统方式配置

1
2
3
<listener>
<listener-class>com.xxx.ApplicationServletContextListener</listener-class>
</listener>

spring boot方式

1
2
3
4
@WebListener
public class IndexListener implements ServletContextListener {
xxx
}

servlet

1
2
3
public class IndexServlet extends HttpServlet {
xxx
}

传统方式

1
2
3
4
5
6
7
8
9
10
11
12
13
<servlet>
<servlet-name>indexServlet</servlet-name>
<servlet-class>org.xxx.indexServlet</servlet-class>
<init-param>
<param-name>location</param-name>
<param-value>xxxx</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>indexServlet</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>

spring boot方式

1
2
3
4
5
6
7
8
9
10
11
@WebServlet(
name = "indexServlet",
urlPatterns = "*.action",
loadOnStartup = 1,
initParams = {
@WebInitParam(name="location",value ="xxxx")
}
)
public class IndexServlet extends HttpServlet {
xxx
}

上面配置完了后,需要在main所在类加一个注解@ServletComponentScan
在spring boot中还有一种通过java bean的方式配置,不如注解简单直观,可自行搜索。此处贴出网上的一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Bean
public ServletRegistrationBean indexServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new IndexServlet());
registration.addUrlMappings("/hello");
return registration;
}
@Bean
public FilterRegistrationBean indexFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean(new IndexFilter());
registration.addUrlPatterns("/");
return registration;
}
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean(){
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
servletListenerRegistrationBean.setListener(new IndexListener());
return servletListenerRegistrationBean;
}

spring boot在使用spring MVC时是不需要配置DispatcherServlet,因为已经自动配置了,如果需要加一些初始配置参数,可以使用bean的方式解决

1
2
3
4
5
6
7
@Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet);
registration.addUrlMappings("*.action");
registration.addUrlMappings("*.jsp");
return registration;
}

上面方法中的DispatcherServlet参数是spring自动注入进来的。

404 500配置

传统方式

1
2
3
4
5
6
7
8
<error-page>
<error-code>500</error-code>
<location>/500.html</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>

spring boot

1
2
3
4
5
6
7
8
9
10
11
12
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer(){
@Override
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");
configurableEmbeddedServletContainer.addErrorPages( error404Page, error500Page);
configurableEmbeddedServletContainer.setRegisterDefaultServlet(true);
}
};
}

Mybatis

在application-{profile}.yml添加配置

1
2
3
mybatis:
mapperLocations: classpath*:com/xxx/**/model/*.xml
configLocation: classpath:mybatis-config.xml

关于mybatis的配置文件还是依照mybatis-config.xml的形式,跟以前一样。

druid数据库连接池

在application-{profile}.yml添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@10.43.164.148:1521:orcl
username: c##ims_dev
password: 1234567890
#连接的其它配置
initialSize: 1
minIdle: 5
maxActive: 50
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 1 from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: false
maxPoolPreparedStatementPerConnectionSize: 20
removeAbandoned: true
removeAbandonedTimeout: 1800
logAbandoned: true
filters: stat,log4j
connectionProperties: druid.stat.slowSqlMillis=5000

由于spring boot内置的数据库连接池不支持druid,所以需要自己创建bean

1
2
3
4
5
6
7
@Bean(initMethod = "init",destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource")
@Primary
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}

上面@ConfigurationProperties(prefix = "spring.datasource")注解是spring boot读取配置的一种方式,将所有以spring.datasource开头的配置读入,再注入到DataSource Bean中。@Primary表示这里定义的DataSource将覆盖其他来源的DataSource。

SqlSession与事务

SqlSessionFactory

spring boot监测到DataSource存在时,会创建并注册SqlSessionFactoryBean实例,并传入Datasource,在mybatis中,sqlsession可以由SqlSessionFactory创建;而在mybatis-spring中则需要SqlSessionFactoryBean来创建,并传入datasource。

1
2
3
4
5
6
7
8
9
<bean id="defaultSQLSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="defaulteDataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations">
<array>
<value>classpath*:com/xxx/**/model/*.xml</value>
</array>
</property>
</bean>

现在,mybatis-spring-boot支持自动创建并注册SqlSessionFactoryBean,所以以上的配置都不需要了

SqlSessionTemplate
SqlSessionTemplate是SqlSession的实现类,较SqlSession的默认实现类DefaultSqlSession来说,是线程安全的。在以前的方式中中需要如下配置

1
2
3
<bean id="defaultSQLSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
<constructor-arg index="0" ref="defaultSQLSessionFactory" />
</bean>

mybatis-spring-boot支持自动创建并注册SqlSessionTemplate,所以不需要以上配置了

使用SqlSession时直接注入就行

1
2
@Autowired
private SqlSession sqlSession;

事务配置

传统方式使用事务配置如下

1
2
3
4
5
6
<!-- 使用JDBC事物 -->
<bean id="defaultTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="defaulteDataSource" />
</bean>
<!-- 使用annotation注解方式配置事务 -->
<tx:annotation-driven transaction-manager="defaultTransactionManager" />

在spring boot中,开启事务简单了许多,在main方法所在类加入@EnableTransactionManagement就开启了事务支持,在service类或方法上添加@Transactional注解,执行的方法就支持事务了。

关于事务管理器,不管是JPA还是JDBC等都实现自接口PlatformTransactionManager,如果添加的是 spring-boot-starter-jdbc依赖,框架会默认注入DataSourceTransactionManager实例。如果你添加的是 spring-boot-starter-data-jpa依赖,框架会默认注入JpaTransactionManager 实例。

mybatis-spring-boot-starter默认依赖了spring-boot-starter-jdbc

实现多个事务管理器可以参考这篇博客 Spring Boot 事务的使用

spring及MVC

1
2
<!-- 扫描控制器类 -->
<context:component-scan base-package="com.xxx.**.controller" />

传统方式中上面这个配置不需要了,在main方法所在类添加@ComponentScan注解,不用指定basePackage。

spring boot中关于MVC部分的配置基本都是通过继承WebMvcConfigurerAdapter类重写其中的方法完成。

1
2
3
4
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter{
}

需要在类上添加注解@Configuration,下面没有特殊说明都是重写该类的方法。

创建bean

传统xml配置方法

1
2
3
4
5
6
7
8
<bean id="appConfigConstant" class="com.xxx.AppConfigConstant" init-method="init" >
<property name="sqlSessionFactory" ref="defaultSQLSessionFactory"></property>
<property name="configTypes" >
<list>
<value>BSSUTILS</value>
</list>
</property>
</bean>

spring boot

1
2
3
4
5
6
7
@Bean(initMethod="init")
public AppConfigConstant createAppConfigConstant(@Autowired SqlSessionFactory sqlSessionFactory) throws Exception{
AppConfigConstant appConfigConstant = new AppConfigConstant();
appConfigConstant.setSqlSessionFactory(sqlSessionFactory);
appConfigConstant.setConfigTypes(Arrays.asList("BSSUTILS"));
return appConfigConstant;
}

拦截器

传统方式

1
2
3
4
5
6
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/*.action"/>
<bean class="com.xxx.XxxxInterceptor" />
</mvc:interceptor>
</mvc:interceptors>

spring boot
实现了一个日志拦截器AccessLogHandlerInterceptor,注册方式如下:

1
2
3
4
5
6
/**日志拦截器*/
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new AccessLogHandlerInterceptor())
.addPathPatterns("/**/*.action");
}

消息转换器

继承自MappingJackson2HttpMessageConverter类实现自定义JSON消息转换器JSONMessageConverter
传统方式

1
2
3
4
5
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.xxx.JSONMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>

spring boot

1
2
3
4
5
/**JSON消息转换器*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new JSONMessageConverter());
}

启用默认servlet

传统方式

1
<mvc:default-servlet-handler />

关于<mvc:default-servlet-handler />可以参考博客【Spring框架】的作用

spring boot

1
2
3
4
5
/**启用默认servlet*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

资源目录映射

传统方式

1
2
<!-- 配置静态资源 -->
<mvc:resources location="/resources/" mapping="/resources/**" cache-period="1024" />

spring boot

1
2
3
4
5
6
7
8
/**设置外部资源目录*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//映射文件上传目录
String rePath = "file:/"+Application.appRuntimePath+"/upload/";
registry.addResourceHandler("/upload/**").addResourceLocations(rePath).setCachePeriod(1024);
}

上面例子中Application.appRuntimePath是jar运行时jar包所在目录(全路径),这属性值是在main方法中动态获取的,目的是在jar包所在目录创建一个文件上传目录,然后在动态映射,以实现文件上传的目的(映射全路径时需要加file:/前缀)。获取方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 发布时获取jar包所在目录
* 开发时获取src目录的上层目录
* @return
*/
private static String getRuntimePath(){
try {
String path = Application.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path,"UTF-8");
String fileSeparator = File.separator;
if(path.contains("/")){
fileSeparator = "/";
}
Pattern pattern = Pattern.compile(fileSeparator+"[-a-zA-Z0-9_]+\\.jar");
Matcher matcher = pattern.matcher(path);
if(matcher.find()){//被打包成jar包运行
int end = matcher.start();
path = path.substring(6,end);
}else{//开发时以IDE运行(maven)
path = path.replace(fileSeparator+"target"+fileSeparator+"classes"+fileSeparator,"");
int end = path.lastIndexOf(fileSeparator);
path = path.substring(1,end);
}
return path;
}catch (Exception e){
e.printStackTrace();
}
return null;
}

仅在windows环境下测试通过,Linux暂未测试。参考自博客 获得执行jar的运行路径

视图解析器

传统方式

1
2
3
4
5
6
7
<!-- jsp视图解析器 -->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/" />
<property name="suffix" value=".html" />
<property name="order" value="1" />
</bean>

spring boot

1
2
3
4
5
6
7
8
9
/**添加视图解析器*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/");
resolver.setSuffix(".html");
resolver.setOrder(1);
registry.viewResolver(resolver);
}

property-placeholder

传统方式

1
<context:property-placeholder location="appconfig.properties" />

spring boot

1
2
3
4
5
6
7
@Bean
public PropertySourcesPlaceholderConfigurer createPropertySourcesPlaceholderConfigurer() {
ClassPathResource resource = new ClassPathResource("appconfig.properties");
PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyPlaceholderConfigurer.setLocations(resource);
return propertyPlaceholderConfigurer;
}