SpringBoot2学习笔记

信息来源:https://www.bilibili.com/video/BV19K4y1L7MT?p=5&vd_source=3969f30b089463e19db0cc5e8fe4583a

作者提供的文档:https://www.yuque.com/atguigu/springboot

作者提供的代码:https://gitee.com/leifengyang/springboot2

-------------------------------------------------------------------------------------------------------

简明内容:

1、springboot简介
通过starter及第三方库快速创建spring应用

2、Spring注解
@Bean:给容器中添加组件,以方法名作为组件id
@Configuration:配置类组件,相当于创建了beans.xml配置文件
@Component:一般代表它是一个普通POJO组件
@Controller:代表它是一个控制器
@Service:代表它是一个业务逻辑组件
@Repository:代表它是一个数据库层组件

@ComponentScan:定义包扫描规则
@Import:给容器中自动引入组件
@Conditional:满足Conditional指定的条件,则进行组件注入
@ConfigurationProperties:含参数prefix,把一些内容写到配置文件中,只需要通过该注解即可封装到JavaBean中
@SpringBootConfiguration:代表当前是一个配置类
@ComponentScan:指定扫描哪些包

@GetMapping:将HTTP的GET请求映射到指定的处理方法上
@PostMapping:将HTTP的POST请求映射到指定的处理方法上
@PathVariable:将URL绑定的占位符映射到控制器的参数中
@RequestHeader:将请求头中的参数映射到控制器的参数中
@RequestParam:将请求参数映射到控制器的参数中
@RequestPart:接收表单和文件数据
@RequestBody:将请求体映射到控制器的参数中,仅限POST请求
@ResponseBody:将controller返回的对象转换为指定的格式之后,写入到response对象的body中,通常返回json格式
@RequestAttribute:获取由过滤器或者拦截器创建的、预先存在的请求属性


3、@Bean标注方法的对象参数
给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找,然后赋值再返回。

4、Lombok简化类的定义及日志
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Slf4j

@Builder

5、MVC架构模式
Model:处理应用程序中数据逻辑的部分,通常Model负责在数据库中存取数据。
View:应用程序中处理数据显示的部分,是用户看到并与之交互的界面。
Controller:应用程序中处理用户交互的部分,通常Controller负责从View中读取数据、控制用户输入,并向Model发送数据。

6、@Controller和@RestController的区别
@RestController = @Controller + @ResponseBody

7、POJO对象
普通JavaBean对象,区别于EJB(Enterprise Java Beans,分布式事务处理组件)

8、自定义Converter
在SpringMVC中添加一种新的Converter,以实现特定数据类型之间的转化。
WebMvcConfigurer定制化SpringMVC的功能

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

10、Servlet、Filter、Listener区别
(1)Servlet
接收请求数据
处理请求:通常会在Service、doPost或者doGet方法进行接收参数,并且调用业务层(service)的方法来处理请求。
完成响应。处理完请求后,一般会转发(forward)或者重定向(redirect)到某个页面,转发是HttpServletRequest中的方法,重定向是HttpServletResponse中的方
(2)Filter
与Servlet相似,但是Servlet主要负责处理请求,而filter主要负责拦截请求和放行
(3)Listener
监听器,监听Application、Session、Request对象,当这些对象发生变化就会调用对应的监听方法

11、JDBC、Druid、MyBatis区别
JDBC:Java Database Connectivity java和数据库的连接技术
Druid:数据库连接池,普通JDBC数据库连接使用DriverManager来获取会出现很多问题。为了解决创痛开发中数据库的连接问题,使用数据库连接池负责分配、管理和释放数据库连接。
Mybatis:进一步封装了jdbc,sql语句写在配置文件中,面向对象操作,有一二级缓存功能。

12、MyBatisPlus使用方法

(1)编写POJO对象,如果对象和表名不一致,则用@TableName注解一下
(2)编写Mapper接口继承BaseMapper<T>
(3)在main类中@MapperScan扫描mapper文件的目录
(4)使用时注入mapper组件即可

13、aop面向切面编程

通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。将非核心代码剥离出来从而实现解耦。

Aspect定义到aop类上,类中包含切点+通知两部分。

Pointcut:切点,连接点,定义在源码中哪里进行切入。

Advice:通知,定义增强到哪个切点,增强什么内容。

14、@Autowired和@Resource的异同

相同:都是用来装配组件

不同:前者spring的注解,后者java的注解。当IOC容器中有多个同类型组件时,使用后者指定名称注入。

15、Service + ServiceImpl搭配的编程模式

Service:是一个接口,只定义了与业务相关的操作。无@Service注解,不是组件
ServiceImpl:是Service的一个具体实现类,有@Service注解

如果service只有一个实现类,使用时直接装配Service的组件。
如果一个Service接口有多个实现类,则在实现类中通过@Service(name="xxx")进行注解,然后使用时通过@Resource(name="xxx")进行装配

16、@Value注解

获取属性文件中定义的属性值

17、@PostConstruct注解

被该注解修饰的方法会在项目启动的时候执行一次

18、@interface自定义注解

注解提供了类和方法的额外备注信息,通过反射可以获取这些信息。
注解和注释的区别是,注释只是给人看的,编译时会去掉,而注解在编译时可以被捕获。
如果某一个类进行了使用了自定义注解:
通过ApplicationContext的getBeansWithAnnotation方法可以获取所有带这些注解的示例
通过AnnotationUtils的findAnnotation方法可以获取注解的详细信息。

19、springboot设置定时任务

首先,在main主程序中添加注解@EnableScheduling
然后,在component组件中给定时任务方法添加注解@Scheduled(cron = "0 0/2 * * * ?")

-------------------------------------------------------------------------------------------------------

1、spring-boot简介

能够让我们快速集成spring解决方案和第三方库。

(1)快速创建独立Spring应用

之前SSM框架中,需要导包、写配置、启动运行,现在直接配置下就可以了。

(2)直接嵌入tomcat、jetty(无需部署war包)

之前需要一个linux环境,java环境、安装tomcat,打一个war包放到tomcat的webapps下。现在只要java环境,把程序打成jar包,直接java -jar即可运行。

(3)提供可选的starter(场景启动器),简化应用整合。

以前需要开发web、json、邮件服务、oss、异步、定时任务、缓存…等,需要导包一堆,并且需要控制好版本。现在springboot为每一种场景准备好了一个依赖,web-starter、mybatis-starter,只要配置这个依赖,所以事情都帮搞定。

(4)按需自动配置spring以及第三方库

如果这些场景需要使用,则这个场景的所有配置都会自动配置好。

约定大于配置:每个场景都有很多默认配置。如果需要自定义,则在配置文件中修改几项即可。

(5)提供生产级特性:如监控指标、健康检查、外部化配置等。

健康检查:如果发送一个请求检查应用是否活着,否则发送个请求重启。

外部化配置:比如发布了一个应用,通过java -jar demo.jar启动了。如果修改了配置则修改源码需要重启。后续外部化配置就可以在一个目录下放置配置文件,就不需要重启了。

(6)无代码生成、无xml

总结:简化开发、简化配置、简化整合、简化部署、简化监控、简化运维。

2、第一个springboot程序解读

(1)pom文件

<!-- 所有springboot项目都必须继承自 spring-boot-starter-parent -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>
<dependencies>
    <!-- web开发的场景启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

配置了一个springboot工程的parent父项,外加一个web的场景启动器。

(2)MainApplication

@SpringBootApplication // 这是一个SpringBoot应用
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

程序主入口,SpringBootApplication注解告诉框架这个一个springboot应用。

执行完上述两步之后就可以启动程序了,localhost:8080会显示一个404的页面。

(3)配置rest的controller

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handler01() {
        return "hello, springboot!";
    }
}

ResponseBody注解告诉框架这个请求返回的是一个响应式的body,而不是一个跳转之类的。如果把该注解放到类上,表明该类的所有方法都是返回body。

Controller注解+ResponseBody注解 = RestController注解。

(4)配置文件修改

在resources下新增application.properties(文件名固定不可修改),

新增:server.port=8888

(5)程序打包

在pom中添加:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

然后在IDEA右侧的Maven Projects中运行clean、package,就在target目录中得到jar包。然后通过java -jar <xx.jar>即可启动服务。

3、父项目做依赖管理

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>

每个springboot工程都有这么一个父项目,它的父项目的父项目中几乎声明了所有开发中常用的依赖的版本号。这样子项目<dependency>依赖这个父项目就不需要写版本号。

当父项目中提供的依赖不满意时,可以自定义依赖的版本号,操作为:

在parent同级下添加:

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

假如依赖的包没在parent的版本仲裁中,则需要自己写版本号。

4、自动配置功能

(1)自动配好tomcat:引入tomcat依赖、配置tomcat

(2)自动配好SpringMVC:引入SpringMVC全套组件、自动配好springVMC常用组件

(3)自动配好Web常见功能,如字符编码问题。

(4)默认包结构:主程序所在包及其下面的所有子包的组件都会被默认扫描进来,无需以前的包扫描配置。如果一定要加载不再主程序下面的组件,则需要在组程序中修改:@SpringBootApplication(scanBasePackage=”com.xxx”)或者@ComponentScan(“com.xx”)

(5)各种配置拥有默认值,这些配置最终都映射到MultipartProperties。配置文件的值最终会绑定到某个类上,这个类会在容器中创建对象。

(6)按需加载所有自动配置项。虽然有非常多的starter,但是引入了哪些场景这个场景的自动配置才会开启。Springboot所有的自动配置功能都在spring-boot-autoconfigure包里面。

5、@Configuration注解

创建完Bean后创建配置类。

@Configuration
public class MyConfig {

    @Bean
    public User user01() {
        return new User("zhangsan", 18);
    }

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

创建类就相当于创建了beans.xml文件,通过@Configuration就相当于告诉框架这个是配置类,等同于以前的配置文件。

@Bean:给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例。外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象。

配置类说明:

1)配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的。

2)配置类本身也是组件

3)proxyBeanMethods:代理@Bean的方法

        Full(proxyBeanMethods = true) // 检查容器调用单实例

         Lite(proxyBeanMethods = false) // 不检查容器重新生成实例,SpringBoot启动更快

4)最佳实战:

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

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

6、其他组件标注

@Component:代表它是一个组件

@Controller:代表它是一个控制器

@Service:代表它是一个业务逻辑组件

@Repository:代表它是一个数据库层组件

@ComponentScan:定义包扫描规则

7、@Import注解

位置:任何一个配置类或者组件里面。

@Import({User.class, DBHelper.class})

作用:给容器中自动创建这两个类型的组件,默认组件的名字为全类名(类型包含import时的包信息,比如com.hugh.boot.bean.User)

8、@Conditional注解

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

它是一个根类,包含ConditionalOnBean\ConditionalOnClass等子类

@Bean("user02")

@ConditionalOnBean(name="tom2")

 public User user02(){

      return new User("hugh", 20);

  }

上面只有当tom2的组件存在时user02的组件才会生效。

boolean user = run.containsBean("user02");

System.out.println("容器中user组件:" + user);

当tom2组件 不存在时,上面代码如果去掉@ConditionalOnBean(name="tom2")则显示true,否则显示false。

9、@ConfigurationProperties注解

把一些内容写到配置文件中,只需要通过该注解即可封装到JavaBean中,以供随时使用。

比如有一个Car的Bean如下:

public class Car {

    private String brand;

    private Integer price;

我们希望通过application.properties中添加配置信息从而生成JavaBean,配置文件内容为:

mycar.brand=BYD

mycar.price=100000

如果希望Car的Bean装载这些信息,Bean改造如下:

@Component

@ConfigurationProperties(prefix="mycar")

public class Car {

    private String brand;

    private Integer price;

此时一定要加上@Component的信息,因为只有在容器中的组件,才会拥有SpringBoot提供的强大功能。

在RestController中使用如下:

@Autowired
Car car;


@RequestMapping("/car")
public Car car(){
        return car;
}

此时body返回的信息为: {"brand":"BYD","price":100000}

还有一种方式是通过@EnableConfigurationProperties注解的方式。

Car种只需要配置@ConfigurationProperties(prefix="mycar"),不需要配置@Component,然后在Config类中配置@EnableConfigurationProperties(Car.class)来开启Car属性的配置功能。

该方法的好处是,好多源码中只配置了@ConfigurationProperties(prefix="mycar")注解,又不能修改源码增加@Component信息,就可以通过Config类来开启配置属性。

10、自动配置原理

主程序注解@SpringBootApplication主要由三部分组成:

(1)@SpringBootConfiguration

代表当前是一个配置类

(2)@ComponentScan

指定扫描哪些包

(3)@EnableAutoConfiguration

该注解实现时包含另一个注解:

@AutoConfigurationPackage,自动配置包(自己写的组件),它利用Registrar给容器导入一系列组件。将指定的一个包下的所有组件导入进来?MainApplication所在包下。

@Import({AutoConfigurationImportSelector.class}),给容器中批量导入一些依赖包的组件,默认扫描当前系统里面所有META-INF/spring.factories位置的文件。(如下两个例子)。

spring-boot-autoconfigure中有127项需要加载的包,文件里面写死了spring-boot一启动就要给容器中加载的所有配置类。

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

如上示例,通过@ConditionalOnClass()方法只有在aspectJ的Advice类有的时候AopAutoConfiguration才会生效。

11、自动配置的“修改默认配置”功能

@Bean
@ConditionalOnBean(MultipartResolver.class) // 当容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
// 当容器中没有这个名字(multipartResolver)组件 		
public MultipartResolver multipartResolver(MultipartResolver resolver){
        // Detect if the user has created a MultipartResolver but named it incorrectly 			
        return resolver;
}

上述代码给容器中加入了文件上传解析器。

给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找,然后赋值再返回。在SpringMVC中Bean名有严格的规定,通过上述操作可以防止有些用户配置的文件上传解析器不符合规范的问题。

12、第10-11部分总结

(1)SpringBoot先加载所有的自动配置类

(2)每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值

(3)生效的配置类就会给容器中装配很多组件

(4)只要容器中有这些组件,相当于这些功能就有了。

(5)只要用户有自己配置的,就以用户的优先。

(6)定制化配置的2种方法

用户直接自己@Bean替换底层的组件

用户去看这个组件是获取的配置文件什么值就去替换,取值步骤如下:

XxxAutoConfiguration à 组件 à xxxProperties里面拿值 à application.properties

以HTTP中处理Encode为例,对应源码:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
    private final Encoding properties;

    public HttpEncodingAutoConfiguration(ServerProperties properties) {
        this.properties = properties.getServlet().getEncoding();
    }

    @Bean
    @ConditionalOnMissingBean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
        return filter;
    }

第一种情况,如果@ConditionalOnMissingBean,即没有CharacterEncodingFilter组件,则通过characterEncodingFilter方法(默认的Bean组件名字)加载encode。方法中的this.properties来自于ServerProperties类,而这个类又通过EnableConfigurationProperties(ServerProperties.class)生效的,进一步同application.properties关联配置信息。

第二种情况,假如我们在自己的Config类中编写了如下代码,就可以替换掉默认的encode方法。

@Bean

public CharacterEncodingFilter filter(){

    return null;

}

13、SpringBoot最佳实践

(1)引入场景依赖

看下SpringBoot或者第三方有没有开发好的场景依赖

Spring Boot Reference Documentation

(2)查看自动配置了哪些(选做)

在application.properties中写:debug=true 开启自动配置报告

Negative:都是不生效的;

Positive:都是生效的

(3)是否需要修改

  • 参考文档修改配置项

Spring Boot Reference Documentation

或者自己分析。XxxProperties绑定了配置文件的哪些前缀

  • 自定义加入或者替换组件

@Bean @Component

  • 自定义器 xxxxCustomizer

14、Lombok工具简化开发

(1)pom中引入依赖

<dependency>

        <groupId>org.projectlombok</groupId>

        <artifactId>lombok</artifactId>

    </dependency>

(2)安装lombok插件

(3)通过lombok对原Bean的改造

@Data | @ToString | @NoArgsConstructor | @AllArgsConstructor

       这样Pet只剩private String name;一行代码了。

(4)增加日志功能

 增加@Slf4j (import lombok.extern.slf4j.Slf4j;)注解。

然后使用log.info(“xxx”); 显示注释信息。

(5)Builder模式的支持

1)Builder模式定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

2)Builder模式理解
比如需要构造一个机器会包含很多部件,但是根据相同的制造过程可以制造出不同的机器(机器猫、机器狗、机器人等)。再比如说相同的注册流程可以注册出不同的公司(百度、华为、小蜜等),不同的注册信息可以表示不同的公司。

为什么引入Builder模式?
比如你做个机器人需要用很多参数(手、脚、关节、眼睛、嘴巴、鼻子等)来构造,如果使用一般的构造方法需要写很多个。比如:
Student();
Student(String name);
Student(String name,int age);
Student(String name,int age,String address);
Student(String name,int age,String address,String id);

3)SpringBoot中的Builder示例

  • UserInfo的bean定义

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserInfo {
    private String username;
    private String password;
}

  • Test类中使用

UserInfo userInfo = new UserInfo().builder()
                .username("hugh")
                .password("123456")
                .build();
        System.out.println(userInfo.toString());

 15、Yaml使用

是Markup标记语言,非常适合用来做以数据为中心的配置文件

(1)基本语言

  • key:  value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • #表示注释
  • ‘’与”” 表示字符串内容,会被 不转义/转义
  • 注解方式装载JavaBean

如果通过配置文件装载可使用@ConfigurationProperties(prefix=”xxx”)的方式。

按优先级会读取application.properties和application.yaml文件加载信息

上述Bean对应的yaml文件可以表示为:

Spring配置东西,首选配置在yaml文件中。

16、配置文件-自定义类绑定的配置提示

目的:在写yaml文件时能够自动给出提示

使用时在pom.xml中配置:

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-configuration-processor</artifactId>

    <optional>true</optional>

</dependency>

然后运行一次主程序后,再在yaml中编写就会有提示词出来。

17、静态资源访问

只要静态资源放在类路径(main/resources)下:/static或/public或/resources或/META-INF/resources,通过当前项目路径/+静态资源名即可访问。

比如:文件放在/static下,通过http://localhost:8888/beauty.png即可访问。

原理:静态映射到/**,也就是拦截所有请求。请求进来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则404。

18、静态资源访问前缀

程序开发过程中需要设置一些拦截器,为了拦截方便需要区分开静态资源,所以静态资源访问需要带上前缀以便让拦截器放行。

默认无前缀,application.yaml加上前缀如下:

spring:

  mvc:

    static-path-pattern: /res/**

19、欢迎页支持

实现的两种方法:

1)在静态资源路径下放index.html,但是不能配置静态资源的访问前缀(static-path-pattern)

2)Controller能处理/index

访问:<ip>: <port>即可访问index.html页面。

20、静态资源加载原理中的关键信息

源码位于:spring-boot-autoconfigure.jar下的org.springframework.boot.autoconfigure.web中的WebMvcAutoConfiguration类中。

@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })  // 和配置相关属性进行了绑定WebMvcProperties(spring.mvc)| ResourceProperties(spring.resources)
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
    public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
                                          ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                                          ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                                          ObjectProvider<DispatcherServletPath> dispatcherServletPath,
                                          ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {

    }
}

当@Configure配置类只有一个有参构造器,有参构造器所有参数的值都会从容器中确定

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
        String staticPathPattern = this.mvcProperties.getStaticPathPattern();  // 指为/**,即拦截所有请求
        if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
        .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
        .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }
        staticLocations对应:"classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/"

21、请求映射原理

所有的请求都由DispatcherServlet来处理。

继承关系如下:

HttpServlet的doGet和doPost都需要子类重写。

在FrameworkServlet中有重写的doGet方法,调用processRequest(request, response)方法,该方法的核心是doService(request, response),在FrameworkServlet中一个抽象类。

接着查看DispatcherServlet的doService方法,核心方法是:doDispatch(request, response)。

所以SpringMVC功能分析都从DispatcherServlet的doDispatch开始。

mappedHandler = getHandler(processedRequest); // 找到当前请求读由哪个Handler(Controller的方法)处理。

// HandlerMappings,处理器映射,/*** -> xxx

每个mapping保存了路径和handler的处理关系,如下图所示:

总结:

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

  • SpringBoot自动配置欢迎页的WelcomePageHandlerMapping,访问/能访问到index.html
  • SpringBoot自动配置了默认的RequestMappingHandlerMapping
  • 请求进来,换个尝试所有的HandlerMapping看是否有请求信息。

如果有就找这个请求对应的Handler

如果没有就是下一个HandlerMapping

  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping,自定义HandlerMapping。

22、MVC架构模式

MVC全程:Model(模型) View(视图) Controller(控制器)

Model:处理应用程序中数据逻辑的部分,通常Model负责在数据库中存取数据。

View:应用程序中处理数据显示的部分,是用户看到并与之交互的界面。

Controller:应用程序中处理用户交互的部分,通常Controller负责从View中读取数据、控制用户输入,并向Model发送数据。

23、请求处理中常用参数注解使用

(1)@GetMapping

将HTTP GET请求映射到指定的处理方法上。

示例:@GetMapping("/car/{id}/owner/{username}")

(2)@PostMapping

将HTTP POST请求映射到指定的处理方法上。

示例:

@PostMapping("/users")

    public User createUser(@RequestBody User user) {

        // 创建用户

        return user;

    }

(3)@PathVariable

作用:将URL绑定的占位符映射到控制器的参数中。URL中{xxx}占位符可以通过@Pathvariable(“XXX”)进行绑定,@Pathvariable可以将全部参数放入Map<String, String>结构的对象中。

使用示例:

@GetMapping("/car/{id}/owner/{username}")

    public Map<String, Object> getCar(@PathVariable("id") Integer id,

                                   @PathVariable("username") String username,

                                   @PathVariable Map<String, String> pv

    ){

        Map<String, Object> map = new HashMap<>();

        map.put("id", id);

        map.put("username", username);

        map.put("pv", pv);

        return map;

    }

请求链接:http://localhost:8888/car/123/owner/hugh

返回:{"pv":{"id":"123","username":"hugh"},"id":123,"username":"hugh"}

(4)@RequestHeader

作用:将请求头中的参数映射到控制器的参数中。

使用示例:

public Map<String, Object> getCar(@RequestHeader("user-agent") String agent,

                               @RequestHeader Map<String, String> headers

浏览器中请求头查看方法:

(5)@RequestParam

作用:将请求参数映射到控制器的参数中。

使用示例:

@GetMapping("/person")

    public Map<String, Object> getCar(@RequestParam("age") Integer age,

                                   @RequestParam("inters") List<String> inters,

                                   @RequestParam Map<String, String> params

请求头:http://localhost:8888/person?age=18&inters=basketbal&inters=baseball

(6)@RequestBody

作用:将请求体映射到控制器的参数中,仅限POST请求

使用示例:

@PostMapping("/hello/post")

    public Map<String, Object> getHello(@RequestBody String content){

(7)@ResponseBody

将controller返回的对象转换为指定的格式之后,写入到response对象的body中,通常返回json格式。

(8)@Controller和@RestController的区别

@Controller+@GetMapping返回一个视图,

而@RestController+@GetMapping返回的是json数据

@RestController = @Controller + @ResponseBody

然后localhost:8080/list返回了list.html页面。

然后localhost:8080/list返回json数据。

(9)@RequestAttribute

作用:获取由过滤器或者拦截器创建的、预先存在的请求属性

示例:

   
    @GetMapping("/goto")

   

    public String goToPage(HttpServletRequest request) {

       request.setAttribute("msg", "成功了...");

       request.setAttribute("code", 200);

       return "forward:/success"; //转发到 /sucess请求

   }



   
    @ResponseBody

   
    @GetMapping("/success")

   

    public Map<String, Object> success(@RequestAttribute("msg") String msg,

                                   @RequestAttribute("code") Integer code) {

       Map<String, Object> map = new HashMap<>();

        map.put("msg", msg);

       map.put("code", code);

       return map;

   }

}

在goToPage()函数完成后,所有的请求数据和视图都会放入ModelAndViewContainer中,包含要去的页面地址view,还包含Model数据。接下来处理派发结果,processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)。上面的mv就是ModelAndViewContainer的内容。

exposeModelAsRequestAttributes(model, request);

暴露模型作为请求属性,把model中的所有数据遍历挨个放在请求域中。

24、自定义POJO对象数据绑定

当表单提交字段比较多时,使用POJO对象(普通JavaBean对象,区别于EJB(Enterprise Java Beans,分布式事务处理组件))保存变量。

我们解析字段通常使用@PathVariable注解或者@RequestParam注解,但是当字段过多时,我们不可能每个字段都给一个对应的参数给函数,此时SpringMVC给我们提供了POJO对象,用于将参数包装成一个对象。

示例:

POJO对象定义:

@Data

public class Student {

    private String userName;

    private Integer age;

    private Date birth;

}

Html中表单提交:

<form action="/savestudent" method="post">

    姓名:<input name="userName" value="zhangsan"><br/>

    年龄:<input name="age" value="18"><br/>

    生日:<input name="birth" value="2019/12/20"><br/>

    <input type="submit" value="保存">

</form>

Controller中获取用户数据:

@PostMapping("/savestudent")

   public Student savestudent(Student student){

      return student;

}

在上述方法中,Spring框架调用get与set函数封装成对象。

POJO封装过程原理说明:

ServletModelAttributeMethodProcessor,这个参数处理器支持

是否为简单类型?POJO并非简单类型。

然后创建一个空的Student对象。

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

WebDataBinder为Web数据绑定器,它的作用是把请求参数webRequest绑定到指定的JavaBean(attribute)里面。

WebDataBinder利用它里面的Converters将请求数据(String类型)转成指定的数据类型(JavaBean对应字段的类型),从而实现封装到JavaBean中。

GenericConversionService:在设置每一个值的时候,找它里面的所有converter哪个可以将这个数据类型(request带来参数的字符串)转换到指定的类型。

未来我们可以给WebDataBinder里面放自己的Converter;

Private static final class StringToNumber<T extends Number> implements Converter<String T>

25、自定义Converter

目的:在SpringMVC中添加一种新的Converter,以实现特定数据类型之间的转化。

示例:

POJO定义:

@Data

public class Student {

    private String userName;

    private Integer age;

    private Date birth;

    private Pet pet;

}

Form表格中pet的两个字段通过逗号隔开:

<form action="/savestudent" method="post">

    姓名:<input name="userName" value="zhangsan"><br/>

    年龄:<input name="age" value="18"><br/>

    生日:<input name="birth" value="2019/12/20"><br/>

    宠物:<input name="pet" value="猫,2"><br/>

    <input type="submit" value="保存">

</form>

WebMvcConfigurer定制化SpringMVC的功能

    @Bean

    public WebMvcConfigurer webMvcConfigurer(){

        return new WebMvcConfigurer() {

            // 参数解析中的自定义Converter

            @Override

            public void addFormatters(FormatterRegistry registry) {

                registry.addConverter(new Converter<String, Pet>() {

                    @Override

                    public Pet convert(String source) {

                        if(!StringUtils.isEmpty(source)){

                            Pet pet = new Pet();

                            String[] split = source.split(",");

                            pet.setName(split[0]);

                            pet.setAge(Integer.parseInt(split[1]));

                            return pet;

                        }

                        return null;

                    }

                });

            }

        };

    }

其中addFormatters函数的说明见注释,可以提供add converter功能。

/**

        * Add {@link Converter Converters} and {@link Formatter Formatters} in addition to the ones

        * registered by default.

        */

       default void addFormatters(FormatterRegistry registry) {

       }

26、ReturnValueHandler原理剖析

由第21个知识点可知,SpringMVC功能分析都从DispatcherServlet的doDispatch开始。

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

继而:

handleInternal(request, response, (HandlerMethod) handler)

继而:

mav = invokeHandlerMethod(request, response, handlerMethod)

继而:

invocableMethod.invokeAndHandle(webRequest, mavContainer)

给前端自动返回json数据:

1)返回值解析器

2)返回值处理器处理返回值

this.returnValueHandlers.handleReturnValue(

        returnValue, getReturnValueType(returnValue), mavContainer, webRequest)

继而:

####1)返回值处理器判断是否支持这种类型返回值supportsReturnType

####2)返回值处理器调用handleReturnValue的selectHandler进行处理

####3)选中了RequestResponseBodyMethodProcessor可以处理返回值标了@ResponseBody注解的。

  writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage

使用消息转化器进行写出操作。

  利用MessageConverters进行处理,将数据写为json的过程如下:

1)内容协商(浏览器默认会以请求头的方式告诉服务器它能接受什么样的内容类型)

*/*表示能接受任何类型的数据

Accept表示我能接受什么内容。

2)服务器最终根据自身的能力,决定服务器能生产什么样内容类型的数据

3)SpringMVC会挨个遍历所有容器底层的HttpMessageConverter,看谁能处理?

HttpMessageConverter:看是否支持将此Class类型的对象,转为MediaType类型的数据。例如:Person对象转为JSON,或者JSON转为Person。

最终MappingJackson2HttpMessageConverter把对象转为JSON

总结:@ResponseBody,利用返回值处理器里面的消息转换器,将对象默认转换为json传出去(如果有指定则可以是其他格式)。

27、内容协商原理

根据客户端接收能力不同,返回不同媒体类型的数据。

先说示例:

1)pom中引入xml依赖

<dependency>

    <groupId>com.fasterxml.jackson.dataformat</groupId>

    <artifactId>jackson-dataformat-xml</artifactId>

</dependency>

2)controller信息

@GetMapping("/test/pet")

@ResponseBody

public Pet getPet(){

   Pet pet = new Pet();

   pet.setName("猫");

   pet.setAge(3);

   return pet;

}

3)浏览器显示

4)Postman显示

两者的主要区别在于:

浏览器中Accept为:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7

其中xml的优先级(q值)高于*/*,所以返回了xml格式的数据。

而postman中Accept为:*/*,所以返回了json格式的数据。

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

再说原理:

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

2)获取客户端(PostMan、浏览器)支持接收的内容类型。List<MediaType> acceptableTypes = getAcceptableMediaTypes(request),获取客户端Accept请求头。【application/xml】

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

4)找到支持操作Pet的Converter(canRead、canWrite),把Converter支持的媒体类型统计出来。

canRead的含义:通过@RequestBody 传过来Pet类型,支持这个类型数据的读取再写为json类型等。

canWrite的含义:内部的Pet类型的数据,转化为json类型后再传出去。

5)客户端需要【application/xml】,服务端能力【10种、json+xml】

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

7)用支持将对象转为最佳匹配类型的converter,调用它进行转换

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

浏览器中不方便改请求头Accept信息,为了方便内容协商,开启基于请求参数的内容协商功能。

示例:

在第27个知识点中pom配置xml包后浏览器返回的也是xml。

1)在application.yaml中添加:

spring:

  mvc:

    contentnegotiation:

      favor-parameter: true

2)点击favor-parameter打开源码,找到注释说明

3)在请求中通过添加format参数即可控制内容协商

原理说明:

在获取请求媒体类型的策略中,之前只有HeaderContentNegotiationStrategy,通过配置favor-parameter: true之后,新增了一个策略:ParameterContentNegotiationStrategy。

确定客户端接收什么样的内容类型:Parameter策略优先确定是要返回json数据(获取请求参数中的format的值)

29、自定义MessageConverter

需求:如果硅谷app发请求,返回自定义协商数据 【application/x-guigu】 xxxConverter

流程:

1)添加自定义的MessageConverter进系统底层

2)系统底层就会统计出所有MessageConverter能操作哪些类型

3)客户端内容协商(guigu à guigu)

示例:

一个入口给容器中添加一个WebMvcConfigurer,重写extendMessageConverters接口。

1)新增一个Converter

2)在WebMvcConfigurer中extendMessageConverters

// 数据返回时的自定义Converter

@Override

public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

    converters.add(new GuiguMessageConverter());

}

3)Postman中发送Get请求后(Accept=application/x-guigu)

30、自定义内容协商中的参数策略中的类型

需求:支持如下请求http://localhost:8888/test/pet?format=gg

当前有两个内容协商策略器,其中ParameterContentNegotiationStrategy只支持xml和json的默认配置,现在需要新增对gg的支持。

示例:

在WebMvcConfigurer中配置内容协商的策略配置器,其为覆盖而非新增模式

@Override

public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

// 配置ParameterContentNegotiationStrategy

Map<String, MediaType> mediaTypes = new HashMap<>();

mediaTypes.put("json", MediaType.APPLICATION_JSON);

mediaTypes.put("xml", MediaType.APPLICATION_XML);

mediaTypes.put("gg", MediaType.parseMediaType("application/x-guigu"));

// 指定支持解析哪些参数对应的哪些媒体类型

ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);

// 配置HeaderContentNegotiationStrategy

HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();

configurer.strategies(Arrays.asList(parameterStrategy, headerStrategy));

}

31、 视图解析原理流程

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

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

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

4)ProcessDispatchResult 处理派发结果(页面该如何响应)

4.1)render(mv, request, response)进行页面渲染

      4.1.1)根据方法的String返回值得到View对象【定义了页面的渲染逻辑】

                 4.1.1.1)所有的视图解析器尝试是否能根据当前返回值得到view对象

               

                 4.1.1.2)得到了redirect/main.html -> Thymeleaf new RedirectView()

                 4.1.1.3) ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象

                 4.1.1.4)view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作

+++++RedirectView 如何渲染:【重定向到一个页面】

  1. 获取目标url地址
  2. Response.sendRedirect(encodedURL);

+++++视图解析:

  1. 返回值以forward:开始:new InternalResourceView(forwardUrl);  -》 request.getRequestDispatcher(path).forward(request, response);
  2. 返回值以redirect:开始:new RedirectView();  à render重定向
  3. 返回值是普通字符串: new ThymeleafView()

32、HandlerInterceptor拦截器

三个拦截点的示意图如下:

步骤:

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

       示例:

编写拦截器类

public class LoginInterceptor implements HandlerInterceptor {

    /**

     * 目标方法执行完成以前

     * @param request

     * @param response

     * @param handler

     * @return

     */

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        // 登录检查

        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){

            // 放行

            return true;

        }

        // 拦截住

        request.setAttribute("msg", "请先登录");

        try {

            request.getRequestDispatcher("/").forward(request, response);

        } catch (ServletException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

        return false;

    }

在WebMvcConfigurer中注册一个拦截器:

    // 配置拦截器

@Override

public void addInterceptors(InterceptorRegistry registry) {

       registry.addInterceptor(new LoginInterceptor())

                     .addPathPatterns("/**")   //所有请求都被拦截,包括静态资源

                     .excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/js/**");  // 放行的请求

}

 33、拦截器的原理

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

2)先来顺序执行所有拦截器的preHandler方法

     如果当前拦截器prehandler返回为true,则执行下一个拦截器的preHandler

     如果当前拦截器返回为false,倒序执行所有已经执行了的拦截器的afterCompletion方法。

3)如果任何一个拦截器false,直接跳出不执行目标方法

4)所有拦截器都返回true,执行目标方法

5)倒序执行所有拦截器的postHandler方法

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

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

34、文件上传实现

流程:

  1. HTML的form中增加上传功能
  2. Controller类中处理上传请求
  3. Yaml文件中增加文件相关的配置

示例:

1)HTML编写:

2)Controller类处理上传请求:

3)yaml配置:

35、文件上传原理

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

自动配置好了 StandardServletMultipartResolver【文件上传解析器】

原理步骤:

1)请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart, 返回MultipartHttpServletRequest)文件上传请求

2)参数解析器来解析请求中的文件内容封装成MultipartFile

3)将request中文件信息封装为一个Map,MultiValueMap<String, MultipartFile>

FileCopyUtils实现文件流的拷贝

36、异常处理

默认规则:

1)默认情况下,SpringBoot提供/error处理所有错误的映射

2)对于机器客户端,它将生成JSON响应,其中包含错误、HTTP状态和异常消息的详细信息;对于浏览器客户端,响应一个“whitelabel”错误视图,以HTML格式呈现相同的数据 

异常处理自动配置原理 

ErrorMvcAutoConfiguration 自动配置异常处理规则

1)容器中的组件:类型:DefaultErrorAttributes  à id: errorAttributes

2)容器中的组件:类型:BasicErrorController –》id:basicErrorController

处理默认/error路径的请求:页面响应new ModelAndView(“error”, model);

容器中有组件View -> id是error

容器中放组件BeanNameViewResolver(视图解析器):按照返回的视图名作为组件的id去容器中找View对象。

3)容器中的组件:类型DefaultErrorViewResolver -> id: conventionErrorViewResolver

如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面

error/ViewName.html   404.html 5xx.html


37、JavaWeb三大组件(Servlet、Filter、Listener)

(1)Servlet:

作用:处理客户端请求的动态资源。

任务:

  1. 接收请求数据
  2. 处理请求:通常会在Service、doPost或者doGet方法进行接收参数,并且调用业务层(service)的方法来处理请求。
  3. 完成响应。处理完请求后,一般会转发(forward)或者重定向(redirect)到某个页面,转发是HttpServletRequest中的方法,重定向是HttpServletResponse中的方法。

生命周期:

  1. servlet的初始化方法,Servlet是单例的,整个服务器就只能创建一个同类型Servlet
  2. servlet的处理请求方法:每处理一次请求,就会被调用一次
  3. servlet销毁之前执行的方法:只执行一次

(2)Filter

       与Servlet相似,但是Servlet主要负责处理请求,而filter主要负责拦截请求和放行

(3)Listener

监听器,监听Application、Session、Request对象,当这些对象发生变化就会调用对应的监听方法。

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

有两种方法,使用Servlet API和使用RegistrationBean。

在使用Servlet API中:

创建MyServlet类:

@WebServlet(urlPatterns = "/my")

public class MyServlet extends HttpServlet {

    @Override

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.getWriter().write("6666");

    }

}

在main中将servlet扫描进来

@SpringBootApplication // 这是一个SpringBoot应用

@ServletComponentScan(basePackages = "com.hugh.boot")  // 将servlet扫描进来

public class MainApplication {

使用总结:

@ServletComponentScan(basePackages = "com.hugh.boot"):指定原生Servlet组件都放在哪里

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

创建MyFilter类

创建MyServletContextListener类

@Slf4j

@WebListener

public class MyServletContextListener implements ServletContextListener {

    @Override

    public void contextInitialized(ServletContextEvent sce) {

        log.info("MyServletContextListener监听项目初始化完成");

    }

}

在使用RegistrationBean中:

@Configuration(proxyBeanMethods = true) // 单例模式:保证依赖的组件始终是单例的

public class MyRegisterConfig {

    @Bean

    public ServletRegistrationBean myServlets(){

        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet, "/my");

    }

    @Bean

    public FilterRegistrationBean myFilter(){

        MyFilter myFilter = new MyFilter();

        return new FilterRegistrationBean(myFilter, myServlets());

    }

}

39、 DispatchServlet注入原理

1)容器中自动配置了DispatcherServlet,属性绑定到WebMvcProperties;对应的配置文件配置项是spring.mvc

2)通过ServletRegistrationBean<DispatcherServlet>把Dispatcher配置进来

3)默认映射的是 / 路径

Tomcat-Servlet:

多个Servlet都能处理同一层路径,精确优先原则

40、嵌入式Servlet容器

原理:

1)SpringBoot容器启动发现当前是web应用。Web场景-导入tomcat

2)Web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext

3)ServletWebServerApplicationContext启动的时候寻找 ServletWebServerFactory(Servlet的web服务器工厂-à servlet的web服务器)

4)SpringBoot底层默认有很多的WebServer工厂

5)底层直接会有一个自动配置类,ServletWebServerFactoryAutoConfiguration

6)ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)

7)ServletWebServerFactoryConfiguration配置类,根据动态判断系统中到底导入了哪个Web服务器的包,(默认是web-starter导入tomcat包),容器中就有TomcatServletWebServerFactory

8)TomcatServletWebServerFactory创建出Tomat服务器并启动

9)内部服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

41、SpringBoot定制化组件

默认情况下,springboot是运行良好的,这得益于它默认的良好配置,原理如下:

场景starter -> xxxxAutoConfiguration -> 通过@Bean导入xxxx组件 -> 绑定xxxProperties -> 绑定配置文件项

 定制化的常见方式

1)修改配置文件

2)XxxxCustomizer

3)编写自定义的配置类xxxConfiguration+@Bean替换,增加容器中默认组件;视图解析器

4)Web应用实现WebMvcConfigurer即可定制化web功能

5)@EnableWebMvc+WebMvcConfigurer ----@Bean可以全面接管SpringMvc,所有规则全部自己重新配置,实现定制和扩展功能

对于5)的原理:

  • WebMvcAutoConfiguration默认的SpringMVC的自动配置功能类。静态资源、欢迎页….
  • 一旦使用@EnableWebMvc,会@Import(DelegatingWebMvcConfiguration.class)
  • DelegatingWebMvcConfiguration的作用,只保证SpringMVC最基本的使用

把所有系统中的WebMvcConfigurer拿过来,所有功能的定制都是这些WebMvcConfigurer合起来一起生效

自动配置了一些非常底层的组件。RequestMappingHandlerMapping,这些组件依赖的组件都是从容器中获取

Public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSuppor

  • WebMvcAutoConfiguration里面的配置要能生效必须

@ConfitionalOnMissingBean(WebMvcConfigurationSupport.class)

@EnableWebMvc 导致了WebMvcAutoConfiguration没有生效

42、数据库访问

1)导入JDBC场景

  • <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-jdbc</artifactId>

    </dependency>

    数据库驱动?

    为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下来要操作什么数据库。

    <dependency>

    <groupId>mysql</groupId>

    <artifactId>mysql-connector-java</artifactId>

    </dependency>

    想要修改版本:

  • 直接依赖引入具体版本(maven的就近依赖原则)
  • 重新声明版本,增加properties配置项。(maven的属性的就近优先原则分析)2

2)分析自动配置

自动配置的类

  • DataSourceAutoConfiguration:数据源的自动配置

       修改数据源相关的配置:spring.datasource

       数据库连接池的配置,是自己容器中没有DataSource才自动配置

       底层配置好的连接池是:HikariDataSource

  • DataSourceTransactionManagerAutoConfiguration:事务管理器的配置
  • JdbcTemplateAutoConfiguration:JdbcTemplate的自动配置,可以对数据库进行crud

可以修改这个配置项@ConfigurationProperties(prefix=”spring.jdbc”)来修改jdbcTemplate

@Bean@Primary JdbcTemplate:容器中有这个组件

3)数据库的yaml配置

datasource:

    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC

    username: root

    password: root

driver-class-name: com.mysql.jdbc.Driver

4)示例

Pom.xml添加:

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

<scope>test</scope>

</dependency>

Java代码:

@RunWith(SpringJUnit4ClassRunner.class)

@SpringBootTest

public class JDBCTest {

    @Autowired

    JdbcTemplate jdbcTemplate;

    @Test

    public void testQuery(){

        Long aLong = jdbcTemplate.queryForObject("select count(1) as cnt from account;", Long.class);

        System.out.println("记录总数:" + aLong);

    }

}

43、自定义方式整合Druid数据源

它包含了数据源的全套解决方案,包括防止SQL的注入攻击

Druid官方地址:GitHub - alibaba/druid: 阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池

1)pom中增加druid信息

2)构造配置类,给容器中注入DataSource的组件

@Configuration

public class MyDataSourceConfig {

    // 默认的自动配置是判断容器中没有自会配@ConfitionalOnMissingBean(DataSource.class)

    @ConfigurationProperties("spring.datasource")

    @Bean

    public DataSource dataSource() throws SQLException {

        DruidDataSource druidDataSource = new DruidDataSource();

        // 加入监控功能

        druidDataSource.setFilters("stat");

        return druidDataSource;

}

3)查看dataSource的类型

4)配置Druid监控

// 给容器中放入servlet

/**

 * 配置druid的监控页面

 * @return

 */

@Bean

public ServletRegistrationBean StatViewServlet(){

       StatViewServlet statViewServlet = new StatViewServlet();

       ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");

       return registrationBean;

}

44、Starter方式整合Druid

1)引入druid-starter

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>druid-spring-boot-starter</artifactId>

<version>1.1.17</version>

</dependency>

2)分析自动配置

  1. 扩展配置项:spring.datasource.druid
  2. DruidSpringAopConfiguration.class,:监控spring组件
  3. DruidStatViewServletConfiguration.class,:监控页的配置
  4. DruidWebStatFilterConfiguration.class, web监控配置
  5. DruidFilterConfiguration.class:所有druid自己filter配置

所有功能都可操作yaml文件进行配置

spring->datasource:

druid:

      filter: stat,wall

      stat-view-servlet:

        enabled: true

        login-username: root

        login-password: root

      web-stat-filter:

        enabled: true

        url-pattern: /*

        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'

45、JDBC、Druid、MyBatis区别

JDBC:Java Database Connectivity java和数据库的连接技术

Druid:数据库连接池,普通JDBC数据库连接使用DriverManager来获取会出现很多问题。为了解决创痛开发中数据库的连接问题,使用数据库连接池负责分配、管理和释放数据库连接。

Mybatis:进一步封装了jdbc,sql语句写在配置文件中,面向对象操作,有一二级缓存功能。

46、配置Mybatis

1)引入mybatis包

<dependency>

<groupId>org.mybatis.spring.boot</groupId>

<artifactId>mybatis-spring-boot-starter</artifactId>

<version>2.1.0</version>

</dependency>

2)配置模式

可以修改配置文件中mybatis开始的所有

SqlSessionFactory:自动配置好了

SqlSession:自动配置了SqlSessionTemplate组合了SqlSession

Mapper:只要我们写的操作MyBatis的接口标注了@Mapper,就会被自动扫描进来

实操流程

MyBatis对应文档:mybatis – MyBatis 3 | 简介

  1. 导入mybats官方starter
  2. 编写mapper接口。标注@Mapper注释
  3. 编写sql映射文件并绑定mapper接口
  4. 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息(建议:配置在mybatis.configuration)

实操示例:(任务:获取mysql中的数据)

1)查询mysql中的数据

2)构造数据对应的Account类(Account.java)

@Data

@ToString

@NoArgsConstructor

@AllArgsConstructor

public class Account {

    private String name;

    private String phone;

}

3)创建mapper接口(AccountMapper.java)

@Mapper

public interface AccountMapper {

    public Account getAcct(String name);

}

4)编写sql映射文件(AccountMapper.xml)

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper

        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.hugh.boot.mapper.AccountMapper">

    <select id="getAcct" resultType="com.hugh.boot.bean.Account">

        select * from account where name = #{name}

    </select>

</mapper>

5)yaml中增加mybatis规则(xxxmapper.xml文件读取)

# 配置mybatis规则

mybatis:

  mapper-locations: classpath: mybatis/mapper/*.xml

6)构造Service类,读取mapper中接口的数据(AccountService.java)

@Service

public class AccountService {

    @Autowired

    AccountMapper accountMapper;

    public Account getAcctByName(String name){

        return accountMapper.getAcct(name);

    }

}

7)在Controller中使用service类获取数据并返回请求结果

@Autowired

AccountService accountService;

@ResponseBody

@GetMapping("/acct")

public Account getByName(@RequestParam("name") String name){

       return accountService.getAcctByName(name);

}

       假如不希望写第4)步的mapper文件,则可以直接在mapper接口上进行注解:

其他步骤保持不变。

混合模式:注解模式和配置文件模式可以共存。

       假如mapper目录下每个类都标记@Mapper太麻烦,则可以直接在主程序中通过@MapperScan(“map的目录”)进行自动装载,这样每个mapper接口就不用@Mapper进行标注了。

最佳实战:

  1. 引入mybatis-starter
  2. 配置application.yaml,指定mapper-location位置即可
  3. 编写Mapper接口并标注@Mapper注解
  4. 简单方法直接注解方式
  5. 复杂方法编写@mapper.xml进行绑定映射
  6. @MapperScan(“xxx”)简化,其他的接口就可以不用标注@Mapper注解

47、整合MyBatisPlus

MyBatis-Plus是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发,提供效率而生。

1)pom中引入依赖

<dependency>

       <groupId>com.baomidou</groupId>

       <artifactId>mybatis-plus-boot-starter</artifactId>

       <version>3.5.1</version>

</dependency>

2)自动配置

  1. MybatisPlusAutoConfiguration配置类,MybatisPlusProperties配置项绑定,mybatis-plus: xxx就是对mybatis-plus的定制
  2. SqlSessionFactory自动配置好。底层是容器中默认的数据源。(druid)
  3. MapperLocations自动配置好,有默认值。Classpath*:/mapper/**/*.xml:任意包的类路径下所有mapper文件夹下任意路径下的所有xml都是sql映射文件。建议以后sql映射文件,放在mapper下
  4. 容器中也自动配置好了SqlSessionTemplate
  5. @Mapper标注的接口也会被自动扫描,建议@MapperScan(“xxx”)批量扫描就行

优点:

只需要我们的Mapper继承BaseMapper就可以拥有crud能力

实战:

1)在mysql中创建数据

2)编写数据库对应的Bean类

@Data

@ToString

@AllArgsConstructor

@NoArgsConstructor

@TableName(“user”)  // 当表名与类名不一致时可显示指定表名

public class User {

    /**

     * 使用mybatis-plus时所有属性都应在在数据库中

     */

    @TableField(exist=false)    // 数据库中无该字段

    private String userName;

    @TableField(exist=false)

    private String password;

    private Long id;

    private String name;

    private Integer age;

    private String email;

}

3)编写Mapper接口(mybatis-plus的精华部分)

public interface UserMapper extends BaseMapper<User> {

}

只需要我们的Mapper继承BaseMapper就可以拥有crud能力

4)在main类中@MapperScan扫描mapper文件的目录

@MapperScan({"com.hugh.boot.mapper","com.hugh.boot.dao.mapper"})  // 将mapper扫描进来

public class MainApplication {

5)单元测试中调用mapper接口实现查询

@Autowired

UserMapper userMapper;

@Test

public void testMapper(){

       User user = userMapper.selectById(1L);

       System.out.println("用户信息:" + user);

}

MybatisPlus使用方法总结
(1)编写POJO对象,如果对象和表名不一致,则用@TableName注解一下
(2)编写Mapper接口继承BaseMapper<T>
(3)在main类中@MapperScan扫描mapper文件的目录
(4)使用时注入mapper组件即可

 

48、通过Mybatis接口快速实现CRUD

关键点:

  1. 继承IService<T>构造Service层
  2. 继承ServiceImpl<xxxxMapper, T>实现IService<T>的默认方法
  3. 注入service即可直接使用

1)UserService接口类

public interface UserService extends IService<User> {

}

继承MyBatis中的IService,它是所有Service层的总接口,需要写一个泛型

2)UserServiceImpl实现类

@Service

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

}

MyBatis又提供了一个对顶级Service的实现类,ServiceImpl中提供了非常多的增删改查的方法。

getxxx:查询的

list:查询的

page:分页的

remove:删除的

save:保存的

update:更新的

后续只要注入Service,就可以直接使用这些方法

3)使用

@Autowired

UserService userService;

@ResponseBody

@GetMapping("/test/query")

public List<User> queryTable(@RequestParam(value="pn", defaultValue = "1") Integer pn){

// 分页查询数据

Page<User> userPage = new Page<>(pn, 2);

// 分页查询结果

Page<User> page = userService.page(userPage, null);

log.info("page:{ }", page);

// 查询数据库中所有记录

List<User> list = userService.list();

return list;

}

49、Redis使用

安装参考:Windows 安装 Redis_redis windows_羽之大公公的博客-CSDN博客

Redis自动配置

1)pom中引入依赖

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

2)依赖包分析

3)自动配置

  • RedisAutoConfiguration自动配置类,RedisProperties属性类-> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object>:xxxTemplate;
  • 自动注入了StringRedisTemplate
  • Key: value。
  • 底层只要我们使用StringRedisTemplate、RedisTemplate就可以操作redis

4)使用示例

Yaml中进行配置:

redis:

  url: redis://localhost:6379

测试类中编写代码:

@Autowired

StringRedisTemplate stringRedisTemplate;

@Test

public void testRedis(){

ValueOperations operations = redisTemplate.opsForValue();

operations.set("hello", "world");

String hello = (String) operations.get("hello");

System.out.println("redis返回:" + hello);

}

 50、Filter和Interceptor几乎拥有相同的功能,区别

Filter是Servlet定义的原生组件,好处,脱离Spring应用也能使用

Interceptor是Spring定义的接口,可以使用Spring的自动装配功能

51、测试类(Junit4)

1)引入包

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

2)编写测试类代码

@RunWith(SpringJUnit4ClassRunner.class)

@SpringBootTest

public class JDBCTest {

3)SpringBoot整合Junit以后

编写测试方法:@Test标注

Junit类具有Spring的功能,@Autowired,比如@Transactional标注测试方法,测试完成后自动回滚

52、指标监控

SpringBoot Actuator

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

1)pom引入依赖包

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

2)访问http://localhost:8080/actuator/

默认情况下可以访问health和info的endpoint,其他都以jmx方式暴露,http方式没有暴露

3)yaml中配置暴露所有的http方式

# management 是所有actuator的配置

management:

  endpoints:

    enabled-by-default: true  # 默认开启所有监控端点

    web:

      exposure:

        include: '*'  # 以web方式暴露所有端点

53、maven多模块构建项目

通过modules来管理同个项目中的各个模块,如果maven用的比较简单,或者说项目的模块在pom.xml没进行划分,那么此元素是用不到的;不过一般大一点的项目是要用到的。

需求场景:如果我们的项目分成了好几个模块,那么我们构建的时候是不是有几个模块就需要构建几次了,到每个模块的目录下执行mvn命令。当然,逐个构建是没有问题的,但是非要这么麻烦的一个个的构建吗?那么简单的做法就是使用聚合,一次构建全部模块

方法是:先创建一个普通的maven工程,然后再项目右击创建子模块,如下:

此时在外层pom中就会有modules信息:

此时构建最外层项目,所有的子模块都会构建。

54、aop面向切面编程

aop:面向切面编程,通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。将非核心代码剥离出来从而实现解耦。

aop的应用场景:

AOP中主要概念:

Aspect定义到aop类上,包含切点+通知两部分。

Pointcut:切点,连接点,定义在源码中哪里进行切入。

Advice:通知,定义增强到哪个切点,增强什么内容。

完整例子:

@Component
@Aspect
@Slf4j
public class MyAdvice {

    @Pointcut(value = "execution(* com.hugh.boot.controller.HelloController.handler01(..))")  // 只对controller一个函数生效
    public void myPointcut(){

    }

    @Around("myPointcut()")
    public Object myLogger(ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getTarget().getClass().toString();
        String methodName = pjp.getSignature().getName();
        Object[] array = pjp.getArgs();

        ObjectMapper mapper = new ObjectMapper();

        log.info("调用前," + className + ":" + methodName + " 传递的参数为:" + mapper.writeValueAsString(array));
        Object obj = pjp.proceed();
        log.info("调用后," + className + ":" + methodName + " 返回:" + mapper.writeValueAsString(obj));

        return obj;
    }
}

55、@Autowired和@Resource的异同

相同:两个都是用来注入组件

区别:@Autowired是spring的注解,而@Resource是java的注解
@Autowired按类型装配,如果有多个同类型的bean则抛出异常;@Resource按名称装配,如果名称不存在则使用类型装配。
如果你的项目中没有多个同类型的bean,那么@Autowired和@Resource是可以互换使用的,如果有多个同类型的bean,那么就要使用@Resource进行指定名称注入。

56、Service + ServiceImpl搭配的编程模式

Service:是一个接口,只定义了与业务相关的操作。无@Service注解,不是组件
ServiceImpl:是Service的一个具体实现类,有@Service注解

如果service只有一个实现类,使用时直接装配Service的组件。
如果一个Service接口有多个实现类,则在实现类中通过@Service(name="xxx")进行注解,然后使用时通过@Resource(name="xxx")进行装配

多个实现类的示例:

HumanService.java

public interface HumanService {
    public String name();
}

TeacherServiceImpl.java

@Service("teacherService")
public class TeacherServiceImpl implements HumanService {
    @Override
    public String name() {
        System.out.println("teacher!");
        return "teacher";
    }
}

DoctorServiceImpl.java

@Service("doctorService")
public class DoctorServiceImpl implements HumanService {
    @Override
    public String name() {
        System.out.println("doctor!");
        return "doctor";
    }
}

xxController.java

@Resource(name="teacherService")
HumanService humanService;

@GetMapping("/human")
public String helloHuman(){
    return humanService.name();
}

57、@Value注解

获取属性文件中定义的属性值,默认从application.yaml中获取
示例:
1)yaml中的配置
# 常量值定义
api:
  host:
    aliyun: http://aliyun:9091/test

2)测试代码:
@Value("${api.host.aliyun}")
private String aliyunUrl;

@Test
public void testValue(){
    System.out.println(aliyunUrl);
}

58、@PostConstruct注解

被该注解修饰的方法会在项目启动的时候执行,并且只会被服务器执行一次。
示例:
@PostConstruct
public void init(){
    System.out.println("ExtInitiator init");
}
 

59、@interface自定义注解

注解提供了类和方法的额外备注信息,通过反射可以获取这些信息。
注解和注释的区别是,注释只是给人看的,编译时会去掉,而注解在编译时可以被捕获。
如果某一个类进行了使用了自定义注解:
通过ApplicationContext的getBeansWithAnnotation方法可以获取所有带这些注解的示例
通过AnnotationUtils的findAnnotation方法可以获取注解的详细信息。

自定义注解示例:
@Retention(RetentionPolicy.RUNTIME) // 作用于运行时
@Target(ElementType.TYPE)   // 作用于类上
@Component
public @interface Extension {
    String bizScene() default "defaultBizScene";
    String useCase() default "defaultUseCase";
}

注解的使用
@Component
@Extension(bizScene="test", useCase="one")
public class FatherOne implements Father {
    @Override
    public String sayHello() {
        System.out.println("测试: father1");
        return "father1";
    }
}

注解类的使用
Map<String, Object> extensionBeans = applicationContext.getBeansWithAnnotation(Extension.class);
for(Object bean : extensionBeans.values()){
    Class<?> extensionClz = ClassUtils.getUserClass(bean);
    Extension extension = AnnotationUtils.findAnnotation(extensionClz, Extension.class);
    String bizScene = extension.bizScene();
    String useCase = extension.useCase();
    extRespository.getExtRepository().put(bizScene + "_" + useCase, bean);
}

60、springboot设置定时任务


分两步,
首先,在main主程序中添加注解@EnableScheduling
然后,在component组件中给定时任务方法添加注解@Scheduled(cron = "0 0/2 * * * ?")
比如:

@Component
@Slf4j
public class LoadTask {

    /**
     * 每2分钟执行一次
     */
    @Scheduled(cron = "0 0/2 * * * ?")
    public void execute(){
        Date date = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        log.info("定时任务:" + formatter.format(date));
    }
}

猜你喜欢

转载自blog.csdn.net/benben044/article/details/132042596