原文链接:https://dzone.com/articles/why-springboot
作者注:本文概述不同的Spring配置方式并帮助你理解复杂的Spring应用配置。
Spring是一个非常流行的、基于Java语言的开发框架,常用于开发web和企业应用。不同于其它在某一领域特别出名的框架,Spring通过一系列的项目为不同的业务需求提供了许多特性。
Spring提供诸如XML、Annotations和JavaConfig等方式来提高配置java beans的灵活性。随着Spring特性数量的不断增加,其复杂性也不断增加,这导致Spring应用的配置变得冗长且容易出错。
Spring团队创建SpringBoot项目来处理愈发复杂的配置问题。
在深入SpringBoot之前,我们先快速浏览一下Spring框架并理解SpringBoot试图去解决的问题
本文将涉及如下内容:
- Spring框架概述
- 使用Spring MVC和JPA(Hibernate)的web应用
- SpringBoot快速体验
Spring框架概述
如果你是一名Java开发者,你很有可能听说并且在你自己的项目中使用过Spring框架。Spring框架主要用于提供 依赖注入容器,但功能绝不限于此。
Spring流行的原因:
- Spring的依赖注入方法鼓励写出可测试 的代码
- 强大而且易用的数据库事务管理能力
- Spring易与诸如JPA/Hibernate ORM、Struts/JSF等web框架集成
- 先进的Web MVC框架用于构建web应用
与Spring框架一起,还有许多Spring的项目用于构建满足不同业务需求的应用:
- Spring Data:简单的关系型和NoSQL数据库访问框架
- Spring Batch:批处理框架
- Spring Security:安全框架
- Spring Social:用于支持与Facebook、Twitter、LinkedIn、GitHub等社交网络站点的集成
- Spring Integration:采用轻量级消息框架和公开适配接口,用于与其他企业应用集成的集成框架
此外,还有许多用于满足不同应用开发冯需求的项目,详见:http://spring.io/projects
.
一开始的时候,Spring框架采用基于XML的方法进行java beans的配置。随后,Spring又引入了基于XML的DSLs、Annotations和JavaConfig。
让我们快速看一下这些方法的配置风格。
基于XML的配置方式
<bean id="userService" class="com.sivalabs.myapp.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.sivalabs.myapp.dao.JdbcUserDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="secret"/>
</bean
基于注解的配置方式
@Service
public class UserService
{
private UserDao userDao;
@Autowired
public UserService(UserDao dao){
this.userDao = dao;
}
...
...
}
@Repository
public class JdbcUserDao
{
private DataSource dataSource;
@Autowired
public JdbcUserDao(DataSource dataSource){
this.dataSource = dataSource;
}
...
...
}
基于JavaConfig的配置方式
@Configuration
public class AppConfig
{
@Bean
public UserService userService(UserDao dao){
return new UserService(dao);
}
@Bean
public UserDao userDao(DataSource dataSource){
return new JdbcUserDao(dataSource);
}
@Bean
public DataSource dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("secret");
return dataSource;
}
}
哇...Spring在bean配置一事上提供了如此多的方法,我们甚至可以在同一个应用中混用不同的方法。
如此灵活的配置方式是一把双刃剑。Spring框架的初学者会对使用哪种方式感到困惑。现在Spring团队建议使用基于JavaConfig的配置方式,因为这种方式提供了更高的灵活性。
但是,没有哪种方式能够适合所有类型的解决方案我们需要根据自己应用的需要选择合适的配置方式。
好了,现在你已经了解到多种Spring配置beean的方式。
接下来,让我们看一个典型的Spring MVC + JPA/Hibernate的web应用的配置。
使用Spring MVC和JPA(Hibernate)的web应用
在开始了解SpringBoot及其提供的特性之前,让我们先看一个典型的Spring web应用程序的配置,看一下配置的痛点所在,然后我们将讨论SpringBoot是如何解决这些问题的。
第一步:配置Maven依赖
我们需要做的第一件事是在pom.xml中配置所有需要的依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sivalabs</groupId>
<artifactId>springmvc-jpa-demo</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>springmvc-jpa-demo</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.9.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.190</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.11.Final</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
</dependencies>
</project>
通过配置,我们引入依赖了Spring MVC, Spring Data JPA, JPA/Hibernate, Thymeleaf和Log4j。
第二步:使用JavaConfig配置Service/DAO层
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages="com.sivalabs.demo")
@PropertySource(value = { "classpath:application.properties" })
public class AppConfig
{
@Autowired
private Environment env;
@Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer()
{
return new PropertySourcesPlaceholderConfigurer();
}
@Value("${init-db:false}")
private String initDatabase;
@Bean
public PlatformTransactionManager transactionManager()
{
EntityManagerFactory factory = entityManagerFactory().getObject();
return new JpaTransactionManager(factory);
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory()
{
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(Boolean.TRUE);
vendorAdapter.setShowSql(Boolean.TRUE);
factory.setDataSource(dataSource());
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.sivalabs.demo");
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
factory.setJpaProperties(jpaProperties);
factory.afterPropertiesSet();
factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
return factory;
}
@Bean
public HibernateExceptionTranslator hibernateExceptionTranslator()
{
return new HibernateExceptionTranslator();
}
@Bean
public DataSource dataSource()
{
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
return dataSource;
}
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource)
{
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.addScript(new ClassPathResource("data.sql"));
dataSourceInitializer.setDatabasePopulator(databasePopulator);
dataSourceInitializer.setEnabled(Boolean.parseBoolean(initDatabase));
return dataSourceInitializer;
}
}
在AppConfig.java配置类中,我们做了如下配置:
- 使用@Configuration注解将自身标注为Spring配置类
- 通过@EnableTransactionManagement标注启用事务管理
- 配置@EnableJpaRepositories标明何处寻找Spring Data JPA仓库
- 使用@PropertySource注解和PropertySourcesPlaceholderConfigurer类读取配置文件application.properties
- 定义DataSource、JPA EntityManagerFactory和JpaTransactionManager为java bean
- 配置DataSourceInitializer bean在应用启动时通过data.sql脚本初始化数据库
application.properties中的配置信息如下:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=admin
init-db=true
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update
创建简单的SQL脚本data.sql将数据导入USER表:
delete from user;
insert into user(id, name) values(1,'Siva');
insert into user(id, name) values(2,'Prasad');
insert into user(id, name) values(3,'Reddy');
创建log4j.properties配置文件如下:
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %t %c{2}:%L - %m%n
log4j.category.org.springframework=INFO
log4j.category.com.sivalabs=DEBUG
第三步:配置Spring MVC Web层beans
配置Thymeleaf的ViewResolver,静态ResouceHandlers和用于国际化i18n的MessageSource。
@Configuration
@ComponentScan(basePackages = { "com.sivalabs.demo"})
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter
{
@Bean
public TemplateResolver templateResolver() {
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
templateResolver.setCacheable(false);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver();
thymeleafViewResolver.setTemplateEngine(templateEngine());
thymeleafViewResolver.setCharacterEncoding("UTF-8");
return thymeleafViewResolver;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
{
configurer.enable();
}
@Bean(name = "messageSource")
public MessageSource configureMessageSource()
{
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setCacheSeconds(5);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
在WebMvcConfig.java类中,我们做了如下操作:
- 使用@Configuration注解将其标注为Spring配置类
- 使用@EnableWebMvc注解启用基于Spring MVC的注解
- 通过注册TemplateResolver、SpringTemplateEngine和ThymeleafViewResolver,配置Thymeleaf的ViewResolver
- 注册资源处理bean来标明来自URI/resources/**的请求会访问位于/resources/目录的资源
- 配置MessageSource加载类路径上的messages-{country-code}.properties为i18n消息
现在我们没有配置任何消息,那就在src/main/resources文件夹下创建一个空的messages.properties文件。
第四步:注册Spring MVC 前端控制servlet-DispatcherServletf
Servlet 3.x之前的版本我们需要在web.xml中注册servlet或者filter。Servlet 3.x及其之后的版本我们可以使用ServletContainerInitializer注册Servlet和filter。
Spring MVC提供了一个方便的类:AbstractAnnotationConfigDispatcherServletInitializer用来注册DispatcherServlet。
public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
@Override
protected Class<?>[] getRootConfigClasses()
{
return new Class<?>[] { AppConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses()
{
return new Class<?>[] { WebMvcConfig.class };
}
@Override
protected String[] getServletMappings()
{
return new String[] { "/" };
}
@Override
protected Filter[] getServletFilters() {
return new Filter[]{ new OpenEntityManagerInViewFilter() };
}
}
在SpringWebAppInitializer.java类中我们做了如下事情:
- 配置AppConfig.class作为RootConfirationClasses,它是包含所有子上下文(DispatcherServlet)bean定义的父ApplicationContext。
- 配置WebMvcConfig.class作为ServletConfigClasses,它是包括WebMvc bean定义的子ApplicationContext。
- 配置ServletMapping为"/",让DispatcherServlet处理所有请求。
- 注册OpenEntityManagerInViewFilter过滤器,用于展开视图时懒加载JPA实体集合。
第五步:创建JPA实体和Spring Data JPA repository
创建JPA实体User.java和User实体对应的Spring Data JPA repository。
@Entity
public class User
{
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
//setters and getters
}
public interface UserRepository extends JpaRepository<User, Integer>
{
}
第六步:创建Spring MVC控制器
创建SpringMVC控制器来处理"/"URL请求并返回用户列表。
@Controller
public class HomeController
{
@Autowired UserRepository userRepo;
@RequestMapping("/")
public String home(Model model)
{
model.addAttribute("users", userRepo.findAll());
return "index";
}
}
第七步:创建Thymeleaf视图:/WEB-INF/views/index.html来展示用户列表
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>Home</title>
</head>
<body>
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}">Id</td>
<td th:text="${user.name}">Name</td>
</tr>
</tbody>
</table>
</body>
</html>
我们准备好运行程序啦。但在此之前,我们需要下载和配置诸如Tomcat或者Jetty或者Wilddfly等容器。
你可以下载Tomcat 8并在你喜欢使用的IDE中配置并运行。然后打开浏览器访问http://localhost:8080/springmvcjpa-demo
。你可以看到user的详细列表。
耶...我们做到了。
但是等等..仅仅从数据库获取数据并显示用户列表需要如此繁杂的工作吗?
让我们诚实而公正的看待这个问题。所有的配置工作不都是为了这个简单的用例。这些配置内容同样是应用程序配置的一部分。
但是,在迅速启动和运行之前仍有许多工作要做。
还有一个问题,假设你想使用相同的技术栈开发基于Spring MVC的另一个程序 。你复制黏贴配置并调整代码。但是记住一件事情:如果你必须重复做同样的事情,你应该去寻找一种自动化的方法。
除了重复写同样的配置,你还发现其他问题了吗?
好了,下面列出我发现的问题。
- 你需要寻找依赖的特定版本的spring库文件并配置它们。
- 我们配置DataSource、EntitymanagerFactory和TransactionManager等beans的工作,有95%的可能性是相同的,假如Spring可以自动配置的,那样会不会更酷?
- 我们用相同的方法配置许多SpringMVC bean,如ViewResolver、MessageSource等。
如果Spring可以为我自动做这些工作,那该有多酷!!!
设想一下,如果spring能够自动配置bean会发生什么?如果你可以使用简单的配置化文件定制自动规则会发生什么?
例如,你想把url的模式从"/"变更为"/app/",你想把"/WEB-INF/views"文件夹下的Thymeleaf
展示文件放到"/WEB-INF/templates/"文件夹下。
因此,你需要Spring即能够自动化配置又能够用简单的方式提供默认配置上的灵活的可配置性。
好了,你可以进入可以让你梦想成真的Spriing Boot的世界了。
Spring Boot快速尝鲜
欢迎来到Spring Boot,它正是你在寻找的技术。它可以为你自动化配置并且允许你覆盖默认配置。
我选择用具体的实例来介绍Spring Boot,而不是理论知识。
因此,让我们使用spring boot来实现与上面相同的应用。
第一步:创建基于Maven的Spring Boot项目
创建Maven项目饼配置如下依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sivalabs</groupId>
<artifactId>hello-springboot</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>hello-springboot</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
哇,我们的pom.xml瞬间变小了很多。
第二步:在applicationroperties中配置Datasource数据源/JPA配置文件
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.initialize=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
你可以拷贝data.sql放入src/main/resources文件夹。
第三步:创建JPA实体和spring data JPA repository接口
创建与使用Spring MVC项目一样的fUserva/UserRepositoryva和HomeController.java。
第四步:创建展示用户列表的Thymeleaf视图
拷贝springmvc-jpa-demo应用中的/WEB-INF/views/index.html到新工程的src/main/resources/templates文件夹。
第五步:创建Spring Boot入口类
创建含有main方法的应用入口类,如下:
@SpringBootApplication
public class Application
{
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);
}
}
现在运行Application.java的main方法,打开浏览器访问http://localhost:8080/
。你会看到用户列表,酷吧!!!
好了,我仿佛听见了你再说:发生了什么事情???我来介绍下发生了什么。
1.简单的依赖管理
- 最显而易见的是我们使用了许多spring-boot-starter-*的依赖。还记得我说的95%的配置都是相同的吗?所以当你添加springboot-starter-web依赖时,你将会添加依赖到spring-webmvc、jackson-json、validation-api和tomcat等公共依赖库。
- 我们添加了spring-boot-starter-data-jpa依赖。这将拉去所有spring-data-jpa依赖并且添加Hibernate库,因为应用使用Hibernate作为JPA实现。
2.自动配置 - 不仅能顾通过spring-boot-starer-web添加所有依赖库,而且诸如DispatcherServlet、ResourceHandlers和MessageSource等通用注册带的beans也能通过默认的方式自动配置。
- 我们添加spring-boot-starter-Thymeleaf不仅添加所有Thymeleaf库依赖,而且自动配置ThymeleafViewResolver。
- 我们没有定义诸如DataSource、EntityManagerFactory、TransactionManager等beans但是工程会自动创建它们。如何做到的?如果我们使用内存数据库驱动(如H2或者HSQL),通过默认配置SpringBoot会自动创建内存数据源并注册EntityManagerFactory、TransactionManager。但我们使用的是MySQL,因此我们需要明确提供MySQL连接的详细信息。我们在application.properties文件中配置MySQL连接信息,然后SpringBoot通过该配置文件创建DataSource。
3.内置Servelet容器支持
最重要和令人称奇的是我们通过@SpringAplication注解一个简单java类,然后运行该java类的main方法后就可以访问http://localhost:8080/
。
servlet容器从何来而?我们添加spring-boot-starter-web依赖会自动拉取spring-boot-starter-tomcat依赖,当我们运行main()方法时,Tomcat会作为一个内置容器启动,因此我们不必将我们的应用部署到外部的tomcat容器中。
另外,你是否发现我们pom.xml中的打包类型是jar而不是war额吗?多么棒的一件事情。
好了,如果我想用Jetty代替Tomcat怎么办呢?从spring-boot-starter-web中排除spring-bootstarter-tomcat并包含spring-boot-starter-jetty。
只需要做这些,这是多么的奇妙!
我们设想你在想什么。你在想SpringBoot看上去如此酷并能自动为我做很多事情。但是我还是不太明白它背后是如何工作的,对吗?
我能理解。看魔术表演很有趣,但是软件开发则不然。别担心,在将来的文章中,我将逐步介绍每件事并详细介绍表象后面发生的具体事情。但是,我不打算在本文中将一切内容和盘托出,因为这会压垮你。
总结
本文中我们快速浏览了不同Spring配置风格并理解了配置Spring应用的复杂性。同时,我们通过一个简单的web应用快速体验了SprngBoot。
在下一篇文章中,我将深入SpringBoot内部,介绍它是如何工作的。