美文网首页
spring boot学习与实践练习2

spring boot学习与实践练习2

作者: 机灵鬼鬼 | 来源:发表于2020-11-09 17:28 被阅读0次

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适配各类日志的推荐模式图


image.png

spring boot中的日志依赖了那个日志框架

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>

日志底层依赖关系


image.png

总结:
1、)springboot底层使用slf4j+logback方式进行日志记录
2、)springboot也把其他日志框架转换成了slf4j
3、)如果我们要引入其他日志框架,一定要把这个框架默认的日志框架移除。

Spring Boot下的日志使用

   Logger logger = LoggerFactory.getLogger(this.getClass());

日志的配置
logging:
file:
path: E://bootlog

如何让自己的配置文件生效,不用SpringBoot默认的日志配置文件


image.png

上面说了,如果你使用的是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包的形式导入到工程中

image.png
 <!--引入jquery的webjars-->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>
image.png
直接可以在浏览器中访问到静态资源
http://localhost:8083/webjars/jquery/3.5.1/jquery.js image.png

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");
        }

http://localhost:8083/ 找首页

扩展spring MVC的特性

编写配置类(@Configuration),必须继承继承WebMvcConfigurer


image.png

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的自动配置类


image.png
@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");
    }
}
image.png

添加拦截器

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、)如果是浏览器访问不存在的地址
返回的是个错误页面


image.png
image.png

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


image.png

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


image.png

原理:可以自己查看它的实现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>
image.png

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默认的错误页面。


image.png image.png

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


image.png

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();
            }
        }
    }

相关文章

网友评论

      本文标题:spring boot学习与实践练习2

      本文链接:https://www.haomeiwen.com/subject/rooovktx.html