一埃唯、簡(jiǎn)介
Spring Boot簡(jiǎn)化了Spring應(yīng)用的開發(fā)懂诗,采用約定大于配置的思想,去繁從簡(jiǎn)抛虏,很方便就能構(gòu)建一個(gè)獨(dú)立的博其、產(chǎn)品級(jí)別的應(yīng)用。
1.傳統(tǒng)J2EE開發(fā)的缺點(diǎn)
開發(fā)笨重迂猴、配置繁多復(fù)雜慕淡、開發(fā)效率低下、部署流程復(fù)雜沸毁、第三方技術(shù)集成難度大峰髓。
2.SpringBoot的優(yōu)點(diǎn)
- 快速重建獨(dú)立運(yùn)行的Spring項(xiàng)目以及與主流框架集成。
- 使用嵌入式的Servlet容器息尺,應(yīng)用無需打成WAR包
- starters自動(dòng)依賴與版本控制
- 大量的自動(dòng)配置携兵、簡(jiǎn)化開發(fā),也可以修改其默認(rèn)值
- 無需配置XML搂誉,無代碼生成
- 準(zhǔn)生產(chǎn)環(huán)境的運(yùn)行時(shí)應(yīng)用監(jiān)控
- 與云計(jì)算的天然繼承
3.SpringBoot helloworld說明
1.starters
- SpringBoot為我們提供了簡(jiǎn)化企業(yè)級(jí)開發(fā)絕大多數(shù)場(chǎng)景的starters pom(啟動(dòng)器)徐紧,只要引入了相應(yīng)場(chǎng)景的starters pom,相關(guān)技術(shù)的絕大部分配置將會(huì)消除(字段配置)炭懊,從而簡(jiǎn)化我們的開發(fā)并级。業(yè)務(wù)中我們就會(huì)使用到SpringBoot為我們字段配置的Bean。
- 這些starters幾乎涵蓋了javaee所有常用場(chǎng)景侮腹,SpringBoot對(duì)這些場(chǎng)景依賴的jar也做了嚴(yán)格的測(cè)試和版本控制嘲碧。
- spring-boot-dependencies里面定義了jar包的版本。
2.入口類和@SpringBootApplication
- 程序從main方法開始運(yùn)行父阻。
- 使用SpringApplication.run()加載主程序類
- 主程序類需標(biāo)注@SpringBootApplication
- @EnableAutoConfiguration是核心注解
- @Import導(dǎo)入所有的自動(dòng)配置場(chǎng)景
- @AutoConfigurationPackage定義默認(rèn)的包掃描規(guī)則愈涩。
- 程序啟動(dòng)掃描主程序類所在的包以及下面所有子包的組件
3.自動(dòng)配置
自動(dòng)配置xxxAutoConfiguration
- SpringBoot中存現(xiàn)大量的這些類,這些類的作用就是幫我們進(jìn)行自動(dòng)裝配
- 它會(huì)將這個(gè)場(chǎng)景需要的所有組件都注冊(cè)到容器中加矛,并配置好
- 他們?cè)陬惵窂较碌腗ETA-INF/spring.factories文件中
- spring-boot-autoconfigure.jar中包含了所有場(chǎng)景的字段配置類代碼
- 這些自動(dòng)配置類是SpringBoot進(jìn)行自動(dòng)裝配的關(guān)鍵钠署。
二、SpringBoot配置
1.配置文件
SpringBoot使用一個(gè)全局的配置文件荒椭。配置文件名是固定的谐鼎。
-application.properties或者application.yml配置文件放在src/main/resources目錄或者類路徑/config下。
全局配置文件的作用是對(duì)一些默認(rèn)配置進(jìn)行修改
2.配置文件值注入
- @Value和@ConfigurationProperties為屬性注入值進(jìn)行對(duì)比
對(duì)比點(diǎn) | @ConfigurationProperties | @Value |
---|---|---|
功能 | 批量注入配置文件中的屬性 | 一個(gè)個(gè)指定 |
松散綁定(松散語(yǔ)法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303數(shù)據(jù)校驗(yàn) | 支持 | 不支持 |
復(fù)雜類型封裝 | 支持 | 不支持 |
屬性名匹配規(guī)則
-person.firstName 使用標(biāo)準(zhǔn)方式
-person.first-name 使用-
-person.first_name 使用_
-PERSON_FIRST_NAME 推薦系統(tǒng)屬性使用這種寫法@PropertySource
加載指定的配置文件ConfigurationProperties
-與@Bean結(jié)合為屬性賦值
-與@PropertySource(只能用于properties文件)結(jié)合讀取指定文件。ConfigurationProperties Validation
-支持JSR303進(jìn)行配置文件值校驗(yàn)狸棍。
@Component
@PropertySource(value={"classpath:person.properties"})
@ConfigurationProperties(prefix="person")
@Validated
public class Person{
@Email
@Value("${person.email}")
private String email身害;
}
- ImportResource讀取外部配置文件
3.配置文件占位符
RandomValuePropertySource
配置文件中可以使用隨機(jī)數(shù)
-${random.value}
-${random.int}
-${random.long}
-${random.int(10)}
-${random.int[1024,65536]}屬性配置占用符
-可以在配置文件中引用前面配置過的屬性(Y優(yōu)先級(jí)前面配置過的這里都可以使用)。
-${app.name:默認(rèn)值}來指定找不到屬性時(shí)的默認(rèn)值草戈。
app.name=MyApp
app.description=${app.name} is a SpringBoot Application
4.profile
profile是Spring對(duì)不同環(huán)境提供不同配置功能的支持塌鸯,可以通過激活指定參數(shù)的方式快速切換環(huán)境。
1.多profile文件形式
- 格式:application-{profile}.properties/yml
application-dev.properties唐片、application-prod.properties
2.多profile文檔塊模式
spring.profiles.active=prod #激活指定配置
spring.profiles=prod
server.port=80
# default表示未指定時(shí)的默認(rèn)配置
spring.profiles=default
server.port=8080
3.激活方式
- 命令行:--spring.profiles.active=dev
- 配置文件:spring.profiles.active=dev
- jvm參數(shù):-Dspring.profiles.active=dev
5.配置文件加載位置
SpringBoot啟動(dòng)會(huì)掃描一下位置的application.properties或者application.yml文件作為SpringBoot的默認(rèn)配置文件丙猬。
- file:./config/
- file:./
- classpath:/config/
-classpath:/
-以上是按照優(yōu)先級(jí)從高到低的順序,所有位置的文件都會(huì)被加載费韭,高優(yōu)先級(jí)配置內(nèi)容會(huì)覆蓋低優(yōu)先級(jí)配置內(nèi)容茧球。
-可以通過配置spring.config.location來改變默認(rèn)配置。
6.外部配置加載順序
- 命令行參數(shù)
- 來自java:comp/env的JNDI屬性
- Java系統(tǒng)屬性(System.getProperties())
- 操作系統(tǒng)環(huán)境變量
- RandomValuePropertySource配置的random.*屬性值
- jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
- jar包內(nèi)部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
- jar包外部的application.properties或application.yml(不帶spring.profile)配置文件
- jar包內(nèi)部的application.properties或application.yml(不帶spring.profile)配置文件
- @Configuration注解類上的@PropertySource星持。
- 通過SpringApplication.setDefaultproperties指定的默認(rèn)屬性抢埋。
7.自動(dòng)配置原理
1.SpringBoot啟動(dòng)的時(shí)候加載主配置類,開啟了自動(dòng)配置功能@EnableAutoConfiguration
2.@EnableAutoConfiguration作用
- 利用EnableAutoConfigurationImportSelector給容器中導(dǎo)入一些組件督暂。
- 將類路徑小META-INF/spring.factories里面配置的所有EnableAutoConfiguration的值加入到了容器中揪垄。
3.@Conditional派生注解
@Conditional擴(kuò)展注解 | 作用(判斷是否滿足當(dāng)期指定條件) |
---|---|
@ConditionalOnJava | 系統(tǒng)的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 滿足SpEL表達(dá)式指定 |
@ConditionalOnClass | 系統(tǒng)中有指定的類 |
@ConditionalOnMissingClass | 容器中沒有指定類 |
@ConditionalOnSingleCandidate | 容器中只有一個(gè)指定的Bean,或者這個(gè)Bean是首選Bean |
@ConditionalOnProperty | 系統(tǒng)中指定的屬性是否有指定的值 |
@ConditionalOnResource | 類路徑下是否存在指定資源文件 |
@ConditionalOnWebApplication | 當(dāng)前是web環(huán)境 |
@ConditionalOnNotWebApplication | 當(dāng)前不是web環(huán)境 |
@ConditionalOnJndi | JNDI存在指定項(xiàng) |
- 作用:必須是@Conditional指定的條件成立逻翁,才給容器中添加組件饥努,配置配里面的所有內(nèi)容才生效。
三八回、SpringBoot與日志
1.日志框架
市場(chǎng)上存在非常多的日志框架酷愧,JUL(java.util.logging)、JCL(Apache Commons Logging)辽社、Log4J、Log4J2翘鸭、Logback滴铅、SLF4j、jboss-logging等就乓。
- SpringBoot早框架內(nèi)部使用JCL汉匙。spring-boot-starter-logging采用了slf4j+logback的形式,SpringBoot也能自動(dòng)配置(jul生蚁、log4j2噩翠、logback)并簡(jiǎn)化配置。
日志門面 | 日志實(shí)現(xiàn) |
---|---|
JCL邦投、SLF4J伤锚、jboss-logging | log4j、JUL志衣、Log4j2屯援、Logback |
日志系統(tǒng) | 配置文件 |
---|---|
Logback | logback-spring.xml猛们、logback-spring.groovy、logback.xml或logback.groovy |
Log4j2 | log4j2-spring.xml狞洋、log4j2.xml |
JUL | logging.properties |
- 總結(jié):
1.SpringBoot底層也是使用slf4j+logback的方式進(jìn)行日志記錄弯淘。
2.SpringBoot也把其他的日志都替換成了slf4j。
3.如果要引入其他日志框架吉懊,要排除Spring框架的commons-logging依賴庐橙。
四、Web開發(fā)
1.SpringBoot對(duì)靜態(tài)資源的映射規(guī)則
- 所有/webjars/**,都去classpath:/META-INF/resources/webjars/ 下面找資源借嗽。
- "/**" 訪問當(dāng)前項(xiàng)目的任何資源态鳖,都去(靜態(tài)資源的文件夾)找映射。
- 歡迎頁(yè)淹魄;靜態(tài)資源文件夾下的所有index.html頁(yè)面郁惜,被"/**" 映射。
- 所有的 **/favicon.ico都是在靜態(tài)資源文件下找甲锡。
2.SpringMVC自動(dòng)配置
1.SpringMVC auto-configuration
SpringBoot對(duì)SpringMVC的默認(rèn)配置(WebMvcAutoConfiguration)如下:
-
包含了ContentNegotiatingViewResolver和BeanNameViewResolver兆蕉。
- 自動(dòng)配置了ViewResolver
- ContentNegotiatingViewResolver:組合所有的視圖解析器
支持靜態(tài)資源,包括支持Wenjars
靜態(tài)首頁(yè)訪問
支持favicon.ico
-
自動(dòng)注冊(cè)了Converter缤沦、GenericConverter虎韵、Formatter。
- Converter:轉(zhuǎn)換器缸废。
- Formatter:格式化器
-
支持HttpMessageConverters
- HttpMessageConverter:SpringMVC用來轉(zhuǎn)換Http請(qǐng)求和響應(yīng)包蓝。
- HttpMessageConverters:從容器中確定,獲取所有的HttpMessageConverter企量;
自動(dòng)注入MessageCodesResolver测萎,定義錯(cuò)誤碼生成規(guī)則。
自動(dòng)使用ConfigurableWebBindingInitializer届巩。
2.擴(kuò)展SpringMVC
編寫一個(gè)配置類(@Configuration)硅瞧,是WebMvcConfigurerAdapter類型,不能標(biāo)注@EnableWebMvc注解
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/desperado").setViewName("success");
}
}
原理
- WebMvcAutoConfiguration是SpringMVC的自動(dòng)配置類恕汇。
- 在做其他自動(dòng)配置時(shí)會(huì)導(dǎo)入腕唧。
- 容器中所有的WebMvcConfigurer都會(huì)一起被注冊(cè)。
- 我們自定義的配置類也會(huì)被調(diào)用瘾英。
3.全面接管SpringMVC
如果想要使SpringMVC的自動(dòng)配置失效枣接,只需要在我們自定義的配置類中添加@EnableWebMvc注解即可。
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/desperado").setViewName("success");
}
}
原理
- @EnableWebMvc的注解
@Import(DelegatingWebMvcConfiguation.class)
public @interface EnableWebMvc{}
- DelegatingWebMvcConfiguation
@Configuration
public class DelegatingWebMvcConfiguation extend WebMvcConfigurationSupport{}
- WebMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class,DispatcherServlet.class,
WebMvcConfigurerAdapter.class})
//容器中沒有這個(gè)組件缺谴,這個(gè)自動(dòng)配置類才會(huì)生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration{}
- @EnableWebMvc會(huì)將WebMvcConfigurationSupport組件導(dǎo)入進(jìn)來但惶。
- 導(dǎo)入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。
4.修改默認(rèn)配置
- SpringBoot在自動(dòng)配置很多組件的時(shí)候,顯卡容器中有沒有用戶自己配置的(@Bean 榆骚、@Component),如果有就用用戶配置的片拍,如果沒有,才會(huì)進(jìn)行自動(dòng)配置妓肢;如果某些組件可以有多個(gè)捌省,將用戶配置的和自己默認(rèn)的組合起來。
- 在SpringBoot中有許多的xxxConfigurer幫助我們進(jìn)行擴(kuò)展配置
- 在SpringBoot中有許多的xxxCustomizer幫助我們進(jìn)行定制配置
5.默認(rèn)訪問首頁(yè)
使用自定義WebMvcConfigurationAdapter進(jìn)行配置
//使用WebMvcConfigurationAdapter可以擴(kuò)展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//瀏覽器發(fā)送/desperado 請(qǐng)求來到success
registry.addViewController("/desperado").setViewName("success");
}
//所有的webMvcConfigurerAdapter組件都會(huì)一起起作用
@Bean //將組件注冊(cè)到容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//配置默認(rèn)路徑的頁(yè)面
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return adapter;
}
}
6.國(guó)際化
1.編寫國(guó)際化配置文件
編寫不同語(yǔ)言的配置文件碉钠,比如login.properties纲缓、login_en_US.properties、login_zh_CN.properties等喊废。
- SpringBoot自動(dòng)配置好了管理國(guó)際化資源文件的組件祝高。
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = new Resource[0];
public MessageSourceAutoConfiguration() {
}
@Bean
@ConfigurationProperties(
prefix = "spring.messages"
)
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//設(shè)置國(guó)際化資源文件的基礎(chǔ)名(去掉語(yǔ)言國(guó)家代碼)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
原理
根據(jù)請(qǐng)求頭帶來的區(qū)域信息獲取Locale進(jìn)行國(guó)際化。
五污筷、錯(cuò)誤處理機(jī)制
1.默認(rèn)的錯(cuò)誤處理機(jī)制
- 瀏覽器工闺,默認(rèn)返回一個(gè)默認(rèn)的錯(cuò)誤頁(yè)面。
- 其他客戶端瓣蛀,默認(rèn)響應(yīng)一個(gè)json數(shù)據(jù)陆蟆。
原理
- 在DefaultErrorAttributes中獲取錯(cuò)誤頁(yè)面的信息
public class DefaultErrorAttributes implements ErrorAttributes {
//獲取錯(cuò)誤頁(yè)面的信息
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
Throwable error = this.getError(request);
HttpStatus errorStatus = this.determineHttpStatus(error);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", this.determineMessage(error));
this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
return errorAttributes;
}
}
- 在BasicErrorController中處理/error請(qǐng)求
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
//產(chǎn)生html類型的數(shù)據(jù),瀏覽器發(fā)送的請(qǐng)求來打這個(gè)方法處理
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪個(gè)頁(yè)面作為錯(cuò)誤頁(yè)面惋增。包含頁(yè)面地址和頁(yè)面內(nèi)容
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
//產(chǎn)生json數(shù)據(jù)叠殷,其他客戶端來到這個(gè)方法處理
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
}
- ErrorPageCustomizer進(jìn)行錯(cuò)誤配置
public class ErrorProperties {
@Value("${error.path:/error}")
private String path = "/error";
private boolean includeException;
private ErrorProperties.IncludeStacktrace includeStacktrace;
private final ErrorProperties.Whitelabel whitelabel;
}
- ErrorMvcAutoConfiguration生成錯(cuò)誤頁(yè)面
public class ErrorMvcAutoConfiguration {
private static class StaticView implements View {
private static final Log logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);
private StaticView() {
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (response.isCommitted()) {
String message = this.getMessage(model);
logger.error(message);
} else {
StringBuilder builder = new StringBuilder();
Date timestamp = (Date)model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(this.getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>There was an unexpected error (type=").append(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
}
private String htmlEscape(Object input) {
return input != null ? HtmlUtils.htmlEscape(input.toString()) : null;
}
private String getMessage(Map<String, ?> model) {
Object path = model.get("path");
String message = "Cannot render error page for request [" + path + "]";
if (model.get("message") != null) {
message = message + " and exception [" + model.get("message") + "]";
}
message = message + " as the response has already been committed.";
message = message + " As a result, the response may have the wrong status code.";
return message;
}
public String getContentType() {
return "text/html";
}
}
5.DefaultErrorViewResolver解析頁(yè)面
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默認(rèn)去找一個(gè)頁(yè), error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析這個(gè)頁(yè)面地址就要模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//模板引擎可用的情況下返回到errorViewName指定的視圖地址
//模板引擎不可以诈皿,就在靜態(tài)資源文件夾下找對(duì)應(yīng)的頁(yè)面
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
2.錯(cuò)誤頁(yè)面的優(yōu)先級(jí)(自定義錯(cuò)誤頁(yè)面)
- 在模板引擎的情況下林束,error/狀態(tài)碼(將錯(cuò)誤頁(yè)面命名為 錯(cuò)誤狀態(tài)碼.html 放在模板引擎文件夾里面的error文件夾下),發(fā)生此狀態(tài)碼的錯(cuò)誤就會(huì)來到對(duì)應(yīng)的頁(yè)面稽亏;
- 沒有模板引擎(模板引擎找不到錯(cuò)誤頁(yè)面),就在靜態(tài)資源文件夾下找壶冒。
- 以上都沒有錯(cuò)誤頁(yè)面,就是默認(rèn)來到SpringBoot默認(rèn)的錯(cuò)誤提示頁(yè)面截歉。
3.如何定制錯(cuò)誤的json數(shù)據(jù)
- 自定義異常處理&返回定制json數(shù)據(jù)(沒有自適應(yīng)效果)
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(CustomException.class)
public Map<String,Object> handleException(Exception e){
HashMap<String, Object> map = new HashMap<>();
map.put("code","錯(cuò)誤信息");
map.put("message",e.getMessage());
return map;
}
}
2.轉(zhuǎn)發(fā)到/error進(jìn)行自適應(yīng)響應(yīng)效果處理胖腾。
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(CustomException.class)
public String handleException(Exception e, HttpServletRequest request){
HashMap<String, Object> map = new HashMap<>();
//傳入我們自己的錯(cuò)誤狀態(tài)碼,否則就不會(huì)進(jìn)入定制錯(cuò)誤頁(yè)面的解析流程
request.setAttribute("java.servlet.error.status_code","500");
map.put("code","錯(cuò)誤信息");
map.put("message",e.getMessage());
//轉(zhuǎn)發(fā)到/error
return "forward:/error";
}
}
4.將定制的數(shù)據(jù)發(fā)送出去
出現(xiàn)錯(cuò)誤之后怎披,回來到/error請(qǐng)求胸嘁,會(huì)被BasicErrorController處理瓶摆,響應(yīng)出去可以獲取的數(shù)據(jù)是由getErrorAttributes得到的凉逛。
- 編寫一個(gè)ErrorController的實(shí)現(xiàn)類(或者是編寫AbstractErrorController的子類),放到容器中群井。
- 頁(yè)面上能用的數(shù)據(jù)状飞,或者是json返回能用的數(shù)據(jù)都是通過errorAttributes.getErrorAttributes得到;容器中DefaultErrorAttributes.getErrorAttributes()默認(rèn)進(jìn)行數(shù)據(jù)處理.
//給容器中加入自定義的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//獲取ErrorAttributes的map
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
//加入自己屬性字段
map.put("name","desperado");
return map;
}
}
六、配置嵌入式Servlet容器
SpringBoot默認(rèn)使用Tomcat作為內(nèi)嵌的Servlet容器
1.修改Servlet容器的配置
在配置文件application文件中修改和server有關(guān)的配置诬辈。
server.port=8081
server.context_path=/crud
server.tomcat.uri-encoding=utf-8
2. 定制Servlet容器的相關(guān)配置
編寫一個(gè)EmbeddedServletContainerCustomizer(2.x中使用WebServerFactoryCustomizer)酵使,來修改Servlet容器的配置。
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8081);
}
};
}
3.注冊(cè)Servlet三大組件
由于SpringBoot是默認(rèn)以jar包的方式啟動(dòng)內(nèi)嵌的Servlet容器來啟動(dòng)SpringBoot的web應(yīng)用焙糟,沒有web.xml文件口渔。所以注冊(cè)Servlet、Filter穿撮、Listener的方式也不同
1. 注入Servlet
@Bean
public ServletRegistrationBean<MyServlet> myServlet(){
ServletRegistrationBean<MyServlet> registrationBean =
new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
return registrationBean;
}
2. 注入Filter
@Bean
public FilterRegistrationBean<MyFilter> myFilter(){
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
3. 注入Listener
@Bean
public ServletListenerRegistrationBean<MyListener> myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean =
new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
4.替換為其他嵌入式Servlet容器
替換為其他的Servlet非常簡(jiǎn)單缺脉,只需要在pom中引入其依賴,然后排除tomcat的依賴即可.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
5. 嵌入式Servlet容器自動(dòng)配置原理
- EmbeddedServletContainerAutoConfiguration(2.x對(duì)應(yīng)ServletWebServerFactoryConfiguration):嵌入式容器的自動(dòng)配置
@Configuration
class ServletWebServerFactoryConfiguration {
ServletWebServerFactoryConfiguration() {
}
@Configuration
@ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedUndertow {
public EmbeddedUndertow() {
}
@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory() {
return new UndertowServletWebServerFactory();
}
}
@Configuration
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedJetty {
public EmbeddedJetty() {
}
@Bean
public JettyServletWebServerFactory JettyServletWebServerFactory() {
return new JettyServletWebServerFactory();
}
}
@Configuration
//判斷當(dāng)前是否引入了tomcat依賴
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
///判斷當(dāng)前容器沒有用戶自己定義ServletWebServerFactory:
//嵌入式的Servlet容器工廠悦穿;作用:創(chuàng)建嵌入式的Servlet容器
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedTomcat {
public EmbeddedTomcat() {
}
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
}
- 嵌入式Servlet容器工廠
- 嵌入式的Servlet容器
4.以tomcat為例
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return thisb.getTomcatWebServer(tomcat);
}
- 容器中導(dǎo)入 WebServerFactoryCustomizerBeanPostProcessor
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
//初始化之前
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//如果當(dāng)前初始化的是一個(gè)WebServerFactory類型的組件
if (bean instanceof WebServerFactory) {
this.postProcessBeforeInitialization((WebServerFactory)bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
// 獲取所有的定制器攻礼,調(diào)用每一個(gè)定制器的customize方法來給servlet容器進(jìn)行屬性賦值
((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
customizer.customize(webServerFactory);
});
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
// 定制Servlet容器,給容器中可以添加一個(gè)WebServerFactoryCustomizer類型的組件
this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
//從容器中獲取所有這個(gè)類型的組件:WebServerFactoryCustomizer
return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
}
總結(jié)
- SpringBoot根據(jù)導(dǎo)入的依賴情況栗柒,給容器中添加相應(yīng)的WebServerFactory【TomcatServletWebServerFactory】礁扮。
- 容器中某個(gè)組件要?jiǎng)?chuàng)建對(duì)象就會(huì)使用WebServerFactoryCustomizerBeanPostProcessor后置處理器,只要是嵌入式的Servlet工廠瞬沦,后置處理器就會(huì)進(jìn)行處理太伊。
- 后置處理器從容器中獲取所有的WebServerFactoryCustomizer,調(diào)用定制器的定制方法
6.嵌入式Servlet容器啟動(dòng)過程
- SpringBoot啟動(dòng)運(yùn)行run方法蛙埂。
- 調(diào)用refreshContext(context);刷新IOC容器【創(chuàng)建IOC容器倦畅,并初始化容器,創(chuàng)建容器中的每一個(gè)組件】绣的;如果是web應(yīng)用創(chuàng)建AnnotationConfigServletWebServerApplicationContext叠赐,如果是reactive應(yīng)用創(chuàng)建AnnotationConfigReactiveWebServerApplicationContext,否則創(chuàng)建AnnotationConfigApplicationContext屡江。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
- 調(diào)用refresh(context);刷新上面創(chuàng)建好的IOC容器
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
//準(zhǔn)備刷新的context
this.prepareRefresh();
//調(diào)用子類去刷新內(nèi)部的實(shí)例工廠
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
//準(zhǔn)備在這個(gè)context中要使用的實(shí)例工廠
this.prepareBeanFactory(beanFactory);
try {
// 允許在上下文子類中對(duì)bean工廠進(jìn)行后置處理芭概。
this.postProcessBeanFactory(beanFactory);
//在context中調(diào)用注冊(cè)為bean的工廠處理器。
this.invokeBeanFactoryPostProcessors(beanFactory);
//注冊(cè)攔截bean創(chuàng)建的bean處理器惩嘉。
this.registerBeanPostProcessors(beanFactory);
//初始化此context的消息源
this.initMessageSource();
//初始化此上下文的事件多播器罢洲。
this.initApplicationEventMulticaster();
//在特定的context子類中初始化其他特殊bean。
this.onRefresh();
// 檢查監(jiān)聽器bean并注冊(cè)它們文黎。
this.registerListeners();
// 實(shí)例化所有剩余(非延遲初始化)單例惹苗。
this.finishBeanFactoryInitialization(beanFactory);
//最后一步:發(fā)布相應(yīng)的事件。
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
//摧毀已經(jīng)創(chuàng)建的單例以避免占用資源耸峭。
this.destroyBeans();
//重置 ‘a(chǎn)ctive’ 標(biāo)志
this.cancelRefresh(var9);
//Propagate exception to caller.
throw var9;
} finally {
//從我們開始桩蓉,重置Spring核心中的常見內(nèi)省緩存
//可能不再需要單例bean的元數(shù)據(jù)了...
this.resetCommonCaches();
}
}
}
- 調(diào)用onRefresh();web的IOC容器重寫了onRefresh方法。
- Web IOC容器創(chuàng)建嵌入式的Servlet容器劳闹。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
獲取嵌入式的Servlet容器工廠: ServletWebServerFactory factory = this.getWebServerFactory();從IOC容器中獲取ServletWebServerFactory組件院究;
使用容器工廠獲取嵌入式的Servlet容器:his.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()})洽瞬;
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
- 嵌入式的Servlet容器創(chuàng)建并啟動(dòng)Servlet容器。
9.先啟動(dòng)嵌入式的Servlet容器业汰,再將IOC容器中剩余的沒有創(chuàng)建出來的對(duì)象獲取出來伙窃、IOC容器啟動(dòng)就會(huì)創(chuàng)建嵌入式的Servlet容器。
7.使用外置的Servlet容器
1. 嵌入式Servlet容器優(yōu)缺點(diǎn)
優(yōu)點(diǎn):簡(jiǎn)單样漆、便捷为障。
缺點(diǎn):默認(rèn)不支持JSP,優(yōu)化定制比較復(fù)雜放祟。
2.使用外部Servlet容器步驟
- 必須創(chuàng)建一個(gè)war項(xiàng)目产场。
- 將嵌入式的Tomcat指定為provided。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐tomcat</artifactId>
<scope>provided</scope>
</dependency>
- 必須編寫一個(gè)SpringBootServletInitializer的子類舞竿,并調(diào)用configure方法京景。
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//傳入SpringBoot應(yīng)用的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
- 啟動(dòng)服務(wù)器就可以了。
3.原理與規(guī)則
原理
啟動(dòng)服務(wù)器骗奖,服務(wù)器啟動(dòng)SpringBoot應(yīng)用[SpringBootServletInitializer],啟動(dòng)IOC容器确徙。
規(guī)則
- 服務(wù)器啟動(dòng)會(huì)創(chuàng)建當(dāng)前web應(yīng)用里面每一個(gè)jar包里面的ServletContainerInitializer實(shí)例。
- ServletContainerInitializer的實(shí)現(xiàn)放在jar包的META-INF/services文件下执桌,有一個(gè)名為javax.servlet.ServletContainerInitializer的文件鄙皇,內(nèi)容就是ServletContainerInitializer實(shí)現(xiàn)類的全類名。
- 還可以使用@HandlerType仰挣,在啟動(dòng)應(yīng)用時(shí)加載指定的類伴逸。
4. 啟動(dòng)流程
- 啟動(dòng)tomcat。
- 加載spring-web包下META-INF/services下面的javax.servlet.ServletContainerInitializer文件膘壶。
- SpringServletContainerInitializer將@HandlerType標(biāo)注的所有這個(gè)類型的類都傳入搭配onStartup方法的Set中错蝴,為這些WebApplicationInitializer類型的類創(chuàng)建實(shí)例。
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
- 每個(gè)WebApplicationInitializer到調(diào)用自己的onStartup()方法颓芭。
相當(dāng)于SpringBootServletInitializer的類會(huì)被創(chuàng)建對(duì)象顷锰,并執(zhí)行onStartup()方法。
SpringBootServletInitializer實(shí)例執(zhí)行onStartup的時(shí)候會(huì)crateRootApplicationContext創(chuàng)建容器亡问。
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
// 1.創(chuàng)建SpringApplicationBuilder
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
// 2.調(diào)用configure方法官紫,子類重新了這個(gè)方法,將SpringBoot的主程序類傳入
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
// 3.使用builder創(chuàng)建一個(gè)Spring應(yīng)用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
//確保錯(cuò)誤頁(yè)被注冊(cè)
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
// 4. q啟動(dòng)Spring應(yīng)用
return this.run(application);
}
Spring的應(yīng)用啟動(dòng)并且創(chuàng)建IOC容器州藕。
先啟動(dòng)Servlet容器束世,再啟動(dòng)SpringBoot應(yīng)用。