美文网首页Spring Boot
Spring Boot 2.0深度实践之核心技术篇(二)

Spring Boot 2.0深度实践之核心技术篇(二)

作者: liuhuiAndroid | 来源:发表于2018-09-23 21:43 被阅读84次

第6章 Web MVC REST 应用

6-1 Web MVC REST应用和REST介绍

  • REST 简介
    REST = RESTful = Representational State Transfer,is one way of providing interoperability between computer systems on the Internet.
  • Web MVC REST 支持
  • REST 内容协商
  • CORS
  • 架构约束
    统一接口、C/S架构、无状态、可缓存、分层系统、按需代码
  • 三个基本方面
    资源识别、资源操作、自描述消息

6-2 Web MVC REST 支持

注解驱动

  • 定义:@Controller、组合注解@RestController


    图片.png
  • 映射:@RequestMapping、别名@*Mapping


    图片.png
  • 请求:@RequestParam、@RequestHeader、@CookieValue


    图片.png
  • 响应:@ResponseBody、ResponseEntity


    图片.png
  • 拦截:@RestControllerAdvice


    图片.png
  • 跨域:@CrossOrigin


    图片.png

6-3 REST 内容协商

核心组件

  • 生产媒体类型:@RequestMapping#produces
  • HTTP消息转换器:HttpMessageConverter
  • REST配置器:WebMvcConfigurer
  • 处理方法参数解析器:HandlerMethodArgumentResolver
  • 处理方法返回值解析器:HandlerMethodReturnValueHandler
  • 内容协商管理器:ContentNegotiationManager
  • 媒体类型:MediaType
  • 消费媒体类型:@RequestMapping#consumes
  • 生产媒体类型:@RequestMapping#produces
  • HTTP消息转换器:HttpMessageConverter
  • REST配置器:WebMvcConfigurer
图片.png

6-4 Web MVC REST 处理流程

图片.png

6-5 Web MVC REST 处理流程源码分析

@SpringBootApplication(scanBasePackages = "com.lh.diveinspringboot")
public class SpringBootRestBootstrap {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRestBootstrap.class, args);
    }
    
}

@RestController
public class HelloWorldRestController {

    @GetMapping(value = "/hello-world")
    public String helloWorld(@RequestParam(required = false) String message) {
        return "Hello,World! : " + message;
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

}

结合处理流程图分析源码

6-6 Web MVC REST 内容协商处理流程

图片.png

6-7 Web MVC REST 内容协商处理流程源码分析

@RestController
public class UserRestController {

    @PostMapping(value = "/echo/user")
    public User user(@RequestBody User user) {
        return user;
    }

}

6-8 理解媒体类型

理解请求的媒体类型
经过 ContentNegotiationManager 的 ContentNegotiationStrategy 解析请求中的媒体类型,比如: Accept 请求头

  • 如果成功解析,返回合法 MediaType 列表
  • 否则,返回单元素 / 媒体类型列表 - MediaType.ALL

理解可生成的媒体类型
返回 @Controller HandlerMethod @RequestMapping.produces() 属性所指定的 MediaType 列表:

  • 如果 @RequestMapping.produces() 存在,返回指定 MediaType 列表
  • 否则,返回已注册的 HttpMessageConverter 列表中支持的 MediaType 列表

理解 @RequestMapping#consumes
用于 @Controller HandlerMethod 匹配:

  • 如果请求头 Content-Type 媒体类型兼容 @RequestMapping.consumes() 属性,执行该 HandlerMethod
  • 否则 HandlerMethod 不会被调用

理解 @RequestMapping#produces
用于获取可生成的 MediaType 列表

  • 如果该列表与请求的媒体类型兼容,执行第一个兼容 HttpMessageConverter 的实现,默认
    @RequestMapping#produces 内容到响应头 Content-Type
  • 否则,抛出 HttpMediaTypeNotAcceptableException , HTTP Status Code : 415

6-9 理解媒体类型源码分析

@RestController
public class UserRestController {

    @PostMapping(value = "/echo/user",
            produces = "application/json;charset=UTF-8")
    public User user(@RequestBody User user) {
        return user;
    }

}
图片.png
@RestController
public class UserRestController {

    @PostMapping(value = "/echo/user",
            produces = "application/json;charset=GBK",
            consumes = "application/json;charset=UTF-8")
    public User user(@RequestBody User user) {
        return user;
    }

}

6-10 扩展 REST 内容协商-反序列化部分

自定义 HttpMessageConverter

需求:
实现 Content-Type 为 text/properties 媒体类型的 HttpMessageConverter

实现步骤:

  • 实现 HttpMessageConverter - PropertiesHttpMessageConverter
  • 配置 PropertiesHttpMessageConverter 到 WebMvcConfigurer#extendMessageConverters

代码:

public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {

    public PropertiesHttpMessageConverter() {
        // 设置支持的 MediaType
        super(new MediaType("text", "properties"));
    }

    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        // Properties -> String
        // OutputStream -> Writer
        HttpHeaders httpHeaders = outputMessage.getHeaders();
        MediaType mediaType = httpHeaders.getContentType();
        // 获取字符编码
        Charset charset = mediaType.getCharset();
        // 当 charset 不存在时,使用 UTF-8
        charset = charset == null ? Charset.forName("UTF-8") : charset;
        // 字节输出流
        OutputStream outputStream = outputMessage.getBody();
        // 字符输出流
        Writer writer = new OutputStreamWriter(outputStream, charset);
        // Properties 写入到字符输出流
        properties.store(writer,"From PropertiesHttpMessageConverter");
    }

    @Override
    protected Properties readInternal(Class<? extends Properties> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

        // 字符流 -> 字符编码
        // 从 请求头 Content-Type 解析编码
        HttpHeaders httpHeaders = inputMessage.getHeaders();
        MediaType mediaType = httpHeaders.getContentType();
        // 获取字符编码
        Charset charset = mediaType.getCharset();
        // 当 charset 不存在时,使用 UTF-8
        charset = charset == null ? Charset.forName("UTF-8") : charset;

        // 字节流
        InputStream inputStream = inputMessage.getBody();
        InputStreamReader reader = new InputStreamReader(inputStream, charset);
        Properties properties = new Properties();
        // 加载字符流成为 Properties 对象
        properties.load(reader);
        return properties;
    }

    @Override
    public Properties read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return readInternal(null, inputMessage);
    }
}

RestWebMvcConfigurer#extendMessageConverters

@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {

    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.set(0, new PropertiesHttpMessageConverter()); // 添加到集合首位
    }
    
}
@Controller
public class PropertiesRestController {

    @PostMapping(
            value = "/add/props",
            consumes = "text/properties;charset=UTF-8"
    )
    public Properties addProperties(Properties properties) {
        return properties;
    }
    
}

6-11 扩展 REST 内容协商-序列化部分

代码:
PropertiesHttpMessageConverter#writeInternal

public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {

    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        // Properties -> String
        // OutputStream -> Writer
        HttpHeaders httpHeaders = outputMessage.getHeaders();
        MediaType mediaType = httpHeaders.getContentType();
        // 获取字符编码
        Charset charset = mediaType.getCharset();
        // 当 charset 不存在时,使用 UTF-8
        charset = charset == null ? Charset.forName("UTF-8") : charset;
        // 字节输出流
        OutputStream outputStream = outputMessage.getBody();
        // 字符输出流
        Writer writer = new OutputStreamWriter(outputStream, charset);
        // Properties 写入到字符输出流
        properties.store(writer,"From PropertiesHttpMessageConverter");
    }

}

6-12 自定义 Resolver 实现

自定义 HandlerMethodArgumentResolver

需求:

  • 不依赖 @RequestBody , 实现 Properties 格式请求内容,解析为 Properties 对象的方法参数
  • 复用 PropertiesHttpMessageConverter

实现步骤:

  • 实现 HandlerMethodArgumentResolver - PropertiesHandlerMethodArgumentResolver
  • RequestMappingHandlerAdapter#setArgumentResolvers

代码:
PropertiesHandlerMethodArgumentResolver
RestWebMvcConfigurer#addArgumentResolvers

6-13 自定义 Handler 实现

自定义 HandlerMethodReturnValueHandler

需求:

  • 不依赖 @ResponseBody ,实现 Properties 类型方法返回值,转化为 Properties 格式内容响应内容
  • 复用 PropertiesHttpMessageConverter

实现步骤:

  • 实现 HandlerMethodReturnValueHandler - PropertiesHandlerMethodReturnValueHandler
  • RequestMappingHandlerAdapter#setReturnValueHandlers

代码:
@Controller
PropertiesHandlerMethodReturnValueHandler

6-14 REST 内容协商CORS

Cross-Origin Resource Sharing(CORS)

  • 注解驱动:@CrossOrigin
  • 代码驱动:WebMvcConfigurer#addCorsMappings
  • Filter组件:CorsFilter

代码:

127.0.0.1 api.rest.org
@Controller
public class HelloWorldController {

    @RequestMapping("")
    public String index() {
        return "index";
    }

}
@RestController
public class HelloWorldRestController {

    @CrossOrigin("*")
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

}
@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {

    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*");
    }

}

第7章 渐行渐远的 Servlet

7-1 渐行渐远的Servlet

  • Servlet 简介
  • Spring Servlet Web
  • Spring Boot Servlet Web

7-2 Servlet 核心 API

图片.png

7-3 Servlet 版本

  • 基本概念


    图片.png

7-4 Servlet 注册

  • 传统web.xml注册方式
  • 注解注册方式
  • 编码注册方式

7-5 理解 Servlet 组件生命周期

理解 Servlet 生命周期

  • 初始化:Servlet#init(ServletConfig)
  • 服务:Servlet#service(ServletRequest,ServletResponse)
  • 销毁:Servlet#destory()

理解 Filter 生命周期

  • 初始化:Filter#init(FilterConfig)
  • 过滤:Filter#doFilter(ServletRequest,ServletResponse,FilterChain)
  • 销毁:Filter#destory()

理解 ServletContext 生命周期

  • 初始化:ServletContextListener#contextInitialized
  • 销毁:ServletContextListener#contextDestoryed()

DispatcherServlet初始化过程

图片.png
结合源码分析

7-6 Servlet 异步支持

  • DeferredResult 支持
  • Callable 支持
  • CompletionStage 支持

代码:

@ComponentScan(basePackages = "com.lh.diveinspringboot.controller")
public class DefaultAnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    @Override
    protected Class<?>[] getRootConfigClasses() { // web.xml
        return new Class[0];
    }

    @Override
    protected Class<?>[] getServletConfigClasses() { // DispatcherServlet
        return new Class[]{
                getClass() // 返回当前类
        };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}
@RestController
public class HelloWorldAsyncController {

    @GetMapping("/hello-world")
    public DeferredResult<String> helloWorld() {
        DeferredResult<String> result = new DeferredResult<>();
        result.setResult("Hello,World");
        result.onCompletion(() -> System.out.println("执行结束"));
        return result;
    }
}
mvn -Dmaven.test.skip -U clean package // 不执行测试用例,也不编译测试用例类
java -jar target\spring-servlet-0.1-SNAPSHOT-war-exec.jar

7-7 DeferredResult 增加线程信息

@RestController
public class HelloWorldAsyncController {

    @GetMapping("/hello-world")
    public DeferredResult<String> helloWorld() {
        DeferredResult<String> result = new DeferredResult<>();
        result.setResult("Hello,World");
        println("Hello,World");
        result.onCompletion(() -> println("执行结束"));
        return result;
    }

    private static void println(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
    }

}

查看日志发现全部是在同一个线程中处理。

7-8 DeferredResult 设置 timeout 以及处理回调

@RestController
public class HelloWorldAsyncController {

    @GetMapping("/hello-world")
    public DeferredResult<String> helloWorld() {
        DeferredResult<String> result = new DeferredResult<>(50L);
        println("Hello,World");
        result.onCompletion(() -> println("执行结束"));
        result.onTimeout(() -> println("执行超时"));
        return result;
    }

    private static void println(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
    }

}

查看日志发现回调是在另一个线程中处理。

7-9 DeferredResult 异步执行

@RestController
@EnableScheduling
public class HelloWorldAsyncController {

    private final BlockingQueue<DeferredResult<String>> queue = new ArrayBlockingQueue<>(5);

    // 超时随机数
    private final Random random = new Random();

    // 这个注解在容器启动时便会生效,5秒执行一次任务
    @Scheduled(fixedRate = 5000)
    public void process() throws InterruptedException { // 定时操作
        DeferredResult<String> result = null;
        do {
            result = queue.take();
            // 随机超时时间
            long timeout = random.nextInt(100);
            // 模拟等待时间,RPC 或者 DB 查询
            Thread.sleep(timeout);
            // 计算结果
            result.setResult("Hello,World");
            println("执行计算结果,消耗:" + timeout + " ms.");
        } while (result != null);
    }

    @GetMapping("/hello-world")
    public DeferredResult<String> helloWorld() {
        DeferredResult<String> result = new DeferredResult<>(50L);
        // 将指定元素插入此队列中
        queue.offer(result);
        println("Hello,World");
        result.onCompletion(() -> println("执行结束"));
        result.onTimeout(() -> println("执行超时"));
        return result;
    }

    private static void println(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
    }

}

7-10 Callable 异步执行

@RestController
public class HelloWorldAsyncController {

    @GetMapping("/callable-hello-world")
    public Callable<String> callableHelloWorld() {
        final long startTime = System.currentTimeMillis();
        println("Hello,World");
        return () -> {
            long costTime = System.currentTimeMillis() - startTime;
            println("执行计算结果,消耗:" + costTime + " ms.");
            return "Hello,World";
        };
    }

    private static void println(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
    }

}

7-11 CompletionStage 异步执行

@RestController
public class HelloWorldAsyncController {

    @GetMapping("/completion-stage")
    public CompletionStage<String> completionStage(){
        final long startTime = System.currentTimeMillis();
        println("Hello,World");
        return CompletableFuture.supplyAsync(()->{
            long costTime = System.currentTimeMillis() - startTime;
            println("执行计算结果,消耗:" + costTime + " ms.");
            return "Hello,World"; // 异步执行结果
        });
    }

    private static void println(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("HelloWorldAsyncController[" + threadName + "]: " + object);
    }

}

7-12 MVC 异步支持原理分析

  • HandlerMethodReturnValueHandler
  • Servlet 3.0 AsyncContext
  • DispatcherServlet 整合

7-13 异步 Servlet 实现

@WebServlet(
        asyncSupported = true, // 激活异步特性
        name = "asyncServlet", // Servlet 名字
        urlPatterns = "/async-servlet"
)
public class AsyncServlet extends HttpServlet {

    @Override
    public void service(HttpServletRequest request, HttpServletResponse resp) throws IOException {
        // 判断是否支持异步
        if (request.isAsyncSupported()) {
            // 创建 AsyncContext
            AsyncContext asyncContext = request.startAsync();
            // 设置超时时间
            asyncContext.setTimeout(1000L);
            asyncContext.addListener(new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent event) {
                    println("执行完成");
                }

                @Override
                public void onTimeout(AsyncEvent event) {
                    HttpServletResponse servletResponse = (HttpServletResponse)event.getSuppliedResponse();
                    servletResponse.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
                    println("执行超时");
                }

                @Override
                public void onError(AsyncEvent event) {
                    println("执行错误");
                }

                @Override
                public void onStartAsync(AsyncEvent event) {
                    println("开始执行");
                }
            });

            println("Hello,World");
            ServletResponse servletResponse = asyncContext.getResponse();
            // 设置响应媒体类型
            servletResponse.setContentType("text/plain;charset=UTF-8");
            // 获取字符输出流
            PrintWriter writer = servletResponse.getWriter();
            writer.println("Hello,World");
            writer.flush();
        }
    }

    private static void println(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("AsyncServlet[" + threadName + "]: " + object);
    }

}

Java Specification Requests (JSR) : https://github.com/mercyblitz/jsr

7-14 DefferedResult 实现原理

HandlerMethodReturnValueHandler 源码
DeferredResultMethodReturnValueHandler 源码
AsyncContext 源码

7-15 Spring Boot 嵌入式 Servlet 容器限制

  • 不支持 web.xml 部署
  • 不支持 ServletContainerInitializer 接口
  • 注解驱动限制

代码:
spring-boot-servlet 项目
SpringBootServletBootstrap

7-16 Spring Boot 嵌入式 Servlet 容器限制 原理分析

没听懂

7-17 Spring Boot 应用传统 Servlet 容器部署

  • SpringBootServletInitializer
  • Spring 3.1 + SPI WebApplicationInitializer
  • Servlet 3.0 + SPI ServletContainerInitializer

7-18 扩展 SpringBootServletInitializer

  • 使用 Tomcat7 插件
  • 使用 Tomcat8 插件
    DefaultSpringBootServletInitializer

7-19 构建应用

7-20 渐行渐远的Servlet总结

  • Spring Servlet Web
  • Spring Boot Servlet Web
  • Spring Boot 应用传统 Servlet 容器部署

第8章 从 Reactive 到 WebFlux

8-1 从 Reactive 到 WebFlux

  • 理解 Reactive
  • Reactive Streams 规范
  • Reactor 框架运用
  • 走向 Spring WebFlux

温故知新

  • 反应堆模式(Reactor) 同步非阻塞
  • Proactor模式 异步非阻塞
  • 观察者模式(Observer)
  • 迭代器模式(Iterator)
  • Java并发模型

8-2 关于 Reactive 的一些说法

  • Reactive 是异步非阻塞编程
  • Reactive 能够提升程序性能
  • Reactive 能解决传统编程模型遇到的困境

Reactive 实现框架

  • RxJava:Reactive Extensions Java
  • Reactor:Spring WebFlux Reactive 类库
  • Flow API:Java 9 Flow API 实现

8-3 理解阻塞的弊端和并行的复杂

Reactor 认为阻塞可能是浪费的

  • 阻塞导致性能瓶颈和浪费资源
  • 增加线程可能会引起资源竞争和并发问题
  • 并行的方式不是银弹(不能解决所有问题)

理解阻塞的弊端
DataLoader.java
结论:由于加载过程串行执行的关系,导致消耗实现线性累加。Blocking 模式即串行执行 。

理解并行的复杂
ParallelDataLoader.java
结论:明显地,程序改造为并行加载后,性能和资源利用率得到提升,消耗时间取最大者。

8-4 Reactor 认为异步不一定能够救赎

Reactor 认为异步不一定能够救赎

  • Callbacks 是解决非阻塞的方案,然而他们之间很难组合,并且快速地将代码引导至 "Callback Hell"的不归路
  • Futures 相对于 Callbacks 好一点,不过还是无法组合,不过 CompletableFuture 能够提升这方面的不足

8-5 理解 Callback Hell

JavaGUI.java
结论:

8-6 理解 Future 阻塞问题

FutureBlockingDataLoader.java
结论:

8-7 理解 Future 链式问题

ChainDataLoader.java
结论:

8-8 Reactive Streams JVM 认为异步系统和资源消费需要特殊处理

观点归纳:

8-9 Reactive Programming 定义

定义:

  • 维基百科
  • The Reactive Manifesto
  • Spring Framework
  • ReactiveX
  • Reactor
  • @andrestaltz

8-10 Reactive Manifesto 定义

关键字:
侧重点:

8-11 维基百科

关键字:
侧重点:
技术连接:

8-12 Spring Framework 定义

关键字:
侧重点:
技术连接:

8-13 ReactiveX 定义

关键字:
侧重点:
技术连接:

8-14 Reactor 定义

关键字:
侧重点:
技术连接:

8-15 andrestaltz 定义

关键字:
侧重点:
技术连接:

8-16 Reactive Programming 特性:编程模型

Reactive 编程模型

  • 语言模型:响应式编程+函数式编程
  • 编发模型:多线程编程
  • 对立模型:命令式编程

小结:

8-17 Reactive Programming 特性:数据结构

Reactive 数据结构

  • 流式
  • 序列
  • 事件

小结:

Reactive 设计模式

  • 扩展模式:观察者
  • 对立模式:迭代器
  • 混合模式:反应堆(Reactor)、Proactor

小结:

8-18 Reactive Programming 特性:并发模型

  • 非阻塞
    同步
    异步

小结:

8-19 Reactive Programming 使用场景

Reactive Streams JVM
Spring Framework
ReactiveX
Reactor

8-20 Reactive Streams 规范:定义

关键字:

  • 潜在无界性
  • 序列
  • 异步传递
  • 非阻塞背压

见Github:https://github.com/reactive-streams/reactive-streams-jvm

8-21 Reactive Streams 规范:API和事件

  • Publisher:数据发布者(上游)
  • Subscriber:数据订阅者(下游)
  • Subscription:订阅信号
  • Processor:Publisher和Subscriber混合体

Subscriber信号事件:

  • onSubscribe:当下游订阅时
  • onNext:当下游接收数据时
  • onComplete:当数据流执行完成时
  • onError:当数据流执行错误时

Subscription信号操作:

  • request:请求上游元素的数量
  • cancel:请求停止发送数据并且清除资源

8-22 Reactive Streams 规范:背压

  • 维基百科
    关键字
  • Reactive Streams JVM
  • Reactor

总结背压

8-23 Reactor 框架运用 - 核心 API

核心 API

  • Mono:0-1的异步结果
  • Flux:0-N的异步序列
  • Scheduler:Reactor调度线程池

8-24 Reactor 框架运用实战(上)

依赖
FluxDemo

8-25 Reactor 框架运用实战(下)

FluxDemo 背压

8-26 走向 Spring WebFlux

Reactive Spring Web

  • 背景介绍
  • 对比Spring Web MVC

第9章 WebFlux 核心

9-1 WebFlux 核心

  • Reactor API
  • 基本介绍

9-2 官方引入WebFlux的动机分析

  • 动机
    去Servlet化

9-3 回顾Reactive

  • 定义Reactive

9-4 编程模型:注解驱动

  • 注解驱动

9-5 Java 函数编程基础

springboot-webflux

  • Java函数编程基础

9-6 编程模型:函数式端点 - Functional Endpoints

WebFluxApplication
hello-world

9-7 WebFlux 核心 - 并发模型

  • 非阻塞
  • 异步
  • Spring官方说明

代码:
WebFluxApplication
mvc、mono

9-8 WebFlux 核心 - 核心组件

  • HttpHandler API
  • WebHandler API
  • Web MVC VS WebFlux

9-9 WebFlux 核心处理流程 - 函数式端点组件请求处理流程

见大纲

9-10 WebFlux 核心处理流程 - 注解驱动组件请求处理流程

见大纲

9-11 WebFlux 核心 - 使用场景

9-12 WebFlux 核心 - 课堂总结

9-13 WebFlux 核心 - 课程彩蛋

源码

相关文章

  • 将SpringMVC项目转为Spring-boot项目

    本文是基于慕课网小马哥的 《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解做的笔记...

  • Spring--视图内容协商(一)

    本文是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • Reactive概念的简单记录

    本文是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • SpringMVC基于Servlet异步支持

    本文是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • Spring--视图内容协商(二)

    本文是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • Springboot 2.0---WebFlux初识

    本文是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • Springboot 2.0---WebFlux请求处理流程

    笔记是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • Springboot--外部化配置(二)

    笔记是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • Springboot--扩展外部化配置(一)

    笔记是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容结合自己的需要和理解...

  • Springboot 2.0---WebFlux核心组件的初始化

    核心组件初始化流程 笔记是学习了小马哥在慕课网的课程的《Spring Boot 2.0深度实践之核心技术篇》的内容...

网友评论

    本文标题:Spring Boot 2.0深度实践之核心技术篇(二)

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