Spring Boot
简述Spring Boot是什么以及Spring Boot能做什么,最后来写自己的starter来支持Spring Boot自动配置,探索Spring Boot为何如此聪明。
简介
Spring Boot 提供约定优于配置的快速构建Spring应用,简单来讲就是快速构建和运行Spring应用。
Spring Boot很容易创建独立的生产级的Spring应用,大多数Spring Boot应用需要非常少的Spring 配置。
特性
- 创建独立运行的spring应用
- 直接嵌入Tomcat、Jetty或者Undertow web 容器使得不需要部署war包
- 提供starter来简化maven配置
- 尽可能的自动化配置应用
- 提供了生成级指标检测、健康状况检查支持外部化配置
- 没有代码生成、没有xml配置要求
快速开始
- 加入maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- SampleController
package hello;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@Controller
@EnableAutoConfiguration
public class SampleController {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleController.class, args);
}
}
直接运行此SampleController。
如此简单的一个Spring Web应用就在tomcat 8080 启动了,而不需要任何额外的配置。
相信应该已经明白Spring Boot是什么以及可以干什么了。
配置
尽管Spring Boot 如此聪明的自动化配置了,但如果想自定义配置,可以通过/src/main/resources/application.properties(或者application.yml)来配置,下面提供几个简单常用的配置:
项目 | 配置 | |
---|---|---|
容器监听端口 | server.port = 8090 | |
数据库连接URL | spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test | |
redis服务器地址 | spring.redis.host=127.0.0.1 | |
activemq服务器地址 | spring.activemq.broker-url=tcp://127.0.0.1:61616 |
官方文档提供了所有配置参考,请参考[Spring Boot Reference#appendix]
Spring Boot为何如此聪明
如果将Jetty的依赖加入到maven配置中,再启动应用就会使用Jetty来启动我们的应用,Spring Boot为何如此聪明?
真相是在Spring Boot启动时会扫描classpath下所有/META-INF/spring.factories文件,此文件里描述了所有自动配置的项目,这里提供Spring Boot自身的/META-INF/spring.factories片段,位于spring-boot-autoconfigure-version.jar中:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
可以看到以上片段包含了amqp、cache、jpa、mongo、redis、es以及大部分web自动配置,Spring Boot就是通过这些XXAutoConfiguration类来聪明的自动化配置,这些XXAutoConfiguration可以根据环境动态配置所需要的Bean(其他资源),这些XXAutoConfiguration本身就是标准的Java based Configuration(@Configuration),依靠条件动态配置bean:
* @ConditionalOnClass
* @ConditionalOnMissingBean
* @ConditionalOnProperty
Spring Boot处理静态资源
Spring Boot 默认从classpath的/static、 /public 、 /resources 或者 /META-INF/resources或者ServletContext的/服务静态资源。
如上述目录结构,访问静态资源分别为:
- /src/main/resources/resources/index.html:http://127.0.0.1:8080/或者http://127.0.0.1:8080/index.html
- /src/main/resources/static/2.jpg:http://127.0.0.1:8080/2.jpg
- /src/main/resources/public/3.jpg:http://127.0.0.1:8080/3.jpg
Spring Boot 试用Spring MVC 的ResourceHttpRequestHandler ,可以添加WebMvcConfigurerAdapter 的实现复写addResourceHandlers 方法来修改这些默认配置。
你可以通过application.properties的spring.resources.static-locations属性自定义静态资源目录位置。
默认配置为:spring.resources.static-locations =
classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/.
可以建立/src/main/resources/cust,并放入index.html和1.jpg
application.properties中配置spring.resources.static-locations = classpath:/cust/(注意一定要加classpath:,并且路径一定是目录而非文件,是/cust/而非/cust)
如此可以使用http://127.0.0.1:8080/index.html和http://127.0.0.1:8080/1.jpg来访问上述资源了。
Spring Boot错误处理
Spring Boot 注册了一个容器全局的错误页,/error
对于为机器的客户返回JSON格式的错误信息,http状态吗和错误信息,对于浏览器以html格式返回同样的错误信息,要完全替换这些机制需要注册一个bean实现ErrorController 接口即可。
如果想返回新的ContentType类型的错误信息BasicErrorController 是特别有用的,继承新加一个@RequestMapping方法produces 为新ContentType即可。
如果想自定义错误页,需要在/public 或者/static新建error目录创建404.html 或者5xx.html这样以http状态码的错误页面,既可以是普通的html也可以是模版。
对于复杂的映射你也可以新加一个bean去实现ErrorViewResolver 接口。
Spring Boot Starters
Spring Boot提供了许多starter来简化maven配置,自动化配置,这些*AutoConfiguration大都在Spring Boot提供的starter pom中,同样包含/META-INF/spring.factories文件,
详情参考[Spring Boot Starters#using-boot-starter]
spring-boot-starter-mail
spring-boot-starter-activemq
spring-boot-starter-web
spring-boot-starter-jdbc
spring-boot-starter-amqp
spring-boot-starter-redis
spring-boot-starter-data-mongodb
…
自定义spring-boot-starter-xxx
Spring Boot支持自定义自己的starter来自动化配置,需要配置/META-INF/spring.factories 和XXAutoConfiguration。
下面来自定义starter,实现的功能为:如果spring bean的某个属性加了@Reference注解、并且该属性是接口类型的话,就为该属性自动注入一个代理对象。
创建mavne项目
加入Spring Boot 依赖
<groupId>org.pretent.open</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>spring-boot-starter-mrpc</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.4.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
创建AutoConfiguration
程序清单:
MrpcAutoConfiguration.java
package org.pretent.mrpc.support.spring.boot;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MrpcAutoConfiguration {
@Bean
public InjectBean mInjectBean() {
return new InjectBean();
}
@Bean
public MrpcBootRegisterBean mMrpcBeanFactory() {
MrpcBootRegisterBean mrpcBeanFactory = new MrpcBootRegisterBean();
return mrpcBeanFactory;
}
}
InjectBean.java
public class InjectBean {
public InjectBean() {
}
public synchronized void inject(Object bean) {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getType().isInterface()) {
Reference reference = field.getAnnotation(Reference.class);
if (reference != null) {
try {
field.setAccessible(true);
Object value = field.get(bean);
field.setAccessible(false);
if (value == null) {
// 通过代理生成接口代理对象
value = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{field.getType()}, new MInvocationHandler());
if (value != null) {
field.setAccessible(true);
field.set(bean, value);
field.setAccessible(false);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
MrpcBootRegisterBean .java
public class MrpcBootRegisterBean implements BeanPostProcessor {
@Autowired
private InjectBean injectBean ;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// annotation @reference
injectBean.inject(bean);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
MInvocationHandler.java
class MInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理对象对于每个方法调用都返回proxy字符串,没有实际意义
return "proxy";
}
}
Reference.java
@Target(value = {ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Reference {
}
注册/META-INF/spring.factories
建立 /src/main/resources/META-INF/spring.factories
写入
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.pretent.mrpc.support.spring.boot.MrpcAutoConfiguration
安装自定义的starter
$ mvn clean install
使用自定义的starter
创建maven项目加入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.pretent.open</groupId>
<artifactId>spring-boot-starter-mrpc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
程序清单:
SampleController .java
@Controller
public class SampleController {
@Reference
private UserService userService;
@RequestMapping("/")
@ResponseBody
String home() throws Exception {
return userService.say(new Person(22, "张三", 22, new Date())) ;
}
}
UserService .java
public interface UserService {
String say(Person person) throws Exception;
}
Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
启动应用浏览器访问http://localhost:8080/,可以看到我们的userService已经被赋值为代理对象,并且返回’proxy’了。
以上自定义spring-boot-starters 在我的rpc框架中有使用到,如果需要可以参考 https://github.com/pretent/mrpc。