SpringBoot和日志
SpringBoot选用的日志框架是日志抽象层(门面)选用SLF4J,日志实现选用是logback
第一、如何在Spring Boot中使用SLF4J
开发过程中,日志记录方法的调用,不应该使用实现类,而是用抽象层。
给系统导入slf4j的jar和logback的实现jar,日志框架官网http://www.slf4j.org/manual.html
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
slf4j适配各类日志的推荐模式图

spring boot中的日志依赖了那个日志框架
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
日志底层依赖关系

总结:
1、)springboot底层使用slf4j+logback方式进行日志记录
2、)springboot也把其他日志框架转换成了slf4j
3、)如果我们要引入其他日志框架,一定要把这个框架默认的日志框架移除。
Spring Boot下的日志使用
Logger logger = LoggerFactory.getLogger(this.getClass());
日志的配置
logging:
file:
path: E://bootlog
如何让自己的配置文件生效,不用SpringBoot默认的日志配置文件

上面说了,如果你使用的是logback,那就新建一个对应的文件放入resource中即可。
不过有个特别提醒:
在logback框架下,如果使用:
logback.xml:该文件会被日志框架直接识别会绕过spring boot
logback-spring.xml:该文件可以使用spring boot的高级特性
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
可以指定某段配置只在某个环境下生效
</springProfile>
<springProfile name="dev | staging">
<!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>
<springProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>
WEB 开发
使用SpringBoot
1)创建SpringBoot应用,选中我们需要的模块
2)SrpingBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量的配置就可以运行起来了
3)自己编写业务代码
自动配置原理
这个场景SpringBoot帮我们配置什么?能不能修改?能修改哪些配置?能不能扩展?等等
xxxAutoConfiguration:帮我们给容器自动配置组件
AutoConfigurationImportSelector:扫描所有类
xxxConfigurationProperties:配置类来封装属性值
SpringBoot对静态资源的映射规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
可以设置和静态资源有关的参数,缓存时间等
以WebMvcAutoConfiguration为例子:他有个重要的方法addResourceHandlers
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
1、)所有/webjars/**请求,都去classpath:/META-INF/resources/webjars/里面找资源
webjars:以jar包的形式引入静态资源。如果要使用它的特性,就参考https://www.webjars.org/ 官网
他能做到把前端框架以jar包的形式导入到工程中

<!--引入jquery的webjars-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>

直接可以在浏览器中访问到静态资源
http://localhost:8083/webjars/jquery/3.5.1/jquery.js

2、)/** 访问当前项目的任何资源(静态资源文件夹)
classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/
"/":当前项目的根路径
3、)欢迎页
静态资源文件夹下所有的index.html页面;被“/**”映射。
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
扩展spring MVC的特性
编写配置类(@Configuration),必须继承继承WebMvcConfigurer

spring boot推荐使用模板引擎 thymeleaf,语法简单功能强大
1、引入thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、thymeleaf语法和使用
我们可以看spring-boot-autoconfiguration.jar里面的关于thymeleaf的自动配置类

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
//只要我们把html页面放在classpath:/templates/路径下,thymeleaf就可以帮我们自动渲染了
thymeleaf的语法详细参看www.thymeleaf.org
//使用WebMvcConfigurer可以扩展spring mvc功能
@Configuration
public class MyMvcAdapter implements WebMvcConfigurer {
//
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//浏览器发送/login请求,可以访问到success页面
registry.addViewController("/login").setViewName("success");
registry.addViewController("/loginout").setViewName("loginout");
}
}

添加拦截器
1)实现自己的拦截器
需要实现此拦截器接口HandlerInterceptor,他有三个重要方法
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
实现类:
/**
* 登陆拦截器
*/
public class LoginHanderFilter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String flag=request.getAttribute("flag")+"";
if ("s".equals(flag)){
System.out.println("通过啦");
return true;
}else {
System.out.println("拦住啦");
return false;
}
}
拦截器注册到容器中
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHanderFilter())
.addPathPatterns("/**")//拦截的uri
.excludePathPatterns("/index.html","/","/actuator/*");//排除的uri
}
Spring boot的错误处理机制
1、)如果是浏览器访问不存在的地址
返回的是个错误页面


2、)如果是postman接口提交
返回的是json数据
{
"timestamp": "2020-11-06T01:24:16.033+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/sssdf"
}

为什么会产生默认的效果?这个原理是什么?
答案在我们的自动配置类里,打开我们的常说的spring-boot-autoconfiguration.xxx版本.jar

原理:可以自己查看它的实现ErrorMvcAutoConfiguration
其中给容器中添加了一下组件:
1、DefaultErrorViewResolver
响应页面,去哪个页面是由DefaultErrorViewResolver解析得到的。
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
//
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//去的页面名称是error/状态码
String errorViewName = "error/" + viewName;
//如果模板引擎可以解析,就用模板引擎来解析,
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
//如果模板引擎可用,就返回ModelAndView视图
return new ModelAndView(errorViewName, model);
}
//如果模板引擎不可用,就在静态文件资源下寻找errorViewName对应的html页面。
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
一旦系统出现4xx或5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),如果系统发生错误就会请求/error路径,就会被BasicErrorController处理。
2、BasicErrorController
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
//产生html类型的数据,浏览器发送的请求,来到这个方法处理
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//指定去哪个页面作为错误页面:包括页面地址和页面内容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
//产生json数据,其他客户端来到这个方法处理
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
3、ErrorPageCustomizer:
/**
* Path of the error controller.
*/
@Value("${error.path:/error}")//冒号代表如果配置文件中未找到error.path对应的值,就使用/error这个路径
private String path = "/error";
4、DefaultErrorProperties:帮我们在页面上共享信息
我们在页面上能获取的信息有:
message:错误信息
timestamp:时间戳
status:状态码
error:错误信息
exception:异常信息
errors:JSR303数据校验信息
我们在4xx页面获取下这些信息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
message:错误信息
timestamp:时间戳
status:状态码
error:错误信息
exception:异常信息
errors:JSR303数据校验信息
<h3>message:[[${message}]]</h3>
<h3>timestamp:[[${timestamp}]]</h3>
<h3>status:[[${status}]]</h3>
<h3>error:[[${error}]]</h3>
<h3>exception:[[${exception}]]</h3>
<h3>errors:[[${errors}]]</h3>
</body>
</html>

4.1)
3)如何定制错误相应
3.1)、如何定制错误页面
1、)在使用模板引擎的情况下;error/状态码【我们将错误页面命名为错误状态码.html,放在模板引擎文件夹下/error文件夹下】放生对应的状态码错误,就会主动跳转到该页面。
2、)如果4或5开头的状态码没有找到精确的页面匹配,就来这个4xx或5xx页面,匹配原则精确优先。
3、)我们能从错误试图中获取什么信息?
message:错误信息
timestamp:时间戳
status:状态码
error:错误信息
exception:异常信息
errors:JSR303数据校验信息
4、)模板引擎下找不到错误页面,就会去静态资源文件夹下找
5、)如果以上都没有,就会来到SpringBoot默认的错误页面。


3.2)、如何定制错误json数据
1)、
/**
* 自定义异常
*/
public class MyException extends RuntimeException{
public MyException(String message) {
super(message);
}
}
//异常处理类
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
//指定对应的异常类进行捕获
@ExceptionHandler(MyException.class)
public Map<String,Object> handlerException(Exception e){
Map<String,Object> error=new HashMap<>();
error.put("code","2002");
error.put("massage",e.getMessage());
return error;
}
}
//上面的异常处理并不是自适应效果,浏览器客户端都是返回同一种json数据。如果我们像让他根据请求的客户端不同,返回不同的数据形式,怎么办?
2)、转发到/error自适应响应效果处理
//异常处理类
@ControllerAdvice
public class MyExceptionHandler {
//指定对应的异常类进行捕获
@ExceptionHandler(MyException.class)
public String handlerException(Exception e, HttpServletRequest request){
Map<String,Object> error=new HashMap<>();
error.put("code","2002");
error.put("massage",e.getMessage());
request.setAttribute("ext",error);
return "forword:/error";
}
}
需要自定义ErrorAttributes这个类
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> attr= super.getErrorAttributes(webRequest, options);
attr.put("ext",webRequest.getAttribute("ext",0));
return attr;
}
}
自定义输出效果:ext为自定义报文,其他的都是spring boot自定义错误
{
"timestamp": "2020-11-09T06:58:09.263+00:00",
"status": 200,
"error": "OK",
"message": "",
"path": "/person/listUser",
"ext": {
"code": "2002",
"massage": "你输入了ls"
}
嵌入式web容器
spring boot默认使用tomcat为嵌入式的web容器
1)、如果是嵌入式,我们怎么修改web容器的参数?
修改和server(ServerProperties类)有关的配置即可,例如:
server.port=8081
通用的web容器设置
server.xxx
tomcat的设置
server.tomcat.xxx
2)、注册servlet的三大组件
servlet、filter、listener
使用ServletRegistrationBean、FilterRegistrationBean、ListenerRegistrationBean
3)、切换web容器
tomcat(spring boot默认选择)
Jetty(长链接场景(比如聊天))
Undertow(不支持jsp)
Netty

3.1)、web容器自动配置原理
忽略
3.2)、嵌入式web容器启动原理
SpringApplication.run()-->ConfigurableApplicationContext.refresh()方法,里面水月洞天
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
网友评论