IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot总结 -> 正文阅读

[Java知识库]SpringBoot总结

基础功能

一、什么是SpringBoot???

? ? ? ? 概念:SpringBoot是Spring开源组织下的子项目,是Spring组件一站式的解决方案,主要是简化了使用Spring的难度,也节省了繁重的配置,还提供了各种的启动器,开发者可以容易上手。

? ? ? ? 优点:

? ? ? ? 1.容易上手,提升开发效率,为Spring开发提供一个更快、更广泛的入门体验;

? ? ? ? 2.开箱即用,远离繁琐的配置;

? ? ? ? 3.提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查以及一些外部化配置等。

? ? ? ? 4.没有代码生成,也无需xml配置;

? ? ? ? 5.避免大量的Maven导入还有各种版本冲突。

二、时代背景

1.微服务? ?微服务 (martinfowler.com)

????????用于描述将 软件应用程序 设计为 可独立部署服务套件 的特定方式。虽然这种架构风格没有精确的定义,但 围绕业务能力,自动化部署,端点智能 以及对 语言和数据 的 分散控制 ,组织存在某些共同特征。

  • 微服务是一种 架构风格
  • 一个应用?拆分 为一组小型服务
  • 每个服务运行在自己的进程内,也就是 可独立部署和升级
  • 服务之间使用 轻量级HTTP交互
  • 服务围绕 业务功能 拆分
  • 可以由 全自动部署机制 独立部署
  • 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

2.分布式? ? ??

  • 远程调用
  • 服务发现
  • 负载均衡
  • 服务容错
  • 配置管理
  • 服务监控
  • 链路追踪
  • 日志管理
  • 任务调度

解决方法:

  • SpringBoot + SpringCloud

3.云原生

  • 服务自愈
  • 弹性伸缩
  • 服务隔离
  • 自动化部署
  • 灰度发布
  • 流量治理

三、了解自动配置原理

1.依赖管理

????????父项目做依赖管理

1 依赖管理
2 <parent>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-parent</artifactId>
5 <version>2.3.4.RELEASE</version>
6 </parent>
7
8 他的父项目
9 <parent>
10 <groupId>org.springframework.boot</groupId>
11 <artifactId>spring-boot-dependencies</artifactId>
12 <version>2.3.4.RELEASE</version>
13 </parent>
14
15 几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制

????????开发导入starter场景启动器

1、见到很多 spring-boot-starter-* : *就某种场景

2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入

3、SpringBoot所有支持的场景

https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。

5、所有场景启动器最底层的依赖

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

????????无需关注版本号,自动版本仲裁

????????可以修改默认版本号

1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。

2、在当前项目里面重写配置

<properties>
 <mysql.version>5.1.43</mysql.version>
</properties>

2.自动配置

自动配好Tomcat

自动配好SpringMVC

自动配好Web常见功能,如:字符编码问题

默认的包结构

各种配置拥有默认值

按需加载所有自动配置项

3.容器功能

组件添加

? ? ? ? 1.@Configuration

????????Full模式与Lite模式? ? ??

????????????????配置 类组件之间无依赖关系?用Lite模式 加速容器启动过程,减少判断

????????????????配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式

#############################Configuration使用示例######################################################
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 *      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式默认。其他默认是否Lite模式
 *
 *
 *
 */
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}


################################@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        //3、从容器中获取组件

        Pet tom01 = run.getBean("tom", Pet.class);

        Pet tom02 = run.getBean("tom", Pet.class);

        System.out.println("组件:"+(tom01 == tom02));


        //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //保持组件单实例
        User user = bean.user01();
        User user1 = bean.user01();
        System.out.println(user == user1);


        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);

        System.out.println("用户的宠物:"+(user01.getPet() == tom));



    }
}

Full模式和Lite模式是针对spring配置而言的,和xml配置无关。

????????何时为Lite模式:

1.类上有@Component注解

2.类上有@ComponentScan注解

3.类上有@Import注解

4.类上有@ImportResource注解

5.类上没有任何注解,但是类中存在@Bean方法

6.类上有@Configuration(proxyBeanMethods = false)注解

Lite总结:运行时不用生成CGLIB子类,提高运行性能,降低启动时间,可以作为普通类使用。但是不能声明@Bean之间的依赖

????????何时为Full模式:

1.标注有@Configuration或者@Configuration(proxyBeanMethods = true)的类被称为Full模式的配置类。

Full模式总结:单例模式能有效避免Lite模式下的错误。性能没有Lite模式好

????????2.@Bean、@Component、@Controller、@Service、@Repository

????????根据它们的源码可以看到,Controller、Service、Repository其本质就是Component。

它存在的本质只是给开发者看的,对Spring而言它们就都是Component。

????????@Controller 控制层类,@Service 业务层类,@Repository 持久层类,@Component 无法归类到前3种时就称为组件。

? ? ? ? 3.@ComponentScan、@Import

@ComponentScan:扫描包(如果不写路径的话就默认扫描当前类的包路径)

@Import:自动从类中的无参构造函数创建一个实例注册到 IOC 容器中

【注意】@Import所创建的实例在 IOC 容器中默认的id名为类的全限定名,如 User 类就是:com.cui.bean.User

? ? ? ? 4.@Conditional

????????条件装配:满足Conditional指定的条件,则进行组件注入。

????????@Conditional中派生了很多的子注解,它们可以添加在@Bean注解的方法上也可以放在配置类上,在方法上满足所需条件时则执行方法中内容并注册到 IOC 容器中如果不满足条件则不注册,在配置类中满足需求时则执行配置类中所有的@Bean方法并注册到 IOC 容器中。如果不满足条件则不注册,以@ConditionalOnBean(name="tom")为例,当 IOC 容器中拥有id为tom的组件时才会满足条件,否则不满足条件。

原生配置文件引入

????????@ImportResource

????????@ImportResource("classpath:beans.xml")用来导入指定的配置文件。

配置绑定

????????如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;

????????1、@ConfigurationProperties

配置绑定:两种方式

????????1.@Compone

????????2.@ConfigurationProperties(prefix = "mycar")声明在要绑定的类的上方;

在配置类的上方声明@EnableConfigurationProperties(Car.class),开启对应类的配置绑定功能,把Car这个组件自动注入到容器中;

????????2、@EnableConfigurationProperties + @ConfigurationProperties

????????如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。

????????说白了 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入

????????3、@Component + @ConfigurationProperties

自动配置原理入门

????????引导加载自动配置类


@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}

????????1、@SpringBootConfiguration

????????@Configuration。代表当前是一个配置类

????????@ SpringBootConfiguration只是Spring标准@Configuration批注的替代方法。?两者之间的唯一区别是@SpringBootConfiguration允许自动找到配置。

????????2、@ComponentScan

????????指定扫描哪些,Spring注解;

????????3、@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

? ? ? ? ? ? ? ? 1)@AutoConfigurationPackage

????????????????自动配置包,指定了默认的包规则

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

? ? ? ? ? ? ? ? 2)@Import(AutoConfigurationImportSelector.class)

1、利用 getAutoConfigurationEntry(annotationMetadata); 给容器中批量导入一些组件
2、调用 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes) 获取到?所有需要导入到容器中?的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从 META-INF/spring.factories 位置来加载一个文件。
?? ?默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
? ? spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

按需开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置。

修改默认配置

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties

4.开发小技巧

1.Lombok

????????简化JavaBean开发

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
===============================简化JavaBean开发===================================
@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {

    private String name;
    private Integer age;

    private Pet pet;

    public User(String name,Integer age){
        this.name = name;
        this.age = age;
    }


}



================================简化日志开发===================================
@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(@RequestParam("name") String name){
        
        log.info("请求进来了....");
        
        return "Hello, Spring Boot 2!"+"你好:"+name;
    }
}

@Data---帮助生产getset方法

????????@Data应该是 包含了 @ToString、@EqualsAndHashCode、@Getter / @Setter和?@RequiredArgsConstructor的功能

@ToString---帮助生成ToString方法

@AllArgsConstructor---生成有参构造器

@NoArgsConstructor---生成无参构造方法

@EqualsAndHashCode---生成HashCode方法

2.dev-tools

核心功能

四、配置文件

1.文件类型

1.1 properties

1.2、yaml

????????YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。

????????非常适合用来做 以数据为中心 的配置文件

? ? ? ? 补充:

? ? ? ? application.properties的 配置优先级 高于application.yaml

????????基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义

????????数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
  • 对象:键值对的集合。map、hash、set、object
  • 数组:一组 按次序排列 的值。array、list、queue

2.Web开发

?2.1?SpringMVC自动配置概览

??1.ContentNegotiatingViewResolver and BeanNameViewResolver?

? ? 内容协商视图解析器和BeanName视图解析器

? ? ? ? 2.静态资源(包括webjars)

? ? ? ? 3.自动注册 Converter,GenericConverter,Formatter

? ? ? ? 4.支持 HttpMessageConverters

? ? ? ? 5.自动注册 MessageCodesResolver (国际化用)

? ? ? ? 6.静态index.html 页支持

? ? ? ? 7.自定义 Favicon

? ? ? ? 8.自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

????????不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

????????声明 WebMvcRegistrations 改变默认底层组件

????????使用 @EnableWebMvc+@Configuration+@DelegatingWebMvcConfiguration 全面接管SpringMVC

2.2?简单功能分析

2.2.1静态资源访问

1、静态资源目录

????????只要静态资源放在类路径下:

????????called?/static?(or?/public?or?/resources?or?/META-INF/resources

访问 : 当前项目根路径/ + 静态资源名

????????原理: 静态映射/**。

????????请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面.

? ? ? ? 补充:

? ? ? ? 1.默认情况下,Spring Boot 从类路径中的/static (或/public 或/resources 或/META-INF/resources)目录或 ServletContext 的根目录提供静态内容。

????????它使用 Spring MVC 中的 ResourceHttpRequestHandler,因此您可以通过添加自己的 webmvcconfigurer?和重写?resourcehandlers 方法来修改该行为。

? ? ? ? 2.静态资源一般包括图片、视频、js、css文件

改变默认的静态资源路径:

spring:
  mvc:
    static-path-pattern: /res/**

  resources:
    static-locations: [classpath:/haha/]

2、静态资源访问前缀

默认无前缀

spring:
  mvc:
    static-path-pattern: /res/**

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

3、webjar

自动映射 /webjars/**

WebJars - Web Libraries in Jars

对于webjar依赖中的静态资源,会被映射处理成两种请求路径:

/webjars/** ;/static-path-pattern属性值/webjars/**。

所以访问webjars中的静态资源时,静态请求前缀可加可不加。

2.2.2欢迎页支持

  • 静态资源路径下 index.html
  • 可以配置静态资源路径
  • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效

  resources:
    static-locations: [classpath:/haha/]
  • controller能处理/index

????????controller中配置@RequestMapping("/") return 返回的页面为欢迎页

2.2.3自定义 Favicon

favicon.ico 放在静态资源目录下即可。

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效

? ? ? ? 补充:

? ? ? ? 1.你存入一个静态资源,可以先看target编译目录中有没有,没有的话使用Maven 进行clean操作,再启动服务器编译,这样就有了,访问不到应该是没有编译到静态资源

? ? ? ? 2.Favicon默认是session级别的资源,也就是当次会话会一直共享该Favicon,所以更新了Favicon后需要禁用缓存,才能访问到最新的Favicon图标。

2.2.4静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效

????????创建xxxConfig类并实现WebMvcConfiger,就会使WebMvcAutoConfiguration配置类失效,因为在该类中有@ConditionalOnMissingBean(WebMvcConfigurationSupport.class),这个注解会触发 ,这是因为我们在自定义的xxxConfig类中实现了WebMvcConfiger,点进这个接口就可以知道它继承了WebMvcConfigurationSupport,所以springboot自动帮我们配置好的webMvcAutoConfiguration就会失效,注意只是我们在xxxConfig类中重写的default失效

配置文件的相关属性和xxx进行了绑定。

WebMvcProperties==spring.mvc

ResourceProperties==spring.resources

? ? ? ? 补充:新版本中

????????Resources WebProperties 前缀都为spring.web

????????WebMvcProperties 前缀为spring.mvc

3、请求参数处理

3.1rest使用与原理

  • @xxxMapping;
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作
    • 以前:/getUser ? 获取用户? /deleteUser 删除用户 ? /editUser ?修改用户 ? /saveUser 保存用户
    • 现在: /user GET-获取用户? ??DELETE-删除用户? ??PUT-修改用户? ??POST-保存用户
    • 核心Filter:HiddenHttpMethodFilter
      • 用法: 表单method=post,隐藏域 _method=put
      • SpringBoot中手动开启

????????????????扩展:如何把_method 这个名字换成我们自己喜欢的。

Rest原理(表单提交要使用REST的时候)

  • 表单提交会带上_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
    • 请求是否正常,并且是POST
      • 获取到_method的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。

Rest使用客户端工具,

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。

3.2请求映射原理

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet->doDispatch()

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

4.普通参数与基本注解

4.1 注解

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

4.2 Servlet API:

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

4.3复杂参数

MapModel(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

5.POJO封装过程

6.参数处理原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值

6.1、HandlerAdapter

0 - 支持方法上标注@RequestMapping

1 - 支持函数式编程的

6.2、执行目标方法

?6.3、参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么:

SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

  • 当前解析器是否支持解析这种参数
  • 支持就调用 resolveArgument

6.4、返回值处理器

ModelAndView、Model、View、ResponseBody、HttpEntity

五、数据响应与内容协商

?1、响应JSON

1.1、jackson.jar+@ResponseBody

给前端自动返回json数据;

1.2、返回值解析器原理

  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
  • 2、返回值处理器调用 handleReturnValue 进行处理
  • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
    • 1. 利用 MessageConverters 进行处理 将数据写为json
      • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
      • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理
        • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

SpringMVC到底支持哪些返回值

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

1.3、HTTPMessageConverter原理

1、MessageConverter规范

HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

例子:Person对象转为JSON。或者 JSON转为Person

2、默认的MessageConverter

2、内容协商

????????1、引入xml依赖

????????2、postman分别测试返回json和xml

????????????????只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

????????3、开启浏览器参数方式内容协商功能

????????确定客户端接收什么样的内容类型:

????????????????1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

????????????????2、最终进行内容协商返回给客户端json即可。

?????????4、内容协商原理

????????????????1、判断当前响应头中是否已经有确定的媒体类型。MediaType

????????????????2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】

????????????????????????contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略

????????????????????????HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型

????????????????

????????????????3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

????????????????4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。

????????????????5、客户端需要【application/xml】。服务端能力【10种、json、xml】

????????????????6、进行内容协商的最佳匹配媒体类型

????????????????7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。

3.自定义 MessageConverter

实现多协议数据兼容。json、xml、x-guigu

0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

1、Processor 处理方法返回值。通过 MessageConverter 处理

2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

3、内容协商找到最终的 messageConverter

SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer

六、视图解析与模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。

1、视图解析

?????????1、视图解析原理流程

1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址

2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer

3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。

4、processDispatchResult 处理派发结果(页面改如何响应)

  • render(mv, request, response); 进行页面渲染逻辑
    • 根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
      • 1、所有的视图解析器尝试是否能根据当前返回值得到View对象
      • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
      • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
      • 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
        • RedirectView 如何渲染【重定向到一个页面】
        • 1、获取目标url地址
        • 2、response.sendRedirect(encodedURL);

视图解析:

    • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
    • 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
    • 返回值是普通字符串: new ThymeleafView()--->

2、模板引擎-Thymeleaf

1、thymeleaf简介

????????现代化、服务端Java模板引擎

2、thymeleaf使用

????????1、引入Starter

????????2、自动配置好了thymeleaf

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面

3、页面开发

七、拦截器

1、HandlerInterceptor 接口

? ? ? ? 3个方法要记住,这里不写出来,还有prehandler()里面执行的是拦截的主要逻辑

2、配置拦截器

1、编写一个拦截器实现HandlerInterceptor接口
2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】

3、拦截器原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】

2、先来顺序执行 所有拦截器的 preHandle方法

  • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 2、如果当前拦截器返回为false。直接倒序执行所有已经执行的拦截器的 afterCompletion;

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

八、文件上传

1、页面表单

2、文件上传代码

3、自动配置原理

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】
  • 原理步骤
    • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
    • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>

FileCopyUtils。实现文件流的拷贝

九、异常处理

1、错误处理

1、默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

  • 要对其进行自定义,添加View解析为error
  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析;

2、定制错误处理逻辑

  • 自定义错误页
    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
  • ErrorViewResolver 实现自定义处理异常;
    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver

3、异常处理自动配置原理

  • ErrorMvcAutoConfiguration 自动配置异常处理规则
    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
      • DefaultErrorAttributes:定义错误页面中可以包含哪些数据
    • 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
      • 处理默认 /error 路径的请求;页面响应 new ModelAndView("error", model);
      • 容器中有组件 View->id是error;(响应默认错误页)
      • 容器中放组件 BeanNameViewResolver(视图解析器):按照返回的视图名作为组件的id去容器中找View对象。
    • 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
      • 如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面
      • error/404、5xx.html

如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)

4、异常处理步骤流程

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException

2、进入视图解析流程(页面渲染?)

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

  • 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器
  • 2、系统默认的 异常解析器;
    • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
    • 2、默认没有任何人能处理异常,所以异常会被抛出
      • 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
      • 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
      • 3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
      • 4、模板引擎最终响应这个页面 error/500.html

十、Web原生组件注入(Servlet、Filter、Listener)

1、使用Servlet API

@ServletComponentScan(basePackages = "com.atguigu.admin") :指定原生Servlet组件都放在那里

@WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器?

@WebFilter(urlPatterns={"/css/*","/images/*"})

@WebListener

推荐可以这种方式:

扩展:DispatchServlet 如何注册进来

  • 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
  • 通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet 配置进来。
  • 默认映射的是 / 路径。

2、使用RegistrationBean

ServletRegistrationBean,?FilterRegistrationBean, and?ServletListenerRegistrationBean

十一、嵌入式Servlet容器

1、切换嵌入式Servlet容器

2、定制Servlet容器

十二、定制化原理

1、定制化的常见方式

  • 修改配置文件;
  • xxxxxCustomizer;
  • 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
  • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
  • @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能

2、原理分析套路

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项

数据访问

1、SQL

1、数据源的自动配置-HikariDataSource

1、导入JDBC场景

(数据源、jdbc、事务)

2、分析自动配置

自动配置的类

  • DataSourceAutoConfiguration : 数据源的自动配置
    • 修改数据源相关的配置:spring.datasource
    • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    • 底层配置好的连接池是:HikariDataSource
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以对数据库进行crud
    • 可以修改这个配置项@ConfigurationProperties(prefix = "spring.jdbc") 来修改JdbcTemplate
    • @Bean@Primary JdbcTemplate;容器中有这个组件
  • JndiDataSourceAutoConfiguration: jndi的自动配置
  • XADataSourceAutoConfiguration: 分布式事务相关的

3、修改配置项(YAML)

(url、username、passowrd、driver-class-name)

4、测试

2、使用Druid数据源

1.StatViewServlet

StatViewServlet的用途包括:

  • 提供监控信息展示的html页面
  • 提供监控信息的JSON API

2.StatFilter

用于统计监控信息;如SQL监控、URI监控

3、使用官方starter方式

单元测试

????????Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

????????作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

????????JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

????????JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

????????JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。

????????JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

以前:

@SpringBootTest + @RunWith(SpringTest.class)

SpringBoot整合Junit以后。

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

2、JUnit5常用注解

3、断言(assertions)

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:

检查业务逻辑返回的数据是否合理。

所有的测试运行结束以后,会有一个详细的测试报告;

1、简单断言

?2、数组断言

通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

3、组合断言

????????assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。

4、异常断言

在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。

而JUnit5提供了一种新的断言方式Assertions.assertThrows()?,配合函数式编程就可以进行使用。

5、超时断言

????????Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

6、快速失败

????????通过 fail 方法直接使得测试失败

4、前置条件(assumptions)

????????JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

????????assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

5、嵌套测试

????????JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach@AfterEach 注解,而且嵌套的层次没有限制。

6、参数化测试

????????参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

????????利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@EnumSource: 表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

????????当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

7、迁移指南

在进行迁移的时候需要注意如下的变化:

  • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
  • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
  • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
  • 把@Ignore 替换成@Disabled。
  • 把@Category 替换成@Tag。
  • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。

指标监控

1、SpringBoot Actuator

?未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

(SpringBoot1.x和2.x的区别要了解)

2、Actuator Endpoint

1、最常使用的端点

最常用的Endpoint

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录

2、Health Endpoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制

3、Metrics Endpoint

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics

4、管理Endpoints

1、开启与禁用Endpoints

  • 默认所有的Endpoint除过shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.<endpointName>.enabled = true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoint

2、暴露Endpoints

支持的暴露方式

  • HTTP:默认只暴露healthinfo Endpoint
  • JMX:默认暴露所有Endpoint
  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则

原理解析

1、Profile功能

为了方便多环境适配,springboot简化了profile功能。

????????

? ? ? ? 补充:什么是Profiles?

? ? ? ? Spring Profiles允许用户根据配置文件(dev,test,prod等)来注册bean。因此,当应用程序在开发中运行时,只有某些bean可以加载,而在PRODUCTION,某些bean是可以加载的。假设我们要求Swagger文档仅适用于QA环境,并且禁用掉所有其他的文档。这时候可以使用配置文件来完成。

? ? ? ? 补充:Swagger?是一个用于生成、描述和调用 RESTful 接口的 Web 服务。通俗的来讲,Swagger 就是将项目中所有(想要暴露的)接口展现在页面上,并且可以进行接口调用和测试的服务。

1、application-profile功能

  • 默认配置文件 application.yaml;任何时候都会加载
  • 指定环境配置文件 application-{env}.yaml
  • 激活指定环境
    • 配置文件激活
    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
      • 修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效
  • 同名配置项,profile配置优先

2、@Profile条件装配功能

3、profile分组

2、外部化配置

1、外部配置源

常用:Java属性文件YAML文件环境变量命令行参数

2、配置文件查找位置

(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

3、配置文件加载顺序:

  1.  当前jar包内部的application.properties和application.yml
  2.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3.  引用的外部jar包的application.properties和application.yml
  4.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

4、指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

3、自定义starter

1、starter启动原理

  • starter-pom引入 autoconfigurer 包
  • starter-pom引入 autoconfigurer 包

????????autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类

????????编写自动配置类 xxxAutoConfiguration -> xxxxProperties

????????????????@Configuration

????????????????@Conditional

? ? ? ? ? ? ? ? @EnableConfigurationProperties

? ? ? ? ? ? ? ? @Bean

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

2、自定义starter

atguigu-hello-spring-boot-starter(启动器)

atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)

4、SpringBoot原理

Spring原理、SpringMVC原理、自动配置原理、SpringBoot原理

1、SpringBoot启动过程

创建 SpringApplication

  • 保存一些信息。
  • 判定当前应用的类型。ClassUtils。Servlet
  • bootstrappers:初始启动引导器(List<Bootstrapper>):去spring.factories文件中找 org.springframework.boot.Bootstrapper
  • ApplicationContextInitializer;去spring.factoriesApplicationContextInitializer;List<ApplicationContextInitializer<?>> initializers
  • ApplicationListener ;应用监听器。spring.factoriesApplicationListenerList<ApplicationListener<?>> listeners

运行 SpringApplication

  • StopWatch
  • 记录应用的启动时间
  • 创建引导上下文(Context环境)createBootstrapContext()
    • 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置

  • 让当前应用进入headless模式。java.awt.headless
  • 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】

????????????????getSpringFactoriesInstances 去spring.factoriesSpringApplicationRunListener

  • 遍历 SpringApplicationRunListener 调用 starting 方法;
    • 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
  • 保存命令行参数:ApplicationArguments
  • 准备环境 prepareEnvironment();
    • 返回或者创建基础环境信息对象。StandardServletEnvironment
      • 配置环境信息对象。
        • 读取所有的配置源的配置属性值

????????????????绑定环境信息

????????????????监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成

  • 创建IOC容器(createApplicationContext())
    • 根据项目类型(Servlet)创建容器,
    • 当前会创建 AnnotationConfigServletWebServerApplicationContext
  • 准备ApplicationContext IOC容器的基本信息? ?prepareContext()
    • 保存环境信息
    • IOC容器的后置处理流程
    • 应用初始化器:applyInitializers
      • 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
      • 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared

? ? ? ? ? ? ? ? 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;

????????????????调用所有runners;callRunners()

????????????????????????获取容器中的 ApplicationRunner

????????????????????????获取容器中的 CommandLineRunner

????????????????????????合并所有runner并且按照@Order进行排序

????????????????????????遍历所有的runner。调用 run 方法

  • 如果以上有异常,
    • ???????调用Listener 的 failed
  • 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
  • running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed

2、Application Events and Listeners

ApplicationContextInitializer

ApplicationListener

SpringApplicationRunListener

3、ApplicationRunner?与 CommandLineRunner

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 00:56:31  更:2022-09-04 01:01:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:04:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码