SpringBoot2学习笔记

SpringBoot2入门

  • SpringBoot官方文档
https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started.introducing-spring-boot
  • pom文件 引入依赖


    4.0.0

    com.amberli
    boot-01-helloworld
    1.0-SNAPSHOT
    jar
    

    
        spring-boot-starter-parent
        org.springframework.boot
        2.3.4.RELEASE
    





    

        
            org.springframework.boot
            
            spring-boot-starter-web
        

        
            mysql
            mysql-connector-java
        
    



    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


  • 创建主程序
/*
主程序类
@springBootApplication:这是一个springboot应用
 */
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);

    }
}
  • 编写业务
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;


//@ResponseBody
//@Controller

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String handle01(){
        return "hello, Spring Boot 2";
    }
}
  • 测试
直接运行main方法
浏览器输入:http://localhost:8080/hello
  • 简化配置
application.properties
默认配置:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties

server.port=8888
  • 简化部署

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

把项目打成jar包,直接在目标服务器运行即可 java -jar boot-01-helloworld-1.0-SNAPSHOT.jar
注意点:
    取消掉cmd的快速编辑模式,否则需要在cmd中按Enter回车键 才能启动应用

了解自动配置原理

  • SpringBoot特点
1.依赖管理
    父项目做依赖管理
        
                spring-boot-starter-parent
                org.springframework.boot
                2.4.5
        
    开发导入starter场景启动器 
        见到很多spring-boot-starter-*,  *就是某种场景
        SpringBoot所有支持的场景 https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
        所有官方首发都遵循类似的命名模式;spring-boot-starter-*,其中*是特定类型的应用程序。
        见到的 *-spring-boot-starter,第三方为我们提供的简化开发的场景启动器
        
    无需关注版本号,自动版本仲裁
    可以修改版本号
        先查看spring-boot-dependencies里面的当前依赖的版本
        在当前项目里面重写配置
        
                5.1.43
        
2. 自动配置
    自动配好Tomcat
         
                org.springframework.boot
                spring-boot-starter-tomcat
                2.3.4.RELEASE
         
    自动配好SpringMVC
        引入SpringMVC全套组件
        自动配好SpringMVC常用组件(功能)
    自动配好Web常见功能,如字符编码问题
        SpringBoot帮我们配好了所有web开发的常见场景
    默认的包结构
        主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
        无需以前的包扫描配置
        想要改变扫描路径,@SpringBootApplication(scanBasePackages = "com.amberli")
            或者@ComponentScan指定扫描路径
        @SpringBootApplication等同于
            @SpringBootConfiguration
            @EnableAutoConfiguration
            @ComponentScan("com.amberli")
    各种配置拥有的默认值
        配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
    按需加载所有自动配置项
        非常多的starter
        引入了哪些场景,这个场景的自动配置才会开启
        SpringBoot所有的自动配置功能都在spring-boot-autoconfigure包里面
  • 打包部署 在pom.xml中添加

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

在IDEA中Maven插件上点击运行clean、package,即可把工程项目打包成jar包
打包好的jar包被生成在工程项目的target文件夹内
用cmd运行 java -jar boot-01-helloworld-1.0-SNAPSHOT.jar,就可以运行工程项目
  • IDEA快捷键
ctrl + alt + U  以UML的雷同展现类有哪些继承类,派生类以及实现哪些接口
ctrl + shift + alt + u    以图的方式显示项目中依赖之间的关系,同上区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现
alt + ins 创建新类,新包,构造方法,getter、setter等
ctrl + n  查找所有类
ctrl + H  以树形的方式展现类层次结构图

  • SpringBoot帮我们配好了所有web开发的常见场景
public class MainApplication {
    public static void main(String[] args) {
        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

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

底层注解-@Configuration详解

  • 基本使用 Full模式和Lite模式
/**
 * 1.配置类里面是有@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2.配置类本身也是组件
 * 3.proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods=true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
 *      Lite(proxyBeanMethods=false)(每个@Bean方法被调用多次返回的组件都是新创建的)
 *      组件依赖
 */

//@Configuration(proxyBeanMethods=false)  //告诉SpringBoot这是一个配置类 == 配置文件
@Configuration(proxyBeanMethods=true)  //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean       //给容器中添加组件,以方法名作为组件id, 返回类型就是组件类型,返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        // (proxyBeanMethods=true 说明user组件依赖pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}
  • @Configuration测试代码
/*
主程序类
@springBootApplication:这是一个springboot应用
 */
//@SpringBootApplication(scanBasePackages = "com.amberli")

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
    public static void main(String[] args) {
        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

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

        //从容器中获取组件
        Pet tom01 = run.getBean("tom", Pet.class);
        Pet tom02 = run.getBean("tom", Pet.class);

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

        // com.amberli.boot.config.MyConfig$$EnhancerBySpringCGLIB$$106bc9e2@435cc7f9
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);   //com.amberli.boot.config.MyConfig$$EnhancerBySpringCGLIB$$106bc9e2@435cc7f9

        // 如果@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("用户的宠物:" + (user1.getPet()==tom));
    }
}
  • 日常开发实战
配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)
配置 类组件之间无依赖关系,用Lite模式加速容器启动过程,减少判断
  • 底层注解-@Conditional条件装配
    • 条件装配:满足Conditional指定的条件,则进行组件注入
//@ConditionalOnBean(name="tom")  //容器中必须先有 tom 组件,才会有下面的user01、tomcatPet组件

//@Configuration(proxyBeanMethods=false)  //告诉SpringBoot这是一个配置类 == 配置文件

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods=true)  //告诉SpringBoot这是一个配置类 == 配置文件
@ConditionalOnBean(name="tom")  //容器中必须先有 tom 组件,才会有下面的user01、tomcatPet组件
//@ConditionalOnMissingBean(name="tom") //反向操作,容器中没有 tom 组件时, 才会有下面的user01、tomcatPet组件
public class MyConfig {
    @Bean       //给容器中添加组件,以方法名作为组件id, 返回类型就是组件类型,返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        // (proxyBeanMethods=true 说明user组件依赖pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

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

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
    public static void main(String[] args) {
        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

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

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:" + tom);  // false

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+ user01);     // false

        boolean tomcatPet = run.containsBean("tomcatPet");
        System.out.println("容器中tomcatPet组件:" + tomcatPet);  //false

    }

}

@ConditionalOnMissingBean(name="tom") //反向操作,容器中没有 tom 组件时, 才会有下面的user01、tomcatPet组件

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods=true)  //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name="tom")  //容器中必须先有 tom 组件,才会有下面的user01、tomcatPet组件
@ConditionalOnMissingBean(name="tom") //反向操作,容器中没有 tom 组件时, 才会有下面的user01、tomcatPet组件
public class MyConfig {
    @Bean       //给容器中添加组件,以方法名作为组件id, 返回类型就是组件类型,返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        // (proxyBeanMethods=true 说明user组件依赖pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

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

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
    public static void main(String[] args) {
        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

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

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:" + tom);  // false

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+ user01);     // true

        boolean tomcatPet = run.containsBean("tomcatPet");
        System.out.println("容器中tomcatPet组件:" + tomcatPet);  //true

    }
}
  • 底层注解-@ImportResource导入Spring配置文件
    • 如果业务中想继续复用bean.xml文件,可使用@ImportResource
bean.xml:




    
        
        
    

    
        
    



使用方法:
@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}

测试类:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
    public static void main(String[] args) {
        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        boolean amber01 = run.containsBean("amber01");
        boolean amber02 = run.containsBean("amber02");
        System.out.println("容器中amber01组件:" + amber01);  //  true
        System.out.println("容器中amber02组件:" + amber02);  //  true
    }
}
  • 配置绑定 @ConfigurationProperties
    • 如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用
SpringBoot配置文件绑定方法一
@ConfigurationProperties + @Component

假设配置文件application.properties有:
mycar.brand=aodi
mycar.price=150000

只有在容器中的额组件,才会拥有SpringBoot提供的强大功能
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

SpringBoot配置文件绑定方法二
@EnableConfigurationProperties + @ConfigurationProperties

在配置类中绑定
    @EnableConfigurationProperties(Car.class)
        1.开启Car配置绑定功能
        2.把Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {
...
}

@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}
  • 自动配置原理
    • 加载自动配置类 @SpringBootApplication(scanBasePackages = "com.amberli") 等同于 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com.amberli")
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...}

  • @SpringBootConfiguration @Configuration 代表当前是一个配置类

  • @ComponentScan 指定扫描哪些Spring注解

  • @EnableAutoConfiguration

@AutoConfigurationPackage   //自动配置包
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
...
}
  • @AutoConfigurationPackage //自动配置包
@Import({Registrar.class})      //给容器中导入一个组件
public @interface AutoConfigurationPackage {
...
}

// 1.利用Registrar给容器中导入一系列组件
// 2.将指定的一个包下的所有组件导入进MainApplication所在包下
  • 自动配置(源码分析),初始加载自动配置类
    • @Import({AutoConfigurationImportSelector.class})
1.利用getAutoConfigurationEntry(AnnotationMetadata annotationMetadata){};给容器中批量导入一些组件
2.调用List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);获取到所有需要导入到容器中的配置类
3.利用工厂加载Map> 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个场景的所有自动配置启动的时候默认全部加载,但是xxxAutoConfiguration按照条件装配规则(@Conditional),最终会按需配置。
  • 总结
 * SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先
    * SpringBoot先加载所有的自动配置类xxxAutoConfiguration
    * 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxProperties里面读取,xxxProperties和配置文件进行了绑定)
    * 生效的配置类就会给容器中装配很多组件
    * 只要容器中有这些组件,相当于这些功能就有了
    * 定制化配置
        * 用户直接自己@Bean替换底层的组件
        * 用户去看这个组件是获取的配置文件什么值就去修改
    xxxAutoConfiguration-->组件-->xxxProperties里面取值-->application.properties
  • SpringBoot最佳实践

    • 引入场景依赖

      官方文档

    • 查看自动配置了哪些

      • 自己分析,引入场景对应的自动配置一般都生效了
      • 配置文件中debug=true开启自动配置报告
        • Negative(不生效)
        • Positive(生效)
    • 是否需要修改

      • 参照文档修改配置项
        • 官方文档
        • 自己分析。xxxProperties绑定了配置文件有哪些
    • 自己定义加入或者替换组件

      • @Bean、@Component...
    • 自定义器xxxCustomizer;

  • Lombok简化开发

Lombok用标签方式代替构造函数、getter、setter、toString()、EqualsAndHashCode()和日志开发等

SpringBoot已经管理Lombok,引入依赖

    org.projectlombok
    lombok


IDEA中File-->Settings-->Plugins,搜索插件Lombok并安装

@NoArgsConstructor  //无参构造器
@AllArgsConstructor //有参构造器
@Data               //setter、getter
@ToString           //toString()方法
@Slf4j              //简化日志开发,控制台输出日志信息
@EqualsAndHashCode
  • dev-tools 自动更新项目中静态页面的修改
添加依赖

        
            org.springframework.boot
            spring-boot-devtools
            true
        
  • Spring Initailizr项目初始化向导

    • Spring Initailizr是创建SpringBoot项目的工程向导
    • 在IDEA中,菜单栏File-->New-->Project-->Spring Initailizr
  • 配置文件 yaml的用法

    • 基本语法
key: value; kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
'#'表示注释
字符串无需加引号,如果要加,单引号''、双引号""表示字符串内容会被 转义、不转义
  • 数据类型
    • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
  • 对象:键值对的集合。map、hash、set、object
# 行内写法
k: {k1:v1,k2:v2,k3:v3}

# 或
k:
 k1: v1
 k2: v2
 k3: v3
  • 数组:一组按次序排列的值。array、list、queue
# 行内写法
k: [v1,v2,v3]

# 或
k:
 - v1
 - v2
 - v3
  • yaml实例
@ConfigurationProperties(prefix = "person")
@Component  //容器中的组件
@ToString
@Data
public class Person {
    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List animal;
    private Map score;
    private Set salarys;
    private Map> allPets;
}

@ToString
@Data
public class Pet {
    private String name;
    private Double weight;
}
  • 用yaml表示以上对象
person:
  userName: zhangsan
  boss: true
  birth: 2020/12/12
  age: 18
#  interests: [篮球,足球,编程]
  interests:
    - 篮球
    - 足球
    - 18
  animal: [阿猫,阿狗]
#  score:
#    english: 80
#    math: 90
#    python: 95
  score: {english:80, math:90, python:95}
  salarys:
    - 9999.9
    - 9999.99
  pet:
    name: 阿狗
    weight: 99.99
  allPets:
    sick:
      - {name:阿狗, weight: 99.99}
      - name: 阿猫
        weight: 88.88
      - name: 阿虫
        weight: 77.77
    health:
      - name: 阿黑
        weight: 66.66
      - {name:阿白, weight:55.55}
  • 自定义的类和配置文件绑定一般没有提示,若需要有提示,添加如下依赖:

    org.springframework.boot
    spring-boot-configuration-processor
    true


    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin

                
                    
                        org.springframework.boot
                        spring-boot-configurationprocessor
                    
                

            
        
    

  • web开发 简单功能分析
    • 静态资源访问
静态资源目录
    只要静态资源放在类路径下:called /static(or /public or /resources or /META-INF/resources
    访问:当前项目根路径/+静态资源名   即可访问
    原理:静态映射/**
    请求进来,先去找Controller看能否处理,不能处理的所有请求都交给静态资源处理器
    静态资源也找不到则相应404页面

静态资源访问前缀
    默认无前缀
        spring:
          mvc:
            static-path-pattern: /res/**
    当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
    
也可以改变默认的静态资源路径,/static, /public, /resources, /META-INF/resources失效
    web:
        resources:
          static-locations: [classpath:/haha/,]
  • webjar
可用jar方式添加css, js等资源文件
https://www.webjars.org/

    org.webjars
    jquery
    3.6.0


访问地址:http://localhost:8080/webjars/jquery/3.6.0/jquery.js 后面的地址要安装依赖里面的包路径
  • web场景-welcome页面
静态资源路径下 index.html
    可以配置静态资源路径
    但是不可以配置静态资源的访问前缀,否则导致index.html不能被默认访问
    spring:
    #  mvc:
    #    static-path-pattern: /res/**   这个会导致welcome page功能失效
    
      web:
        resources:
          static-locations: [classpath:/haha/]
          
 controller能处理/index
  • web场景-Favicon
指网页标签上的小图标
favicon.ico放在静态资源目录下即可
#  mvc:
#    static-path-pattern: /res/**   这个会导致Favicon 功能失效
  • web场景-静态资源管理(源码分析)
    • SpringBoot启动默认加载xxxAutoConfiguration类(自动配置类)
    • SpringMVC功能的自动配置类WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    ...
}

  • 给容器中配置的内容
    • 配置文件的相关属性的绑定:WebMvcProperties==spring.mvc、ResourceProperties==spring.resources
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
    ...
}
  • 配置类只有一个有参构造器
    • 有参构造器所有参数的值都会从容器中确定
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
		ListableBeanFactory beanFactory, ObjectProvider messageConvertersProvider,
		ObjectProvider resourceHandlerRegistrationCustomizerProvider,
		ObjectProvider dispatcherServletPath,
		ObjectProvider> servletRegistrations) {
	this.mvcProperties = mvcProperties;
	this.beanFactory = beanFactory;
	this.messageConvertersProvider = messageConvertersProvider;
	this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
	this.dispatcherServletPath = dispatcherServletPath;
	this.servletRegistrations = servletRegistrations;
	this.mvcProperties.checkConfiguration();
}
ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
WebMvcProperties mvcProperties获取和spring.mvc绑定的所有的值的对象
ListtableBeanFactory beanFactory Spring的beanFactory
HttpMessageConverters找到所有的HttpMessageConverters
ResourceHandlerRegistrationCustomizer找到 资源处理器的自定义器
DispatchcherServletPath
ServletRegistrationBean给应用注册Servlet、Filter...
  • 资源处理的默认规则
...
public class WebMvcAutoConfiguration {
    ...
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
        ...
		@Override
		protected void addResourceHandlers(ResourceHandlerRegistry registry) {
			super.addResourceHandlers(registry);
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			ServletContext servletContext = getServletContext();
			addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (servletContext != null) {
					registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
				}
			});
		}
        ...
        
    }
    ...
}
  • 根据以上代码,我们可以通过配置禁止所有静态资源规则
spring:
  web:
    resources:     
      add-mappings: false   # 禁用所有静态资源规则
  • 静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
            "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/].
     */
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
    ...
}
  • 欢迎页规则
...
public class WebMvcAutoConfiguration {
    ...
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
        ...
		@Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
				FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
			WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
					new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
			welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
			welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
			return welcomePageHandlerMapping;
		}
  • WelcomePageHandlerMapping的构造方法如下
这个构造方法内的代码也解释了web场景-welcome与favicon功能中配置static-path-pattern了welcome页面和小图标失效的问题

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
                          ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        //要用欢迎页功能,必须是/**
        logger.info("Adding welcome page: " + welcomePage);
        setRootViewName("forward:index.html");
    }
    else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        //调用Controller /index
        logger.info("Adding welcome page template: index");
        setRootViewName("index");
    }
}
  • 请求处理-Rest映射及源码分析
    • 请求映射
@xxxMapping
以前:
    /getUser    获取用户
    /deleteUser 删除用户
    /editUser   修改用户
    /saveUser   保存用户
现在:
    /user
    GET-获取用户
    DELETE-删除用户
    PUT-修改用户
    POST-保存用户
核心Filter; HiddenHttpMethodFilter
用法:表单method=post,隐藏域_method=put
  • 测试表单



    
    Title


    

hello,springboot2!欢迎您的光临!

测试REST风格

测试基本注解: @PathVariable
@RequestHeader
@RequestParam-GET
@RequestParam-GET
@MatrixVariable(矩阵变量) @MatrixVariable(矩阵变量) @MatrixVariable(矩阵变量)/boss/{bossId}/{empId}
测试@RequestBody获取数据
用户名:
邮箱:
  • SpringBoot中手动开启
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   # 开启表单的Rest功能
  • Controller层
@Slf4j
@RestController
public class HelloController {


//    @RequestMapping(value = "/user", method = RequestMethod.GET)
    @GetMapping("/user")
    public String getUser(){
        return "GET-张三";
    }
    
//    @RequestMapping(value = "/user", method = RequestMethod.POST)
    @PostMapping("/user")
    public String saveUser(){
        return "POST-张三";
    }

//    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    @PutMapping("/user")
    public String putUser(){
        return "PUT-张三";
    }

//    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    @DeleteMapping("/user")
    public String deleteUser(){
        return "DELETE-张三";
    }
}

===================================
ctrl+n 查WebMvcAutoConfiguration类
@Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
  • Rest原理(基于表单提交要使用REST的时候)
* 表单提交会带上_method=PUT
    * 请求过来被HiddenHttpMethodFilter拦截
        * 请求是否正常,并且是POST
            * 获取到_method的值
            * 兼容以下请求:PUT,DELETE,PATCH
            * 原生request(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的值
            * 过滤器链接放行的时候用wrapper.以后的方法调用getMethod是调用requestWrapper的
* Rest使用客户端工具,如Postman直接发送Put、Delete等请求方式,无需Filter
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List ALLOWED_METHODS;
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";

    public HiddenHttpMethodFilter() {
    }

    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        
        // 请求为post请求时请求正常
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                //传入参数大小写都行
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                //PUT,DELETE,PATCH
                if (ALLOWED_METHODS.contains(method)) {
                    //包装模式
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter((ServletRequest)requestToUse, response);
    }

    static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }

    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }

        public String getMethod() {
            return this.method;
        }
    }
}

  • 请求处理-改变默认的_method
    • 重写上述代码POST请求处理规则
 @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

@ConditionalOnMissingBean({FormContentFilter.class})表示容器中没有HiddenHttpMethodFilter.class类组件时
OrderedHiddenHttpMethodFilter组件才生效,可以仿照springboot中HiddenHttpMethodFilter的写法在配置类中加入该组件。

@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}

在配置类中加入自定义组件,springboot默认规则失效。
  • 请求处理-请求映射原理(源码分析)
    • org.springframek.web.servlet.DispatchServlet开始分析-->doDispatch()
    • doService
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
...

    try {
        this.doDispatch(request, response);
    } finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
            this.restoreAttributesAfterInclude(request, attributesSnapshot);
        }

        if (this.parseRequestPath) {
            ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
        }
}
  • doDispatch()分发请求与响应
 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    // 找到当前请求使用哪个Handler(Controller的方法)处理
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //HandlerMapping:处理器映射。/xxx-->xxx
                    //Dertermine handler adapter for the current request.
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    //Process last-modified header, if supported by the handler
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }
  • 分析getHandler()
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            Iterator var2 = this.handlerMappings.iterator();

            while(var2.hasNext()) {
                HandlerMapping mapping = (HandlerMapping)var2.next();
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }

        return null;
    }

其中保存了所有@RequestMapping和handler的映射规则
所有的请求都在HandlerMapping中。
    SpringBoot自动配置欢迎页的WelcomePageHandlerMapping.访问/能访问到index.html;
    SpringBoot自动配置了默认的RequestMappingHandlerMapping
    请求进来,挨个尝试所有的HandlerMapping看是否有请求信息
        如果有就找到这个请求对应的handler
        如果没有就是下一个HandlerMapping
    我们需要一些自定义的映射出来,我们也可以自己给容器中放HandlerMapping。自定义HandlerMapping
  • 请求处理-常用参数注解使用
    • @PathVariable 路径变量
    • @RequestHeader 获取请求头
    • @RequestParam 获取请求参数(只问号后的参数)
    • @CookieValue 获取Cookie值
    • @RequestBody 获取请求体[POST]
    • @RequestAttribute 获取request域属性
  • 用例
@RestController
public class ParameterTestController {

    @GetMapping("/car/{id}/owner/{username}")
    public Map getCar(@PathVariable("id") Integer id,
                                      @PathVariable("username") String name,
                                      @PathVariable Map pv,
                                      @RequestHeader("User-Agent") String userAgent,
                                      @RequestHeader Map headers,
                                      @RequestParam("age") Integer age,
                                      @RequestParam("inters") List inters,
                                      @RequestParam Map params,
                                      @CookieValue("_xsrf") String _xsrf,
                                      @CookieValue("_xsrf") Cookie cookie){
        Map map = new HashMap<>();
//        map.put("id", id);
//        map.put("name", name);
//        map.put("pv",pv);
//
//        map.put("userAgent", userAgent);
//        map.put("headers", headers);

        map.put("age", age);
        map.put("inters", inters);
        map.put("params", params);
        map.put("_xsrf", _xsrf);
        System.out.println(cookie.getName() + "===>" + cookie.getValue());

        return map;
    }
    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map map = new HashMap<>();
        map.put("content", content);
        return map;
    }


    // 1.语法:    /cars/sell;low=34;brand=byd,audi,yd
    // 2.SpringBoot 默认是禁用了矩阵变量的功能
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List brand,
                        @PathVariable("path") String path){
        Map map = new HashMap<>();
        map.put("low", low);
        map.put("brand", brand);
        map.put("path", path);
        return map;
    }
}
  • @RequestAttribute 获取request域属性
@Controller
public class RequestController {

    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){
        request.setAttribute("msg", "成功了...");
        request.setAttribute("code", 200);
        return "forward:/success";  // 转发到 /success请求
    }
    

    //  @RequestAttribute在这个方法
    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute("msg") String msg,
                       @RequestAttribute("code") Integer code,
                       HttpServletRequest request){
        Object msg1 = request.getAttribute("msg");
        HashMap map = new HashMap<>();
        map.put("reqMethod_msg", msg1);
        map.put("annotation_msg", msg);
        return map;
    }
}
  • 请求处理 矩阵变量 @MatrixVariable与UrlPathHelper
    • 1.语法:请求路径: /cars/sell;low=32;brand=byd,audi,yd
    • 2.SpringBoot默认是禁用了矩阵变量的功能
      • 手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量
    • 3.矩阵变量必须有url路径变量才能被解析
  • 手动开启矩阵变量
* 方式一:实现WebMvcConfigurer接口
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
        public void configurePathMatch(PathMatchConfigurer configurer){
            UrlPathHelper urlPathHelper = new UrlPathHelper();
            // 不移除:后面的内容,矩阵变量功能就可以生效
            urlPathHelper.setRemoveSemicolonContent(false);
            configurer.setUrlPathHelper(urlPathHelper);
        }  
 }
方式二:创建返回WebMvcConfigurer Bean:

@Configuration(proxyBeanMethods = false)
public class WebConfig{

        @Bean
        public WebMvcConfigurer webMvcConfigurer(){
            return new WebMvcConfigurer() {
                @Override
                public void configurePathMatch(PathMatchConfigurer configurer) {
                    UrlPathHelper urlPathHelper = new UrlPathHelper();
                    // 不移除:后面的内容,矩阵变量功能就可以生效
                    urlPathHelper.setRemoveSemicolonContent(false);
                    configurer.setUrlPathHelper(urlPathHelper);
                }
            };
        }
}
  • 矩阵变量@MatrixVariable的用例
@RestController
public class ParameterTestController {
        // 1.语法:    /cars/sell;low=34;brand=byd,audi,yd
        // 2.SpringBoot 默认是禁用了矩阵变量的功能
        @GetMapping("/cars/{path}")
        public Map carsSell(@MatrixVariable("low") Integer low,
                            @MatrixVariable("brand") List brand,
                            @PathVariable("path") String path){
            Map map = new HashMap<>();
            map.put("low", low);
            map.put("brand", brand);
            map.put("path", path);
            return map;
        }


        // /boss/a;age=20/2;age=10
        
            @GetMapping("/boss/{bossId}/{empId}")
            public Map boss(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
                            @MatrixVariable(value = "age", pathVar = "empId") Integer empAge){
                HashMap map = new HashMap<>();
                map.put("bossAge", bossAge);
                map.put("empAge", empAge);
                return map;
            }
    }
  • 请求处理-自定义参数绑定原理
测试封装POJO:
    
姓名:
年龄:
生日:
宠物姓名:
宠物年龄:
@RestController public class ParameterTestController { /** * 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定 * @param person * @return */ @PostMapping("saveuser") public Person saveUser(Person person){ return person; // {"userName":"zhangsan","age":18,"birth":"2020-12-09T16:00:00.000+00:00","pet":{"name":"阿猫","age":"5"}} } } import lombok.Data; import java.util.Date; @Data public class Person { private String userName; private Integer age; private Date birth; private Pet pet; } import lombok.Data; @Data public class Pet { private String name; private String age; }
  • 封装过程用到 ServletModelAttributeMethodProcessor
public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
	
    @Override//本方法在ModelAttributeMethodProcessor类,
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
	}

	@Override
	@Nullable//本方法在ModelAttributeMethodProcessor类,
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		...

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				...
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
                    //web数据绑定器,将请求参数的值绑定到指定的JavaBean里面**
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}
}

WebDataBinder利用它里面的Converters将请求数据转成指定的数据类型,再次封装到javabEAN中
在过程当中,用到GenericConversionService:在设置每个值的时,找它里面所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型
  • 响应处理-ReturnValueHandler原理
数据响应:
    响应页面
    响应数据:JSON、XNL、xls、图片音频、自定义协议数据
  • 给前端返回json数据,需要引入的依赖

    org.springframework.boot
    spring-boot-starter-web


web场景会自动引入json场景

  org.springframework.boot
  spring-boot-starter-json
  2.5.1
  compile

  • 控制层代码
@Controller
public class ResponseTestController {

    @ResponseBody   //利用返回值处理器里面的消息转换器进行处理
    @GetMapping("/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setUserName("张三");
        person.setAge(18);
        person.setBirth(new Date());
        return person;  // {"userName":"张三","age":18,"birth":"2021-06-16T10:56:34.349+00:00","pet":null}
    }

}
  • 响应处理,自定义MessageConverter,实现多协议数据兼容,json、xml、x-amberli(自定义)
1. @ResponseBody响应数据出去 调用RequestResponseBodyMethodProcessor处理
2. Processor处理方法返回值,通过MessageConverter处理
3. 所有MessageConverter合起来可以支持各种媒体类型数据的操作(读、写)
4. 内容协商找到最终的 messageConverter
  • 自定义Converter

/**
 * 自定义的converter
 */
public class AmberliMessageConverter implements HttpMessageConverter {
    @Override
    public boolean canRead(Class aClass, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class aClass, MediaType mediaType) {
        return aClass.isAssignableFrom(Person.class);
    }

    /**
     * 服务器要统计所有MessageConverter都能写出哪些内容类型
     * application/x-amberli
     * @return
     */
    @Override
    public List getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-amberli");
    }

    @Override
    public Person read(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        //自定义协议数据的写出
        String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
        //写出去
        OutputStream body = httpOutputMessage.getBody();
        body.write(data.getBytes());
    }
}

  • SpringMVC的什么功能,一个入口给容器中添加一个WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {

            public void extendMessageConverters(List> converters){
                converters.add(new AmberliMessageConverter());
            }
    }
}
  • 在headers头信息中添加 Accept application/x-amberli
@Controller
//@RestController
public class ResponseTestController {

    @ResponseBody
    @GetMapping("hell")
    public FileSystemResource file(){
        return null;
    }

    /**
     * 1、浏览器发请求直接返回xml      application/xml     jacksonXmlConverter
     * 2.如果是ajax请求 返回json      application/json     jacksonJsonConverter
     * 3、如果自定义发请求,返回自定义协商数据 application/xxx     xxxConverter
     * @return
     */
    @ResponseBody   //利用返回值处理器里面的消息转换器进行处理
    @GetMapping("/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setUserName("张三");
        person.setAge(18);
        person.setBirth(new Date());
        return person;      //张三;18;Thu Jun 17 10:58:15 CST 2021
    }

}
  • Thymeleaf基本语法
表达式名称 语法 用途
变量取值 ${...} 获取请求域、session域、对象等值
选择变量 *{...} 获取上下文对象值
消息 #{...} 获取国际化等值
链接 @{...} 生成链接
片段表达式 ~{...} jsp:include作用,引入公共页面片段
  • web实验-后台管理系统基本功能
    • 项目创建
使用IDEA中的Spring Initializr
thymeleaf
web-starter
devtools
lombok
  • 登录页面
    • /static 放置css、js、fonts、images等静态资源
    • /templates/login.html登录页



    
    
    
    
    

    登陆

    
    
    

    
    




...



  • /templates/index.html主页



  
  
  
  
  
  

  AdminX

  
  
  
  
  

  
  


  




  






    
...
2014 © AdminEx by ThemeBucket
  • 登录控制层
@Slf4j
@Controller
public class IndexController {
    //登录页
    @GetMapping(value = {"/","/login"})
    public String loginnPage(){
        return "login";
    }

//    @PostMapping("/login")
//    public String main(String username, String password){
//
//        // 登录成功重定向到main.html页面
//        return "redirect:/index.html";
//    }

    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){
        //自5.3版本起,isEmpty(Object)已建议弃用,使用hasLength(String)或hasText(String)替代。
//        if(StringUtils.hasText(user.getUserName()) && StringUtils.hasLength(user.getPassword())){
        if(StringUtils.hasText(user.getUserName()) && "123".equals(user.getPassword())){
            //把登录成功的用户保存起来
            session.setAttribute("loginUser", user);
            return "redirect:/index.html";
        }else {
            model.addAttribute("msg","账号或密码错误");
            //回到登录页
            return "login";
        }
    }

//    // index页面
//    @GetMapping("/index.html")
//    public String indexPage(){
//        return "index";
//    }

    @GetMapping("index.html")
    public String indexPage(HttpSession session, Model model){

        log.info("当前方法是:{}","indexPage");
        //判断是否登录, 拦截器, 过滤器
//        Object loginUser = session.getAttribute("loginUser");
//        System.out.println("====>"+loginUser);
//        if(loginUser !=null){
//            return "index";
//        }else {
//            //回到登录页
//            model.addAttribute("msg","请重新登录");
//            return "login";
//        }
        return "index";
    }
}
  • User模型
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {

    private String userName;
    private String password;
}
  • 公共页面/templates/common.html



    
    所有公共信息

    
    

    
    




...
  • web实验-遍历数据
* 控制层
    @GetMapping("/dynamic_table.html")
    public String dynamic_table(Model model){

        // 表格内容的遍历
        List users = Arrays.asList(new User("zhangsan","123"),
                new User("lisi","1234"),
                new User("wangwu","12345"),
                new User("zhaoliu","123456"));
        model.addAttribute("users", users);

        return "table/dynamic_table";
    }

* 页面层
            
            
序号 用户名 密码
[[${user.password}]]
  • 视图解析(源码分析)-视图解析器与视图
视图解析原理流程:
1.目标方法处理的过程中(阅读DispatcherServlet源码),所有数据都会被放在 ModelAndViewContainer 里面,其中包括数据和视图地址。
2.方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer 。
3.任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址)。
4. processDispatchResult()处理派发结果(页面改如何响应)
    render(mv, request, response); 进行页面渲染逻辑
        根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
            所有的视图解析器尝试是否能根据当前返回值得到View对象
                得到了 redirect:/index.html --> Thymeleaf new RedirectView()。
                ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
                view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作。
        RedirectView 如何渲染【重定向到一个页面】
        获取目标url地址
        response.sendRedirect(encodedURL);

视图解析:
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() --> render就是重定向
- 返回值是普通字符串:new ThymeleafView()—>
  • 拦截器-登录检查与静态资源放行
    • 编写一个实现HandlerInterceptor接口的拦截器
/**
 * 拦截器  做登录检查
 * 1、配置好拦截器要拦截哪些请求
 * 2. 把这些配置放在容器中
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);

        //登录检查逻辑
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser != null){
            //放行
            return true;
        }
        //拦截住,未登录,跳转到登录页
//        session.setAttribute("msg","请先登录");
//        response.sendRedirect("/");

        request.setAttribute("msg","请先登录");
        request.getRequestDispatcher("/").forward(request,response);      //请求转发
        return false;
    }

    /**
     * 目标方法执行完成以后
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}", modelAndView);
    }

    /**
     * 页面渲染以后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

  • 拦截器注册到容器中,指定拦截规则
/**
 * 1.编写一个拦截器实现HandlerInterceptor接口
 * 2.将编写好的拦截器注册到容器中(实现WebMvcConfigurer的addInterceptor
 * 3.指定拦截资源规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    /**
     * 将拦截器添加到容器中
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LoginInterceptor())     //将拦截器添加到容器中
                .addPathPatterns("/**")       //需要拦截的部分,/**拦截所有的请求,包括静态资源
                .excludePathPatterns("/", "/login","/css/**","/fonts/**","/images/**","/js/**");    //需放行的部分
    }
}
  • 拦截器(源码分析)-拦截器的执行时机和原理
1.根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有拦截器)
2.先来的顺序执行,所有拦截器的preHandle()方法
    如果当前拦截器preHandle()返回true,则执行下一个拦截器的preHandle()
    如果当前拦截器返回false,直接倒序执行所有已经执行了的拦截器的afterCompletion()
3.如果任何一个拦截器返回false,直接跳出不执行目标方法
4.所有拦截器都返回true,才执行目标方法
5.倒序执行所有拦截器的postHandle()方法
6.前面的步骤有任何异常都会直接倒序触发afterCompletion()
7.页面成功渲染完成以后,也会倒序触发afterCompletion()
  • 文件上传-单文件与多文件上传的使用
    • 页面代码/static/forms/form_layouts.html
  • 控制层代码
@Slf4j
@Controller
public class FormController {

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

    /**
     * MultipartFile 自动封装上传过来的文件
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg")MultipartFile headerImg,  //单文件上传
                         @RequestPart("photos") MultipartFile[] photos      //多文件上传
    ) throws IOException {
        log.info("上传的信息:email={}, username={}, headerImg={}, photos={}",email, username, headerImg.getSize(), photos.length);
        if(!headerImg.isEmpty()){
            //保存到文件服务器,OSS服务器
            String originalFilename = headerImg.getOriginalFilename();
            headerImg.transferTo(new File("D:\\aspring\\springboot_project\\boot-05-web-admin\\src\\main\\resources\\static\\upload\\"+originalFilename));
        }
        if(photos.length>0){
            for (MultipartFile photo: photos
                 ) {
                if(!photo.isEmpty()){
                    String originalFilename = photo.getOriginalFilename();
                    photo.transferTo(new File("D:\\aspring\\springboot_project\\boot-05-web-admin\\src\\main\\resources\\static\\upload\\"+originalFilename));
                }
            }
        }
        return "index";
    }
}
  • 文件上传相关配置项
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartProperties
  • 文件大小相关的配置项
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
  • 数据访问-数据库场景的自动配置分析与整合测试
    • 导入JDBC依赖

    org.springframework.boot
    spring-boot-starter-jdbc

  • 导入数据库驱动包(以Mysql为例)
默认版本为: 8.0.22

 
    mysql
    mysql-connector-java
    


如需修改版本:
    1.直接依赖引入具体版本(maven的就近依赖原则)
    2.重新声明版本(maven的属性的就近优先原则)
    

    1.8
    5.1.49

  • 相关数据源配置类
DataSourceAutoConfiguration:数据源的自动配置
    修改数据源相关的配置:spring.datasource
    数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    底层配置好的连接池是:HikarDataSource
DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置
JdbcTemplateAutoConfiguration:JdbcTemplate的自动配置,可以对数据库进行CRUD
    可以修改前缀为spring.jdbc的配置项来修改JdbcTemplate
    @Bean @Primary JdbcTemplate:Spring容器中有这个JdbcTemplate组件,使用@Autowored
JndiDataSourceAutoConfiguration:JNDI的自动配置
XADataSourceAutoConfiguration:分布式事务相关的
  • yaml数据库配置项
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/java_study
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  • 单元测试数据源
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;

@Slf4j
@SpringBootTest
class Boot05Web01ApplicationTests {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads() {

        Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class);
        log.info("记录数为:{}", aLong);
    }

}
  • 数据访问-自定义方式整合druid数据源
    • Druid是数据库连接池,它能提供强大的监控和扩展功能
    • SpringBoot整合第三方技术的两种方式
      • 自定义
      • 找starter场景
  • 自定义方式
    • 添加依赖
 
    com.alibaba
    druid
    1.1.17

  • 配置Druid数据源
@Configuration
public class MyDataSourceConfig {

    // 默认的自动配置是判断容器中没有才会配@ConditionalOnMissionBean(DataSource.class)
    @ConfigurationProperties("spring.datasource")   //复用配置yaml文件的数据源配置
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }
 }
  • 配置Druid的监控页功能
Druid内置提供了一个StatViewServlet用于展示Druid的统计信息,这个StatViewServlet的用途包括:
    提供监控信息展示的html页面 http://localhost:8080/druid/sql.html
    提供监控信息的JSON API
Druid内置提供一个StatFilter,用于统计监控信息
WebStatFilter用于采集web-jdbc关联监控的数据,如SQL监控、URI监控
Druid提供了WallFilter,它是基于SQL语义分析来实现防御SQL注入攻击的。

@Configuration
public class MyDataSourceConfig {

    // 默认的自动配置是判断容器中没有才会配@ConditionalOnMissionBean(DataSource.class)
    @ConfigurationProperties("spring.datasource")   //复用配置yaml文件的数据源配置
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        // 加入监控和防火墙功能
        druidDataSource.setFilters("stat,wall");
        return druidDataSource;
    }

    /**
     * 配置druid的监控页功能
     */
    @Bean
    public ServletRegistrationBean statViewServlet(){
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
        // 监控页账号和密码:
        registrationBean.addInitParameter("loginUsername","admin");
        registrationBean.addInitParameter("loginPassword","123456");
        return registrationBean;
    }

    /**
     * WebStatFilter 用于采集web-jdbc关联监控的数据
     */
    @Bean
    public FilterRegistrationBean webStatFilter(){
        WebStatFilter webStatFilter = new WebStatFilter();
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
        filterRegistrationBean.addInitParameter("exclusions","*.js,*,jpg,*.png,*.css,*.icon,/druid/*");
        return filterRegistrationBean;
    }
}
    
  • 数据访问-druid数据源starter整合方式
    • 引入依赖
 
    com.alibaba
    druid-spring-boot-starter
    1.1.17

  • 分析自动配置
扩展配置项 spring.datasource.druid
自动配置类 DruidDataSourceAutoConfigure
DruidSpringAopConfiguration.class,监控SpringBean的,配置项:spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration.class,监控页的配置
spring.datasource.druid.stat-view-servlet默认开启
DruidWebStatFilterConfiguration.class, web监控配置
spring.datasource.druid.web-stat-filter默认开启
DruidFilterConfiguration.class所有Druid的filter的配置

private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";

  • yaml配置示例
spring:

  datasource:
    url: jdbc:mysql://localhost:3306/java_study
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      filters: stat,wall  #底层开启功能,stat(sql监控),wall(防火墙)
      stat-view-servlet:  #配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin123
        resetEnable: false
        
      web-stat-filter:  #监控web
        enabled: true
        url-pattern: /*
        exclusions: '*.js,*,jpg,*.png,*.css,*.icon,/druid/*'
      aop-patterns: com.amberli.boot.*  #监控SpringBean

      filter:
        stat: # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          log-slow-sql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

你可能感兴趣的:(JAVA,java,spring,spring,boot)