springboot学习

SpringBoot入门

1,SpringBoot简介

简化spring应用的开发约定大于配置

优点:1,快速创建独立运行的spring项目

​ 2,使用嵌入式的servlet容器,无需打包成war包

​ 3,starter自定依赖和版本控制

​ 4,大量的自动配置,简化开发

​ 5,无需XML配置,无代码生成

​ 6,生成环境的运行时监控

​ 7,与云计算天然集成

缺点:入门简单,深入难

是整个spring技术栈的一个大整合

JAVA EE的一站式 解决方案

2,微服务

微服务:架构风格

3,环境准备

Maven设置

IDEA设置

idea集成maven

4,springboot helloworld

1,用maven构建一个工程(jar)

2,导入springboot相关依赖

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>1.5.9.RELEASEversion>
    parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
    dependencies>

3,编写一个主程序,启动spring boot

package com.controller;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloworldStarter {

    public static void main(String[] args) {

        SpringApplication.run(HelloworldStarter.class,args);
    }
}

4,编写controller

package com.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HomeController {
    
    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        
        return "helloworld";
    }
}

5,运行主程序,进行测试

com.controller.HelloworldStarter

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.9.RELEASE)

2020-02-23 16:29:08.485  INFO 16920 --- [           main] com.controller.HelloworldStarter         : Starting HelloworldStarter on guojingwei with PID 16920 (C:\Users\Administrator\Desktop\springboothelloworld\target\classes started by 郭经伟 in C:\Users\Administrator\Desktop\springboothelloworld)
2020-02-23 16:29:08.535  INFO 16920 --- [           main] com.controller.HelloworldStarter         : No active profile set, falling back to default profiles: default
2020-02-23 16:29:08.800  INFO 16920 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3d3fcdb0: startup date [Sun Feb 23 16:29:08 CST 2020]; root of context hierarchy
2020-02-23 16:29:10.791  INFO 16920 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2020-02-23 16:29:10.819  INFO 16920 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-23 16:29:10.821  INFO 16920 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.23
2020-02-23 16:29:11.022  INFO 16920 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-02-23 16:29:11.022  INFO 16920 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2238 ms
2020-02-23 16:29:11.215  INFO 16920 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2020-02-23 16:29:11.221  INFO 16920 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2020-02-23 16:29:11.222  INFO 16920 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2020-02-23 16:29:11.222  INFO 16920 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2020-02-23 16:29:11.222  INFO 16920 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2020-02-23 16:29:11.622  INFO 16920 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3d3fcdb0: startup date [Sun Feb 23 16:29:08 CST 2020]; root of context hierarchy
2020-02-23 16:29:11.680  INFO 16920 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello]}" onto public java.lang.String com.controller.HomeController.hello()
2020-02-23 16:29:11.684  INFO 16920 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2020-02-23 16:29:11.684  INFO 16920 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2020-02-23 16:29:11.714  INFO 16920 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-02-23 16:29:11.714  INFO 16920 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-02-23 16:29:11.747  INFO 16920 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-02-23 16:29:11.985  INFO 16920 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-02-23 16:29:12.056  INFO 16920 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2020-02-23 16:29:12.060  INFO 16920 --- [           main] com.controller.HelloworldStarter         : Started HelloworldStarter in 4.126 seconds (JVM running for 9.16)

6,简化部署

pom.xml中添加一个插件

        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                plugin>
            plugins>
        build>

通过 maven packing成jar

通过java -jar 运行 springboot

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZPWZYvr-1584596734828)(https://t1.picb.cc/uploads/2020/02/28/kvtFKM.png)]

5,HelloWorld探究

1,pom文件

1,父项目

<parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>1.5.9.RELEASEversion>
 parent>

spring boot 导入依赖 默认不用导入依赖,当导入spring-boot-starter-parent里不含的jar包时

就得重新导入

2,导入依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
    dependencies>

spring-boot-starter:场景启动器:可以根据不同的场景导入不同的依赖

如spring-boot-starter-web/aop…

2,程序的主入口

package com.controller;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloworldStarter {

    public static void main(String[] args) {

        SpringApplication.run(HelloworldStarter.class,args);
    }
}

声明了@SpringBootApplication才说明是springboot的启动类

SpringApplication.run这个方法是启动springboot应用

@SpringBootApplication:主配置类

@Target(ElementType.TYPE) //描述注解作用的位置 这里说明只作用于类上
@Retention(RetentionPolicy.RUNTIME)//被保留的位置 运行时才触发这个注解
@Documented //是否被抽取到api文档
@Inherited //是否被子类继承
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    //默认这个@SpringBootApplication这个注解包括这几个注解

@SpringBootConfiguration

//标注在某个类上,说明是springboot 的配置类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented 
@Configuration //配置类 类似于spring中的xml配置文件 spring容器配置类
public @interface SpringBootConfiguration {

}

@Configuration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //标明是一个组件类,配置类也是一个组件类
public @interface Configuration {

@EnableAutoConfiguration:开启自动配置功能:以前我们需要配置的东西

spring boot 帮我们配置

springboot 开启自动配置的类 以前的ssm框架 都是需要我们在xml中或者java类中配置

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage 
@Import(EnableAutoConfigurationImportSelector.class)//导入了这个类
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage :自动配置包

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

@Import(AutoConfigurationPackages.Registrar.class)

@Import是spring的底层注解:这个注解的作用是给spring的ioc容器导入一个组件类

AutoConfigurationPackages.Registrar.class

重点:将主配置文件(@SpringBootApplication) 定义的类的所在包以及子包里的所有组件扫描到容器中

@Import(EnableAutoConfigurationImportSelector.class)

这个代表导入哪些组件的选择器

将所有组件以全类名的方式返回,这些组件就会被扫描到容器中

会给容器导入非常多的自动配置类,就是给当前场景导入所需要的所有组件类,并帮我们配置组件类

springboot学习_第1张图片

有了自动配置类,就免去了手动配置组件功能类

SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, ClassLoader classLoader)

spring boot 在启动的时候自动加载类路径下的META-INF/spring.factories中获取EnableAutoConfiguration

指定的值,将这些值当做自动配置类导入容器,自动配置类生效,帮我们自动配置

java EE的自动配置都在spring-boot-autoconfig包里

6,使用spring initializr创建spring boot项目

二,配置文件

1,配置文件

spring的配置文件是全局配置文件,

配置文件的名字是固定的

application.properties 配置K=V

application.yml

配置文件的作用:就是修改springboot自动配置的默认值,springboot底层帮我们自动配置好了

YAML:是一个标记语言,又不是一个标记语言

标记语言:

以前的配置文件大多数用…xml

…yml是以数据为中心的配置文件

YAML:配置例子

server:
  port: 8081

XML:使用标签来配置

2,YAML语法

1,基本语法

K:(空格)V:表示一对键值对(空格必须有)

以空格的缩进来控制层级关系:只要左对齐的一列数据,都是同一层级的

server:
	port:8081
	path:/hello

属性和值也是大小敏感

2,值的写法

字面量:普通的值(数字,字符串,布尔)

k: v 字符串默认不加双引号和单引号

“”:双引号;会转义字符串里面的特殊字符:

""单引号:不会转义特殊字符

对象,Map(属性和值)(键值对)

k: v在下一行写对象的属性和值

friends:
​		lastName: zhangsan
​		age: 20

行内写法

frients: {lastName: zs,age: 18}

数组(List Set)

用-值表示数组中的一个元素

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

3,配置文件值注入

配置文件设置值:YAML

server:
  port: 8081
person:
  name: 小明
  age: 18
  boss: false
  map: {a: b,c: x}
  list:
    - dog
    - cat
  date: 2020/2/23
  dog:
    name: kiki
    age:  12

配置文件设置值:properties

person.name=张三
person.age=17
person.date=2020/2/23
person.boss=true
person.dog.name=小狗
person.dog.age=3
person.list=a,b,c
person.map.k1=v1
person.map.k2=v2

组件类和配置文件中的属性值进行绑定

获取配置文件的值

/**
 * FileName: Person
 * Author:   郭经伟
 * Date:     2020/2/23 21:13
 * Description:
 * History:
 *           
package com.gjw.springbootinitializr.entity;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 将配置文件中每一个属性的值都映射到这个组件中
 * @ConfigurationProperties(prefix = "person") 将配置文件中以person为前缀的属性值和这个组件绑定
 * 只有当前类为容器中的组件才能生效
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String name;

    private Integer age;

    private Boolean boss;

    private Map<String,Object> map;

    private List<String> list;

    private Date date;

    private Dog dog; 
}

@value获取值和@configurationProperties获取值比较

@value @configurationProperties
功能 一个一个指定 批量注入配置文件中的值
松散绑定 不支持 支持
SpEL 支持 不支持
JSR303数据校验 不支持 支持
复杂类型的注入 不支持 支持

properties和yml都可以获取属性值

数据校验

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {

    @Value("${person.name}")
    private String name;
    @Value("#{10*10}")
    private Integer age;
    @NotNull
    private Boolean boss;
    @Email
    private Map<String,Object> map;

    private List<String> list;

    private Date date;

    private Dog dog;

4,@propertySource和@ImportResource的区别

@propertySource:加载指定配置文件

@PropertySource("classpath:person.properties")
@Component
@Validated
public class Person {

    @Value("${person.name}")
    private String name;
    @Value("#{10*10}")
    private Integer age;
//    @NotNull
    private Boolean boss;
//    @Email
    private Map<String,Object> map;

    private List<String> list;

    private Date date;

    private Dog dog;

@ImportResource:导入spring的配置文件使其生效

需要定义在配置类上

默认spring的配置文件,不导入里面的组件不生效

@ImportResource("classpath:bean.xml")
@SpringBootApplication
public class SpringbootInitializrApplication {

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

}

spring boot 不推荐使用配置文件添加组件的方式




    

spring boot 推荐全注解的方式

配置文件(xml)===配置类

@Configuration
public class MyAppConfig {

    @Bean
    public HelloService helloService2(){

        return new HelloService();
    }
}

单元测试

@RunWith(SpringRunner.class)
@org.springframework.boot.test.context.SpringBootTest
public class SpringBootTest {

    @Autowired
    private Person person;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private HelloService helloService;
    @Test
    public void test1(){

        System.out.println(person);
    }

    @Test
    public void test2(){

        System.out.println(applicationContext.containsBean("helloService2"));
    }
}

5,配置文件占位符

1,随机数

${random.uuid}${random.value}${random.int} ${random.long}

2,占位符获取之前配置的值,如果没有可以用:指定默认值

person.name=张三${random.uuid}
person.age=17
person.date=2020/2/23
person.boss=true
person.dog.name=小狗${person.hello:hello}
person.dog.age=3
person.list=a,b,c
person.map.k1=v1
person.map.k2=v2

6,Profile文件

profile文件是Spring对不同环境(开发,发布,测试)提供不同配置功能的支持

1,多profile文件形式

​ 格式:application-{profile}.properties

如:application-dev.properties application-prod.properties

2,yml支持多文档块的方式

server:
  port: 8081
spring:
  profiles:
    active: dev

---

server:
  port: 8084
spring:
  profiles: dev
---
server:
  port: 8085
spring:
  profiles: prod

3,激活指定profile

配置文件激活profile

spring.profiles.active=dev

命令行激活profile

java -jar xxx.jar --spring.profiles.active=prod

虚拟机激活profile

Dspring.profiles.active=dev

7,配置文件加载的位置

spring boot 会加载以下位置的application.properties或者application.yml作为默认配置文件

-…/file/config

-…/file/

-classpath:/config/

-classpath:/

上面的优先级是按照高到低的方式,高的优先覆盖低的配置内容 ,互补配置

我们也可以通过配置spring.config.location来配置

8,外部加载配置文件顺序

SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置

  1. 命令行参数

    所有的配置都可以在命令行上进行指定

    java -jar xxx.jar --server.port=8087  --server.context-path=/abc
    
    1. 多个配置用空格分开; --配置项=值
    2. 来自java:comp/env的JNDI属性
    3. Java系统属性(System.getProperties())
    4. 操作系统环境变量
    5. RandomValuePropertySource配置的random.*属性值

    由jar包外向jar包内进行寻找;

    再来加载不带profile

    1. jar包外部的application.properties或application.yml(不带spring.profile)配置文件
    2. **jar包内部的application.propertiesapplication.yml(不带spring.profile)配置文件

    优先加载带profile

    1. **jar包外部的application-{profile}.propertiesapplication.yml(带spring.profile)配置文件
    2. **jar包内部的application-{profile}.propertiesapplication.yml(带spring.profile)配置文件
    3. @Configuration注解类上的@PropertySource
    4. 通过SpringApplication.setDefaultProperties指定的默认属性

    所有支持的配置加载来源:

    参考官方文档

三,自动配置原理

配置文件到底能写什么?怎么写?自动配置原理;

配置文件能配置的属性参照

SpringBoot启动的时候加载主配置类,开启自动配置的功能

@SpringBootApplication
    @EnableAutoConfiguration

@EnableAutoConfiguration的作用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

利用AutoConfigurationImportSelector.class给容器导入一些组件

getAutoConfigurationEntry方法中

protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);//获取候选的配置类(通过全类名返回)

getCandidateConfigurations方法(获取候选配置类)

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());//这个方法获取候选配置类
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,ClassLoader classLoader)

扫描所有jar包类路径下 META-INF/spring.factories,把扫描到的这些文件的内容包装成properties对象,从properties中获取到EnableAutoConfiguration.class(类名)对应的值,然后把它们添加在容器中

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
  • 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
  • 每一个自动配置类进行自动配置功能;

以HttpEncodingAutoConfiguration(http编码自动配置为案例)解释自动配置案例

package org.springframework.boot.autoconfigure.web.servlet;

......

//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(
    proxyBeanMethods = false
)

/**
 * 启动指定类的ConfigurationProperties功能;
 * 将配置文件中对应的值和HttpProperties绑定起来;
 * 并把HttpProperties加入到ioc容器中
 */
@EnableConfigurationProperties({HttpProperties.class})

/**
 * Spring底层@Conditional注解
 * 根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;
 * 判断当前应用是否是web应用,如果是,当前配置类生效
 */
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类
@ConditionalOnClass({CharacterEncodingFilter.class})

/**
 * 判断配置文件中是否存在某个配置  spring.http.encoding.enabled;如果不存在,判断也是成立的
 * 即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
 */
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {

    //它已经和SpringBoot的配置文件映射了
    private final Encoding properties;

    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }

    @Bean     //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @ConditionalOnMissingBean    //判断容器有没有这个组件?(容器中没有才会添加这个组件)
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }

    ......
  1. 根据当前不同的条件判断,决定这个配置类是否生效
  2. 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类

@ConfigurationProperties(
    prefix = "spring.http"
)
public class HttpProperties {
    private boolean logRequestDetails;
    private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();

[总结]

  • SpringBoot启动会加载大量的自动配置类
  • 我们看我们需要的功能有没有SpringBoot默认写好的自动配置类
  • 再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
  • 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值

xxxxAutoConfigurartion:自动配置类;

xxxxProperties:封装配置文件中相关属性;

[@Conditional派生注解]

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

[查看那些自动配置类生效了]

自动配置类必须在一定的条件下才能生效;

我们怎么知道哪些自动配置类生效了;

我们可以通过配置文件启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

Positive matches :(自动配置类启用的)

Negative matches`:(没有启动,没有匹配成功的自动配置类)

四,springboot日志配置

JULJCLJboss-logginglogbacklog4jlog4j2slf4j

日志门面 (日志的抽象层) 日志实现 (日志的实现层)
JCL(Jakarta Commons Logging SLF4j(Simple Logging Facade for Java) jboss-loggi JUL(java.util.logging) Log4j Log4j2 Logback

左边选一个门面(抽象层)、右边来选一个实现;

例:SLF4j–>Logback

SpringBoot选用 SLF4jlogback

SLF4j使用

如何在系统中使用SLF4j :https://www.slf4j.org

以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;

给系统里面导入slf4j的jar和 logback的实现jar

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

日志

每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;

遗留问题

项目中依赖的框架可能使用不同的日志:

Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx

当项目是使用多种日志API时,可以统一适配到SLF4J,中间使用SLF4J或者第三方提供的日志适配器适配到SLF4J,SLF4J在底层用开发者想用的一个日志框架来进行日志系统的实现,从而达到了多种日志的统一实现。

统一日志

如何让系统中所有的日志都统一到slf4j

  1. 将系统中其他日志框架先排除出去;
  2. 用中间包来替换原有的日志框架(适配器的类名和包名与替换的被日志框架一致);
  3. 我们导入slf4j其他的实现

SpringBoot日志关系

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

SpringBoot使用它来做日志功能;

    <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-loggingartifactId>
        dependency>

底层依赖关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cWzs0U4l-1584596734833)(https://t1.picb.cc/uploads/2020/02/28/kvtxhF.png)]

总结:

  1. SpringBoot底层也是使用slf4j+logback的方式进行日志记录
  2. SpringBoot也把其他的日志都替换成了slf4j;
  3. 中间替换包?
@SuppressWarnings("rawtypes")
public abstract class LogFactory {

    static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";

    static LogFactory logFactory = new SLF4JLogFactory();Copy to clipboardErrorCopied

如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉?

Spring框架用的是commons-logging;

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-coreartifactId>
            <exclusions>
                <exclusion>
                    <groupId>commons-logginggroupId>
                    <artifactId>commons-loggingartifactId>
                exclusion>
            exclusions>
        dependency>

SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;

日志使用

默认配置

SpringBoot默认帮我们配置好了日志;

    //记录器
    Logger logger = LoggerFactory.getLogger(getClass());
    @Test
    public void contextLoads() {
        //System.out.println();

        //日志的级别;
        //由低到高   trace
        //可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
        logger.trace("这是trace日志...");
        logger.debug("这是debug日志...");
        //SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别
        logger.info("这是info日志...");
        logger.warn("这是warn日志...");
        logger.error("这是error日志...");


    }
    日志输出格式:
        %d表示日期时间,
        %thread表示线程名,
        %-5level:级别从左显示5个字符宽度
        %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 
        %msg:日志消息,
        %n是换行符
    -->
    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

SpringBoot修改日志的默认配置

# 也可以指定一个包路径 logging.level.com.xxx=error
logging.level.root=error


#logging.path=
# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
#logging.file=G:/springboot.log

# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
logging.path=/spring/log

#  在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n
logging.file logging.path Example Description
(none) (none) 只在控制台输出
指定文件名 (none) my.log 输出日志到my.log文件
(none) 指定目录 /var/log 输出到指定目录的 spring.log 文件中

指定配置

给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了

Logging System Customization
Logback logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy
Log4j2 log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging) logging.properties

logback.xml:直接就被日志框架识别了;

logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能

<springProfile name="staging">
    
      可以指定某段配置只在某个环境下生效
springProfile>

如:

<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        
        <layout class="ch.qos.logback.classic.PatternLayout">
            <springProfile name="dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%npattern>
            springProfile>
            <springProfile name="!dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%npattern>
            springProfile>
        layout>
    appender>

如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误

no applicable action for [springProfile]

切换日志框架

可以按照slf4j的日志适配图,进行相关的切换;

slf4j+log4j的方式;

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-webartifactId>
  <exclusions>
    <exclusion>
      <artifactId>logback-classicartifactId>
      <groupId>ch.qos.logbackgroupId>
    exclusion>
    <exclusion>
      <artifactId>log4j-over-slf4jartifactId>
      <groupId>org.slf4jgroupId>
    exclusion>
  exclusions>
dependency>

<dependency>
  <groupId>org.slf4jgroupId>
  <artifactId>slf4j-log4j12artifactId>
dependency>

切换为log4j2

   <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-loggingartifactId>
                    <groupId>org.springframework.bootgroupId>
                exclusion>
            exclusions>
        dependency>

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-log4j2artifactId>
dependency>

五,Spring Boot Web开发

1,创建springboot应用,选择我们需要导入的模块,如:redis,JPA…

2,springboot已经帮我们把这些场景配置好了,我们只需要在配置文件中配置少量的配置web就能跑起来了

3,就可以编写业务代码了

web自动配置规则

  1. WebMvcAutoConfiguration
  2. WebMvcProperties
  3. ViewResolver自动配置
  4. 静态资源自动映射
  5. Formatter与Converter自动配置
  6. HttpMessageConverter自动配置
  7. 静态首页
  8. favicon
  9. 错误处理

SpringBoot对静态资源的映射规则

WebMvcAutoConfiguration类的addResourceHandlers方法:(添加资源映射)

        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }

所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源

webjars:以jar包的方式引入静态资源;

WebJars官网

springboot学习_第2张图片

例如:添加jquery的maven依赖

    <dependency>
            <groupId>org.webjarsgroupId>
            <artifactId>jqueryartifactId>
            <version>3.4.1version>
        dependency>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpgE9Lvs-1584596734836)(https://t1.picb.cc/uploads/2020/02/28/kvtLfJ.png)]

访问地址对应就是:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

非WebJars,自己的web资源怎么访问

资源配置类

@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;

	/**
	 * Whether to enable default resource handling.
	 */
	private boolean addMappings = true;

	private final Chain chain = new Chain();

	private final Cache cache = new Cache();

	public String[] getStaticLocations() {
		return this.staticLocations;
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y7UEA7Ba-1584596734837)(https://t1.picb.cc/uploads/2020/02/28/kvtUA0.png)]

上图中添加的映射访问路径staticPathPattern值是/**,对应的资源文件夹就是上面配置类ResourceProperties中的CLASSPATH_RESOURCE_LOCATIONS数组中的文件夹:

数组中的值 在项目中的位置
classpath:/META-INF/resources/ src/main/resources/META-INF/resources/
classpath:/resources/ src/main/resources/resources/
classpath:/static/ src/main/resources/static/
classpath:/public/ src/main/resources/public/

localhost:8080/abc —> 去静态资源文件夹里面找abc

欢迎页映射

private Resource getIndexHtml(String location) {
			return this.resourceLoader.getResource(location + "index.html");
		}

		private boolean isReadable(Resource resource) {
			try {
				return resource.exists() && (resource.getURL() != null);
			}
			catch (Exception ex) {
				return false;
			}
		}

location就是静态资源路径,所以欢迎页的页面就是上面静态资源下的index.html,被/**映射,因此直接访问项目就是访问欢迎页!!!

网站图标映射

所有的 favicon.ico 都是在静态资源文件下找;

六,模板引擎

常见的模板引擎有JSPVelocityFreemarkerThymeleaf

SpringBoot推荐使用Thymeleaf;

Thymeleaf

<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
 dependency>

**如需切换thymeleaf版本:**springboot2.0以上直接导入上面的就是thymeleaf3.0版本了

<properties>

        <thymeleaf.version>X.X.X.RELEASEthymeleaf.version>

        
        
        <thymeleaf-layout-dialect.version>2.2.2thymeleaf-layout-dialect.version>

  properties>

Thymeleaf使用

package org.springframework.boot.autoconfigure.thymeleaf;

......

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";

默认只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染;

  1. 创建模板文件t1.html,并导入thymeleaf的名称空间

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>

body>
html>

2.使用模板


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>[[${title}]]title>
head>
<body>
<h1 th:text="${title}">h1>
<div th:text="${info}">这里的文本之后将会被覆盖div>
body>
html>

3.在controller中准备数据

@Controller
public class HelloT {

    @RequestMapping("/ht")
    public String ht(Model model) {
        model.addAttribute("title","hello Thymeleaf")
             .addAttribute("info","this is first thymeleaf test");
        return "t1";
    }
}

语法规则

th:text --> 改变当前元素里面的文本内容;

th:任意html属性 --> 来替换原生属性的值

标准表达式功能:

变量表达式:$ {…}

选择变量表达式:* {…}

消息表达式:#{…}

链接⽹址表达式:@ {…}

⽚段表达式:〜{…}

Thymeleaf语法

在自己使用Thymeleaf语法时,我非常希望有一篇很全的Thymeleaf语法的总结,但很可惜没有找到,总是零零散散。贴上官方链接(比较长):Thymeleaf官方说明文档 。现 全面总结 如下:

\1. 基本表达式

(1) 变量的表达式:${…}
用于访问 容器上下文环境 中的变量,例:

<span th:text="${information}"> servlet9大域对象

(2) 选择变量表达式:*{…}
选择表达式计算的是 选定的对象 (th:object对象属性绑定的对象)

<div th:object="${session. user}" >
Name: <span th: text=" *{firstName}" >Sebastian.

Surname: <span th: text=" *{lastName}" >Pepper.

Nationality: <span th: text=" *{nationality}" >Saturn.

div>

(3) 信息表达式:#{…}
一般用于 显示页面静态文本。将可能需要根据需求而整体变动的静态文本放在properties文件中,方便维护。通常与th:text属性一起使用。例如:
新建/WEB-INF/templates/home.html,段落:

<p th: text=" #{home. welcome}" >This text will not be show!

更多配置参考官方文档:https://www.thymeleaf.org/documentation.html

中文参考书册:https://www.lanzous.com/i7dzr2j

七,SpringMVC自动配置

Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。

以下是SpringBoot对SpringMVC的默认配置

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

自动配置在Spring的默认值之上添加了以下功能:

  • 包含ContentNegotiatingViewResolverBeanNameViewResolver。–> 视图解析器
  • 支持服务静态资源,包括对WebJars的支持(官方文档中有介绍)。–> 静态资源文件夹路径
  • 自动注册ConverterGenericConverterFormatterbeans。–> 转换器,格式化器
  • 支持HttpMessageConverters(官方文档中有介绍)。–> SpringMVC用来转换Http请求和响应的;User—Json;
  • 自动注册MessageCodesResolver(官方文档中有介绍)。–> 定义错误代码生成规则
  • 静态index.html支持。–> 静态首页访问
  • 定制Favicon支持(官方文档中有介绍)。–> 网站图标
  • 自动使用ConfigurableWebBindingInitializerbean(官方文档中有介绍)。

如果您想保留 Spring Boot MVC 的功能,并且需要添加其他 MVC 配置(拦截器,格式化程序和视图控制器等),可以添加自己的 WebMvcConfigurer 类型的 @Configuration 类,但不能@EnableWebMvc 注解。如果您想自定义 RequestMappingHandlerMappingRequestMappingHandlerAdapter 或者 ExceptionHandlerExceptionResolver 实例,可以声明一个 WebMvcRegistrationsAdapter 实例来提供这些组件。

如果您想完全掌控 Spring MVC,可以添加自定义注解了 @EnableWebMvc 的 @Configuration 配置类。

视图解析器

视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?)

  • 自动配置了ViewResolver
  • ContentNegotiatingViewResolver:组合所有的视图解析器的;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aYgIC3mC-1584596734838)(https://t1.picb.cc/uploads/2020/03/02/kvhCVK.png)]

视图解析器从哪里来的?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VNngJ7Wt-1584596734838)(https://t1.picb.cc/uploads/2020/03/02/kvheMj.png)]

所以我们可以自己给容器中添加一个视图解析器;自动的将其组合进来

@Component
public class MyViewResolver implements ViewResolver {

    @Override
    public View resolveViewName(String s, Locale locale) throws Exception {
        return null;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzLoFjge-1584596734839)(https://t1.picb.cc/uploads/2020/03/02/kvhlPc.png)]

转换器、格式化器

  • Converter:转换器; public String hello(User user):类型转换使用Converter(表单数据转为user)
  • Formatter 格式化器; 2017.12.17===Date;
        @Bean
        //在配置文件中配置日期格式化的规则
        @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
        public Formatter<Date> dateFormatter() {
            return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
        }

自己添加的格式化器转换器,我们只需要放在容器中即可

HttpMessageConverters

  • HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json;
  • HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter;

自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)

MessageCodesResolver

我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)

扩展SpringMVC

以前的配置文件中的配置

<mvc:view-controller path="/hello" view-name="success"/>

现在,编写一个配置类(@Configuration),是WebMvcConfigurer类型;不能标注@EnableWebMvc

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hi").setViewName("success");
    }
}

访问:http://localhost:8080/hi

原理:

我们知道WebMvcAutoConfiguration是SpringMVC的自动配置类

下面这个类是WebMvcAutoConfiguration中的一个内部类

springboot学习_第3张图片

看一下@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})中的这个类,

这个类依旧是WebMvcAutoConfiguration中的一个内部类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vpLvn8O7-1584596734841)(https://t1.picb.cc/uploads/2020/03/02/kvhIUG.png)]

重点看一下这个类继承的父类DelegatingWebMvcConfiguration

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    public DelegatingWebMvcConfiguration() {
    }

    //自动注入,从容器中获取所有的WebMvcConfigurer
    @Autowired(
        required = false
    )
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }

    }

    ......

    /**
     * 查看其中一个方法
      * this.configurers:也是WebMvcConfigurer接口的一个实现类
      * 看一下调用的configureViewResolvers方法 ↓
      */
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        this.configurers.configureViewResolvers(registry);
    }Copy to clipboardErrorCopied
    public void configureViewResolvers(ViewResolverRegistry registry) {
        Iterator var2 = this.delegates.iterator();

        while(var2.hasNext()) {
            WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
            //将所有的WebMvcConfigurer相关配置都来一起调用;  
            delegate.configureViewResolvers(registry);
        }

    }

容器中所有的WebMvcConfigurer都会一起起作用;

我们的配置类也会被调用;

效果:SpringMVC的自动配置和我们的扩展配置都会起作用;

springboot学习_第4张图片

全面接管SpringMVC

SpringBoot对SpringMVC的自动配置不需要了,所有都是由我们自己来配置;所有的SpringMVC的自动配置都失效了

我们只需要在配置类中添加@EnableWebMvc即可;

@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer

原理:

为什么@EnableWebMvc自动配置就失效了;

我们看一下EnableWebMvc注解类

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

重点在于@Import({DelegatingWebMvcConfiguration.class})

DelegatingWebMvcConfigurationWebMvcConfigurationSupport的子类

我们再来看一下springmvc的自动配置类WebMvcAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})

//重点是这个注解,只有当容器中没有这个类型组件的时候该配置类才会生效
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration 
  1. @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
  2. 导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;

如何修改SpringBoot的默认配置

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;

  • 在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
  • 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置

八,RestfulCRUD

默认访问index.html(需要配置模板引擎 如thymeleaf)

//template文件夹下的文件默认是不能直接访问的,所有需要添加视图映射

//spring boot 2.0thymeleaf默认3.0

//扩展springmvc
//springboot2.0只需要实现WebMvcConfigurer这个接口的默认方法
//因为这个接口的方法都是default方法,所有直接覆盖想实现的方法就行了
@Configuration
public class WebMvcConfiger implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
    }
    
}

i18n国际化

编写国际化配置文件,抽取页面需要显示的国际化数据

SpringBoot已经帮我们写好了管理国际化资源文件的组件

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnMissingBean(
    name = {"messageSource"},
    search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(
        prefix = "spring.messages"
    )
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }

        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }

        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }

        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }

创建i18n文件夹存放配置文件,文件名格式为基础名(login)+语言代码(zh)+国家代码(CN)

springboot学习_第5张图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKysBj3G-1584596734844)(https://t1.picb.cc/uploads/2020/02/28/kvtDJd.png)]

在配置文件中添加国际化文件的位置和基础名

spring.messages.basename=i18n.login

如果配置文件中没有配置基础名,就在类路径下找基础名为message的配置文件

将页面文字改为获取国际化配置,格式#{key}

<form class="form-signin" action="dashboard.html">
			<img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal" th:text="#{tip}">Please sign inh1>
			<label class="sr-only">Usernamelabel>
			<input type="text" class="form-control" placeholder="Username" th:placeholder="#{username}" required="" autofocus="">
			<label class="sr-only">Passwordlabel>
			<input type="password" class="form-control" placeholder="Password" th:placeholder="#{password}" required="">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me">[[#{remember}]]
        label>
			div>
			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{btn}">Sign inbutton>
			<p class="mt-5 mb-3 text-muted">© 2017-2018p>
			<a class="btn btn-sm">中文a>
			<a class="btn btn-sm">Englisha>
		form>

原理

国际化Locale(区域信息对象);

LocaleResolver(获取区域信息对象的组件);

在springmvc配置类WebMvcAutoConfiguration中注册了该组件

  @Bean
        /**
          *前提是容器中不存在这个组件,
      *所以使用自己的对象就要配置@Bean让这个条件不成立(实现LocaleResolver 即可)
      */
        @ConditionalOnMissingBean

        /**
          * 如果在application.properties中有配置国际化就用配置文件的
          * 没有配置就用AcceptHeaderLocaleResolver 默认request中获取
          */
        @ConditionalOnProperty(
            prefix = "spring.mvc",
            name = {"locale"}
        )
        public LocaleResolver localeResolver() {
            if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            } else {
                AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
                localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
                return localeResolver;
            }
        }

默认的就是根据请求头带来的区域信息获取Locale进行国际化

  public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = this.getDefaultLocale();
        if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;
        } else {
            Locale requestLocale = request.getLocale();
            List<Locale> supportedLocales = this.getSupportedLocales();
            if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
                Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
                if (supportedLocale != null) {
                    return supportedLocale;
                } else {
                    return defaultLocale != null ? defaultLocale : requestLocale;
                }
            } else {
                return requestLocale;
            }
        }
    }

点击切换语言

实现点击连接切换语言,而不是更改浏览器

  • 修改页面,点击连接携带语言参数

                <a class="btn btn-sm" href="?l=zh_CN">中文a>
                <a class="btn btn-sm" href="?l=en_US">Englisha>
    
  • 自己实现区域信息解析器

    • public class MyLocaleResolver implements LocaleResolver {
      
          @Override
          public Locale resolveLocale(HttpServletRequest httpServletRequest) {
              //获取请求参数中的语言
              String language = httpServletRequest.getParameter("l");
              //没带区域信息参数就用系统默认的
              Locale locale = Locale.getDefault();
              if (!StringUtils.isEmpty(language)) {
                  //提交的参数是zh_CN (语言代码_国家代码)
                  String[] s = language.split("_");
      
                  locale = new Locale(s[0], s[1]);
      
              }
      
              return locale;
          }
      
          @Override
          public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
      
          }
      }
      
    • 在配置类中将其注册到容器中

      @Configuration
      public class MyMvcConfig implements WebMvcConfigurer {
      
          @Override
          public void addViewControllers(ViewControllerRegistry registry) {
              registry.addViewController("/").setViewName("login");
              registry.addViewController("/index").setViewName("login");
              registry.addViewController("/index.html").setViewName("login");
          }
      
          @Bean
          public LocaleResolver localeResolver() {
              return new MyLocaleResolver();
          }
      
      }
      

    如果没有生效,请检查@Bean的那个方法的名称是否为localeResolver

实现登录功能

  1. 提供登录的controller

    @Controller
    public class UserController {
    
        @PostMapping("/user/login")
        public String login(@RequestParam String username, @RequestParam String password, HttpSession session, Model model) {
            if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
    
                //登录成功,把用户信息方法哦session中,防止表单重复提交,重定向到后台页面
                session.setAttribute("loginUser", username);
                return "redirect:/main.html";
            }
            //登录失败,返回到登录页面
            model.addAttribute("msg", "用户名或密码错误!");
            return "login";
        }
    }
    
  2. 修改表单提交地址,输入框添加name值与参数名称对应

            <form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
                <img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
                <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
                <label class="sr-only">Usernamelabel>
                <input type="text" name="username" class="form-control" th:placeholder="#{login.username}" placeholder="Username" autofocus="">
                <label class="sr-only">Passwordlabel>
                <input type="password" name="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
                <div class="checkbox mb-3">
                    <label>
              <input type="checkbox" value="remember-me"> [[#{login.remember}]]
            label>
                div>
                <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign inbutton>
                <p class="mt-5 mb-3 text-muted">© 2017-2018p>
                <a class="btn btn-sm" href="?l=zh_CN">中文a>
                <a class="btn btn-sm" href="?l=en_US">Englisha>
            form>
    
  3. 由于登录失败是转发,所以页面的静态资源请求路径会不正确,使用模板引擎语法替换

    <link  href="asserts/css/bootstrap.min.css" th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
    <!-- Custom styles for this template -->
    <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
    
  4. 添加登录失败页面显示

    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
    
    <p th:text="${msg}" th:if="${not #strings.isEmpty(msg)}" style="color: red">p>
    

修改页面立即生效

# 禁用缓存
spring.thymeleaf.cache=false
在页面修改完成以后按快捷键ctrl+f9,重新编译;

拦截器实现登录检查

  1. 实现拦截器

    package cn.clboy.hellospringbootweb.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @Author cloudlandboy
     * @Date 2019/11/17 上午11:44
     * @Since 1.0.0
     */
    
    public class LoginHandlerInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            Object loginUser = request.getSession().getAttribute("loginUser");
            if (loginUser == null) {
                //未登录,拦截,并转发到登录页面
                request.setAttribute("msg", "您还没有登录,请先登录!");
                request.getRequestDispatcher("/index").forward(request, response);
                return false;
            }
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        }
    }
    
  2. 注册拦截器

    package cn.clboy.hellospringbootweb.config;
    
    import cn.clboy.hellospringbootweb.interceptor.LoginHandlerInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.LocaleResolver;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * @Author cloudlandboy
     * @Date 2019/11/16 下午3:32
     * @Since 1.0.0
     */
    
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
        //定义不拦截路径
        private static final String[] excludePaths = {"/", "/index", "/index.html", "/user/login", "/asserts/**"};
    
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/").setViewName("login");
            registry.addViewController("/index").setViewName("login");
            registry.addViewController("/index.html").setViewName("login");
            registry.addViewController("/main.html").setViewName("dashboard");
        }
    
        @Bean
        public LocaleResolver localeResolver() {
            return new MyLocaleResolver();
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //添加不拦截的路径,SpringBoot已经做好了静态资源映射,所以我们不用管
            registry.addInterceptor(new LoginHandlerInterceptor())
                    .excludePathPatterns(excludePaths);
        }
    }
    

    在spring2.0+的版本中,只要用户自定义了拦截器,则静态资源会被拦截。但是在spring1.0+的版本中,是不会拦截静态资源的。

    因此,在使用spring2.0+时,配置拦截器之后,我们要把静态资源的路径加入到不拦截的路径之中。

    CRUD-员工列表

    基于restful风格

    实验功能 请求URI 请求方式
    查询所有员工 emps GET
    查询某个员工(来到修改页面) emp/1 GET
    来到添加页面 emp GET
    添加员工 emp POST
    来到修改页面(查出员工进行信息回显) emp/1 GET
    修改员工 emp PUT
    删除员工 emp/1 DELETE
  3. 为了页面结构清晰,在template文件夹下新建emp文件夹,将list.html移动到emp文件夹下

  4. 将dao层和实体层java代码复制到项目中daoentities

  5. 添加员工controller,实现查询员工列表的方法

    @Controller
    public class EmpController {
    
        @Autowired
        private EmployeeDao employeeDao;
    
        @GetMapping("/emps")
        public String emps(Model model) {
            Collection<Employee> empList = employeeDao.getAll();
            model.addAttribute("emps", empList);
            return "emp/list";
        }
    
    }
    
  6. 修改后台页面,更改左侧侧边栏,将customer改为员工列表,并修改请求路径

    <li class="nav-item">
        <a class="nav-link" th:href="@{/emps}">
            <svg .....>
                ......
            svg>
            员工列表
        a>
    li>
    
  7. 同样emp/list页面的左边侧边栏是和后台页面一模一样的,每个都要修改很麻烦,接下来,抽取公共片段

thymeleaf公共页面抽取

语法

~{templatename::selector}:模板名::选择器

~{templatename::fragmentname}:模板名::片段名

/*公共代码片段*/
<footer th:fragment="copy">
    © 2011 The Good Thymes Virtual Grocery
footer>

/*引用代码片段*/
<div th:insert="~{footer :: copy}">div>

/*(〜{...}包围是完全可选的,所以上⾯的代码 将等价于:*/
<div th:insert="footer :: copy">div>

三种引入公共片段的th属性:

  • th:insert:将公共片段整个插入到声明引入的元素中
  • th:replace:将声明引入的元素替换为公共片段
  • th:include:将被引入的片段的内容包含进这个标签中
/*公共片段*/
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
footer>

/*引入方式*/
<div th:insert="footer :: copy">div>
<div th:replace="footer :: copy">div>
<div th:include="footer :: copy">div>


/*效果*/
<div>
    <footer>
    © 2011 The Good Thymes Virtual Grocery
    footer>
div>

<footer>
© 2011 The Good Thymes Virtual Grocery
footer>

<div>
© 2011 The Good Thymes Virtual Grocery
div>

后台页面抽取

  1. 将后台主页中的顶部导航栏作为片段,在list页面引入

    dashboard.html:

            <nav th:fragment="topbar" class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
                <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
                <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
                <ul class="navbar-nav px-3">
                    <li class="nav-item text-nowrap">
                        <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
                    li>
                ul>
    nav>
    

    list.html:

    <body>
    
    <div th:replace="dashboard::topbar">div>
    
    ......
    
  2. 使用选择器的方式 抽取左侧边栏代码

    dashboard.html:

    <div class="container-fluid">
        <div class="row">
            

    list.html:

    <div class="container-fluid">
        <div class="row">
            <div th:replace="dashboard::#sidebar">div>
            ......
    

引入片段传递参数

dashboard.html中的公共代码块抽出为单独的html文件,放到commos文件夹下

在引入代码片段的时候可以传递参数,然后在sidebar代码片段模板中判断当前点击的链接

语法:

~{templatename::selector(变量名=值)}

/*或者在定义代码片段时,定义参数*/

topbar.html


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<nav th:fragment="topbar" class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company
        namea>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
        li>
    ul>
nav>
body>
html>

sidebar.html


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<nav id="sidebar" class="col-md-2 d-none d-md-block bg-light sidebar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a class="nav-link active" th:class="${currentURI}=='main.html'?'nav-link active':'nav-link'" th:href="@{/main.html}">
      .....
body>
html>

然后在dashboard.htmllist.html中引入

<body>
<div th:replace="commons/topbar::topbar">div>
<div class="container-fluid">
    <div class="row">
        <div th:replace="commons/sidebar::#sidebar(currentURI='main.html')">div>
        ......Copy to clipboardErrorCopied
<body>
<div th:replace="commons/topbar::topbar">div>

<div class="container-fluid">
    <div class="row">
        <div th:replace="commons/sidebar::#sidebar(currentURI='emps')">div>
  1. 显示员工数据,添加增删改按钮

            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
                <h2>
                    <button class="btn btn-sm btn-success">添加员工button>
                h2>
                <div class="table-responsive">
                    <table class="table table-striped table-sm">
                        <thead>
                        <tr>
                            <th>员工号th>
                            <th>姓名th>
                            <th>邮箱th>
                            <th>性别th>
                            <th>部门th>
                            <th>生日th>
                            <th>操作th>
                        tr>
                        thead>
                        <tbody>
                        <tr th:each="emp:${emps}">
                            <td th:text="${emp.id}">td>
                            <td th:text="${emp.lastName}">td>
                            <td th:text="${emp.email}">td>
                            <td th:text="${emp.gender}==1?'':''">td>
                            <td th:text="${emp.department.departmentName}">td>
                            <td th:text="${#dates.format(emp.birth,'yyyy-MM-dd')}">td>
                            <td>
                                <button class="btn btn-sm btn-primary">修改button>
                                <button class="btn btn-sm btn-danger">删除button>
                            td>
                        tr>
                        tbody>
                    table>
                div>
            main>
    

员工添加

  1. 创建员工添加页面add.html

    ......
    <body>
    <div th:replace="commons/topbar::topbar">div>
    
    <div class="container-fluid">
        <div class="row">
            <div th:replace="commons/sidebar::#sidebar(currentURI='emps')">div>
            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
                <form>
                    <div class="form-group">
                        <label>LastNamelabel>
                        <input name="lastName" type="text" class="form-control" placeholder="zhangsan">
                    div>
                    <div class="form-group">
                        <label>Emaillabel>
                        <input  name="email" type="email" class="form-control" placeholder="[email protected]">
                    div>
                    <div class="form-group">
                        <label>Genderlabel><br/>
                        <div class="form-check form-check-inline">
                            <input class="form-check-input" type="radio" name="gender" value="1">
                            <label class="form-check-label">label>
                        div>
                        <div class="form-check form-check-inline">
                            <input class="form-check-input" type="radio" name="gender" value="0">
                            <label class="form-check-label">label>
                        div>
                    div>
                    <div class="form-group">
                        <label>departmentlabel>
                        <select name="department.id" class="form-control">
                            <option th:each="dept:${departments}" th:text="${dept.departmentName}" th:value="${dept.id}">option>
                        select>
                    div>
                    <div class="form-group">
                        <label>Birthlabel>
                        <input name="birth" type="text" class="form-control" placeholder="zhangsan">
                    div>
                    <button type="submit" class="btn btn-primary">添加button>
                form>
            main>
        div>
    div>
    
  2. 点击链接跳转到添加页面

    <a href="/emp" th:href="@{/emp}" class="btn btn-sm btn-success">添加员工a>Copy to clipboardErrorCopied
    
  3. EmpController添加映射方法

        @Autowired
        private DepartmentDao departmentDao;
    
        @GetMapping("/emp")
        public String toAddPage(Model model) {
            //准备部门下拉框数据
            Collection<Department> departments = departmentDao.getDepartments();
            model.addAttribute("departments",departments);
            return "emp/add";
        }
    
  4. 修改页面遍历添加下拉选项

    <select class="form-control">
        <option th:each="dept:${departments}" th:text="${dept.departmentName}">option>
    select>
    
  5. 表单提交,添加员工

    <form th:action="@{/emp}" method="post">Copy to clipboardErrorCopied
    
        @PostMapping("/emp")
        public String add(Employee employee) {
            System.out.println(employee);
            //模拟添加到数据库
            employeeDao.save(employee);
            //添加成功重定向到列表页面
            return "redirect:/emps";
        }
    

日期格式修改

表单提交的日期格式必须是yyyy/MM/dd的格式,可以在配置文件中修改格式

spring.mvc.date-format=yyyy-MM-dd

员工修改

  1. 点击按钮跳转到编辑页面

    <a th:href="@{/emp/}+${emp.id}" class="btn btn-sm btn-primary">修改a>
    
  2. 添加编辑页面,表单的提交要为post方式,提供_method参数

    <body>
    <div th:replace="commons/topbar::topbar">div>
    
    <div class="container-fluid">
        <div class="row">
            <div th:replace="commons/sidebar::#sidebar(currentURI='emps')">div>
            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
                <form th:action="@{/emp}" method="post">
                    
                    <input type="hidden" name="id" th:value="${emp.id}">
                    
                    <input type="hidden" name="_method" value="put">
                    <div class="form-group">
                        <label>LastNamelabel>
                        <input name="lastName" th:value="${emp.lastName}" type="text" class="form-control" placeholder="zhangsan">
                    div>
                    <div class="form-group">
                        <label>Emaillabel>
                        <input  name="email" th:value="${emp.email}" type="email" class="form-control" placeholder="[email protected]">
                    div>
                    <div class="form-group">
                        <label>Genderlabel><br/>
                        <div class="form-check form-check-inline">
                            <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp.gender==1}">
                            <label class="form-check-label">label>
                        div>
                        <div class="form-check form-check-inline">
                            <input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp.gender==0}">
                            <label class="form-check-label">label>
                        div>
                    div>
                    <div class="form-group">
                        <label>departmentlabel>
                        <select name="department.id" class="form-control">
                            <option th:each="dept:${departments}" th:value="${dept.id}" th:selected="${dept.id}==${emp.department.id}" th:text="${dept.departmentName}">option>
                        select>
                    div>
                    <div class="form-group">
                        <label>Birthlabel>
                        <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${#dates.format(emp.birth,'yyyy-MM-dd')}">
                    div>
                    <button type="submit" class="btn btn-primary">添加button>
                form>
            main>
        div>
    div>
    
  3. Controller转发到编辑页面,回显员工信息

        @GetMapping("/emp/{id}")
        public String toEditPage(@PathVariable Integer id, Model model) {
            Employee employee = employeeDao.get(id);
            //准备部门下拉框数据
            Collection<Department> departments = departmentDao.getDepartments();
            model.addAttribute("emp", employee).addAttribute("departments", departments);
            return "emp/edit";
        }
    
  4. 提交表单修改员工信息

        @PutMapping("/emp")
        public String update(Employee employee) {
            employeeDao.save(employee);
            return "redirect:/emps";
        }
    

员工删除

  1. 点击删除提交发出delete请求

        @DeleteMapping("/emp/{id}")
        public String delete(@PathVariable String id){
            employeeDao.delete(id);
            return "redirect:/emps";
        }
    

如果提示不支持POST请求,在确保代码无误的情况下查看是否配置启动HiddenHttpMethodFilter

spring.mvc.hiddenmethod.filter.enabled=true

springboot学习_第6张图片

这个好像是2.0版本以后修改的

springboot学习_第7张图片

如果删除不掉,请修改EmployeeDao,把String转为Integer类型

    public void delete(String id) {
        employees.remove(Integer.parseInt(id));
    }

restful的不同提交方式

<form id="formbtn" method="post">
		<input type="hidden" name="_method" value="delete" >
    <input type="hidden" name="_method" value="put" >
form>
//get方式的提交,可以用默认表单或者超链接 a标签
//post form 表单 method="post"

九,Springboot默认的错误处理机制

当访问一个不存在页面时,或者程序抛出异常:SpringBoot

默认效果:

  • 浏览器会给我们自动给我们返回一个默认页面,注意看请求头

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCSgZuZd-1584596734848)(https://t1.picb.cc/uploads/2020/03/02/kvhXHW.png)]

  • 客户端会给我们返回一个json数据(postman)注意看请求头

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKT3FKNx-1584596734849)(https://t1.picb.cc/uploads/2020/03/02/kvhB0w.png)]

查看org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration源码,

这里是springboot错误处理的自动配置信息

主要给容器中注册了以下组件:

  • ErrorPageCustomizer 系统出现错误以后来到error请求进行处理;相当于(web.xml注册的错误页面规则)
  • BasicErrorController 处理/error请求
  • DefaultErrorViewResolver 默认的错误视图解析器
  • DefaultErrorAttributes 错误信息
  • defaultErrorView 默认错误视图

ErrorPageCustomizer

    @Bean
    public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
        return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
    }
    private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
        private final ServerProperties properties;
        private final DispatcherServletPath dispatcherServletPath;

        protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
            this.properties = properties;
            this.dispatcherServletPath = dispatcherServletPath;
        }

        //注册错误页面
        public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
            //getPath()获取到的是"/error",见下图
            ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
            errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
        }

        public int getOrder() {
            return 0;
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sPsWhZhJ-1584596734850)(https://t1.picb.cc/uploads/2020/03/02/kvhLIG.png)]

当请求出现错误后就会转发到/error

然后这个error请求就会被BasicErrorController处理;

BasicErrorController

    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorController.class},
        search = SearchStrategy.CURRENT
    )
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));
    }

处理/error请求

@Controller
/**
  * 使用配置文件中server.error.path配置
  * 如果server.error.path没有配置使用error.path
  * 如果error.path也没有配置就使用/error
  */
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3igv3EgU-1584596734850)(https://t1.picb.cc/uploads/2020/03/02/kvhUWy.png)]

这两个方法一个用于浏览器请求响应html页面,一个用于其他客户端请求响应json数据

处理浏览器请求的方法 中,modelAndView存储到哪个页面的页面地址和页面内容数据

看一下调用的resolveErrorView方法

    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        Iterator var5 = this.errorViewResolvers.iterator();

        ModelAndView modelAndView;
        do {
            if (!var5.hasNext()) {
                return null;
            }

            ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
            //从所有的ErrorViewResolver得到ModelAndView
            modelAndView = resolver.resolveErrorView(request, status, model);
        } while(modelAndView == null);

        return modelAndView;
    }

ErrorViewResolver从哪里来的呢?

已经在容器中注册了一个DefaultErrorViewResolver

DefaultErrorViewResolver

    @Configuration(
        proxyBeanMethods = false
    )
    static class DefaultErrorViewResolverConfiguration {
        private final ApplicationContext applicationContext;
        private final ResourceProperties resourceProperties;

        DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
            this.applicationContext = applicationContext;
            this.resourceProperties = resourceProperties;
        }

        //注册默认错误视图解析器
        @Bean
        @ConditionalOnBean({DispatcherServlet.class})
        @ConditionalOnMissingBean({ErrorViewResolver.class})
        DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
        }
    }

然后调用ErrorViewResolver的resolveErrorView()方法

    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        //把状态码和model传过去获取视图
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);

        //上面没有获取到视图就使用把状态吗替换再再找,以4开头的替换为4xx,5开头替换为5xx,见下文(如果定制错误响应)
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }

        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //viewName传过来的是状态码,例:/error/404
        String errorViewName = "error/" + viewName;
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        //模板引擎可以解析这个页面地址就用模板引擎解析
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

如果模板引擎不可用,就调用resolveResource方法获取视图

    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        //获取的是静态资源文件夹
        String[] var3 = this.resourceProperties.getStaticLocations();
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];

            try {
                Resource resource = this.applicationContext.getResource(location);
                //例:static/error.html
                resource = resource.createRelative(viewName + ".html");
                //存在则返回视图
                if (resource.exists()) {
                    return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
                }
            } catch (Exception var8) {
            }
        }

        return null;
    }

所以:

如何定制错误响应页面

  • 页面可以获取哪些数据

DefaultErrorAttributes

再看一下BasicErrorController的errorHtml方法

    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);

        //model的数据
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

看一下调用的this.getErrorAttributes()方法

    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }

再看 this.errorAttributes.getErrorAttributes()方法, this.errorAttributes是接口类型ErrorAttributes,实现类就一个DefaultErrorAttributes,看一下DefaultErrorAttributes的 getErrorAttributes()方法

    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }
  • timestamp:时间戳
  • status:状态码
  • error:错误提示
  • exception:异常对象
  • message:异常消息
  • errors:JSR303数据校验的错误都在这里

2.0以后默认是不显示exception的,需要在配置文件中开启

server.error.include-exception=true

原因:

springboot学习_第8张图片

在注册时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e14rXWkg-1584596734854)(https://t1.picb.cc/uploads/2020/03/02/kvhsRj.png)]

  • 没有模板引擎(模板引擎找不到这个错误页面),就会在静态资源文件夹下找;
  • 如果以上都没有找到错误页面,就是默认来到SpringBoot默认的错误提示页面;

defaultErrorView

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6xdj4mA-1584596734855)(https://t1.picb.cc/uploads/2020/03/02/kvhDkc.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-emC12c6j-1584596734856)(https://t1.picb.cc/uploads/2020/03/02/kvhEHK.png)]

如何定制JSON数据

springboot做了自适应效果,浏览器访问响应错误页面。客户端访问响应错误信息的json数据

  1. 第一种方法,定义全局异常处理器类注入到容器中,捕获到异常返回json格式的数据

    @ControllerAdvice
    public class MyExceptionHandler {
    
        @ResponseBody
        @ExceptionHandler(Exception.class)
        public Map<String, Object> handleException(Exception e) {
            Map<String, Object> map = new HashMap(2);
            map.put("code", "100011");
            map.put("msg", e.getMessage());
            return map;
        }
    }
    

    访问localhost:8080/hello?str=hi

    @RestController
    public class Hello {
    
        @RequestMapping("/hello")
        public String hello(String str) {
            if ("hi".equals(str)) {
                int i = 10 / 0;
            }
            return "hello world";
        }
    }
    

    这样的话,不管是浏览器访问还是客户端访问都是响应json数据,就没有了自适应效果

    1. 第二种方法,捕获到异常后转发到/error

      @ControllerAdvice
      public class MyExceptionHandler {
      
          @ExceptionHandler(Exception.class)
          public String handleException(Exception e) {
              Map<String, Object> map = new HashMap(2);
              map.put("code", "100011");
              map.put("msg", e.getMessage());
              return "forward:/error";
          }
      }
      

      访问localhost:8080/hello?str=hi,但这样异常被我们捕获然后转发,显示的状态码就是200,所以在转发之前还要设置一下状态码

          @ExceptionHandler(Exception.class)
          public String handleException(Exception e, HttpServletRequest request) {
              Map<String, Object> map = new HashMap(2);
              map.put("code", "100011");
              map.put("msg", e.getMessage());
      
              //设置状态码
              request.setAttribute("javax.servlet.error.status_code", 500);
              return "forward:/error";
          }
      

      但是设置的数据就没有用了,只能使用默认的

      由上面我们已经知道数据的来源是调用DefaultErrorAttributes的getErrorAttributes方法得到的,而这个DefaultErrorAttributes是在ErrorMvcAutoConfiguration配置类中注册的,并且注册之前会检查容器中是否已经拥有

          @Bean
          @ConditionalOnMissingBean(
              value = {ErrorAttributes.class},
              search = SearchStrategy.CURRENT
          )
          public DefaultErrorAttributes errorAttributes() {
              return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
          }
      

      所以我们可以只要实现ErrorAttributes接口或者继承DefaultErrorAttributes类,然后注册到容器中就行了

      @ControllerAdvice
      public class MyExceptionHandler {
      
          @ExceptionHandler(Exception.class)
          public String handleException(Exception e, HttpServletRequest request) {
              Map<String, Object> map = new HashMap(2);
              map.put("name", "hello");
              map.put("password", "123456");
      
              //设置状态码
              request.setAttribute("javax.servlet.error.status_code", 500);
      
              //把数据放到request域中
              request.setAttribute("ext", map);
              return "forward:/error";
          }
      }
      
      @Configuration
      public class MyMvcConfig implements WebMvcConfigurer {
      
          @Bean
          public DefaultErrorAttributes errorAttributes() {
              return new MyErrorAttributes();
          }
      
          class MyErrorAttributes extends DefaultErrorAttributes {
              @Override
              public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
                  //调用父类的方法获取默认的数据
                  Map<String, Object> map = new HashMap<>(super.getErrorAttributes(webRequest, includeStackTrace));
                  //从request域从获取到自定义数据
                  Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
                  map.putAll(ext);
                  return map;
              }
          }
      
          ......
      

十,配置嵌入式Servlet容器

如何定值和修改Servlet容器的相关配置

  1. 修改和server有关的配置

    server.port=8081
    server.context-path=/crud
    
    server.tomcat.uri-encoding=UTF-8
    
    //通用的Servlet容器设置
    server.xxx
    //Tomcat的设置
    server.tomcat.xxx
    
  2. 编写一个EmbeddedServletContainerCustomizer,2.0以后改为WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
        @Bean
        public WebServerFactoryCustomizer webServerFactoryCustomizer() {
            return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
                @Override
                public void customize(ConfigurableWebServerFactory factory) {
                    factory.setPort(8088);
                }
            };
        }
    ......
    

代码方式的配置会覆盖配置文件的配置

小Tips: 如果使用的是360极速浏览器就不要用8082端口了

注册servlet三大组件

由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件

Servlet

向容器中添加ServletRegistrationBean

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public ServletRegistrationBean myServlet() {
        ServletRegistrationBean register = new ServletRegistrationBean(new MyServlet(), "/myServlet");
        register.setLoadOnStartup(1);
        return register;
    }
    ......

MyServlet

public class MyServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("servlet初始化");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("this is MyServlet");
    }

}

Filter

向容器中添加FilterRegistrationBean

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {


    @Bean
    public FilterRegistrationBean myFilter() {
        FilterRegistrationBean register = new FilterRegistrationBean(new MyFilter());
        register.setUrlPatterns(Arrays.asList("/myServlet","/"));
        return register;
    }

    ......

MyFilter

public class MyFilter extends HttpFilter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        response.getWriter().write("请求被拦截......");
    }
}

Listener

向容器中注入ServletListenerRegistrationBean

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public ServletListenerRegistrationBean myServletContextListener(){
        return new ServletListenerRegistrationBean(new MyServletContextListener());
    }

    ......
public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("web容器   启动......");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("web容器   销毁......");
    }
}

替换为其他的嵌入式web服务器

SpringBoot默认使用的是Tomcat

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0jocVsj-1584596734857)(https://t1.picb.cc/uploads/2020/03/02/kvhK0N.png)]

如果要换成其他的就把Tomcat的依赖排除掉,然后引入其他嵌入式Servlet容器的以来,如JettyUndertow

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcatartifactId>
                    <groupId>org.springframework.bootgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <artifactId>spring-boot-starter-jettyartifactId>
            <groupId>org.springframework.bootgroupId>
        dependency>Copy to clipboardErrorCopied
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcatartifactId>
                    <groupId>org.springframework.bootgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <artifactId>spring-boot-starter-undertowartifactId>
            <groupId>org.springframework.bootgroupId>
        dependency>

原理

2.0以下是:EmbeddedServletContainerAutoConfiguration

ServletWebServerFactoryAutoConfiguration:嵌入式的web服务器自动配置

@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})

//---看这里---
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {

EmbeddedTomcat.class

    @Configuration(
        proxyBeanMethods = false
    )
    //判断当前是否引入了Tomcat依赖;
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
    /**
      *判断当前容器没有用户自己定义ServletWebServerFactory:嵌入式的web服务器工厂;
      *作用:创建嵌入式的web服务器
      */
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedTomcat {

ServletWebServerFactory:嵌入式的web服务器工厂

@FunctionalInterface
public interface ServletWebServerFactory {
    //获取嵌入式的Servlet容器
    WebServer getWebServer(ServletContextInitializer... initializers);
}

工厂实现类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0NmQ0LvM-1584596734858)(https://t1.picb.cc/uploads/2020/03/02/kvvZkF.png)]

WebServer:嵌入式的web服务器实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zvv97tPE-1584596734859)(https://t1.picb.cc/uploads/2020/03/02/kvvwrr.png)]

TomcatServletWebServerFactory为例,下面是TomcatServletWebServerFactory类

    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }

        //创建一个Tomcat
        Tomcat tomcat = new Tomcat();

        //配置Tomcat的基本环境,(tomcat的配置都是从本类获取的,tomcat.setXXX)
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);

        //将配置好的Tomcat传入进去,返回一个WebServer;并且启动Tomcat服务器
        return this.getTomcatWebServer(tomcat);
    }

我们对嵌入式容器的配置修改是怎么生效的?

配置修改原理

springboot学习_第9张图片

BeanPostProcessorsRegistrar:后置处理器注册器(也是给容器注入一些组件)

    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        private ConfigurableListableBeanFactory beanFactory;

        public BeanPostProcessorsRegistrar() {...}

        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {...}

        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if (this.beanFactory != null) {
                //注册了下面两个组件
                this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class);
                this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class);
            }
        }

        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {...}
    }Copy to clipboardErrorCopied
webServerFactoryCustomizerBeanPostProcessor
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {

    ......

    //在Bean初始化之前
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //判断添加的Bean是不是WebServerFactory类型的
        if (bean instanceof WebServerFactory) {
            this.postProcessBeforeInitialization((WebServerFactory)bean);
        }

        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        //获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
        ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
            customizer.customize(webServerFactory);
        });
    }

关于配置文件是如何设置的,参考EmbeddedWebServerFactoryCustomizerAutoConfiguration类,最后还是使用上面的方便

总结:

  1. SpringBoot根据导入的依赖情况,给容器中添加相应的XXXServletWebServerFactory

  2. 容器中某个组件要创建对象就会惊动后置处理器 webServerFactoryCustomizerBeanPostProcessor

    只要是嵌入式的是Servlet容器工厂,后置处理器就会工作;

  3. 后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法给工厂添加配置

嵌入式的servlet容器启动原理

  1. SpringBoot应用启动运行run方法

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNk9wO0x-1584596734863)(https://t1.picb.cc/uploads/2020/03/02/kvv6o0.png)]

  2. 153行,创建IOC容器对象,根据当前环境创建

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kjcWMMVG-1584596734865)(https://t1.picb.cc/uploads/2020/03/02/kvvic1.png)]

  3. 156行,刷新IOC容器

  4. 刷新IOC容器中272行,onRefresh();web的ioc容器重写了onRefresh方法,查看ServletWebServerApplicationContext类的onRefresh方法,在方法中调用了this.createWebServer();方法创建web容器

        protected void onRefresh() {
            super.onRefresh();
    
            try {
                this.createWebServer();
            } catch (Throwable var2) {
                throw new ApplicationContextException("Unable to start web server", var2);
            }
        }
    

    98行获取嵌入式的web容器工厂

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fAySgn9m-1584596734866)(https://t1.picb.cc/uploads/2020/03/02/kvvnNd.png)]

  5. 接下来就是上面的上面的相关配置流程,在创建web容器工厂时会触发webServerFactoryCustomizerBeanPostProcessor

  6. 然后99行使用容器工厂获取嵌入式的Servlet容器

  7. 嵌入式的Servlet容器创建对象并启动Servlet容器;

  8. 嵌入式的Servlet容器启动后,再将ioc容器中剩下没有创建出的对象获取出来(Controller,Service等);

使用外置的Servlet容器

  1. 将项目的打包方式改为war

  2. 编写一个类继承SpringBootServletInitializer,并重写configure方法,调用参数的sources方法springboot启动类传过去然后返回

    public class ServletInitializer extends SpringBootServletInitializer {
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(HelloSpringBootWebApplication.class);
        }
    }
    
  3. 然后把tomcat的依赖范围改为provided

        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-tomcatartifactId>
                <version>2.2.1.RELEASEversion>
                <scope>providedscope>
            dependency>
    
            ......
    
        dependencies>
    
  4. 最后就可以把项目打包成war放到tomcat中了

  5. 在IDEA中可以这样配置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9sptueyt-1584596734867)(https://t1.picb.cc/uploads/2020/03/02/kvveza.png)]

  6. 在创建项目时使用Spring Initializr创建选择打包方式为war,1,2,3步骤会自动配置

如果启动tomcat,报了一大堆错误,不妨把Tomcat改为更高的版本试试,如果你项目中的Filter是继承了HttpFilter,请使用tomcat9版本,9以下好像没有HttpFilter

原理

TODO 2019-11-20

  1. Servlet3.0标准ServletContainerInitializer扫描所有jar包中METAINF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载

  2. 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

  3. 在spring-web-xxx.jar包中的METAINF/services下有javax.servlet.ServletContainerInitializer这个文件

    文件中的类是:

    org.springframework.web.SpringServletContainerInitializerCopy to clipboardErrorCopied
    

    对应的类:

    @HandlesTypes({WebApplicationInitializer.class})
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
        public SpringServletContainerInitializer() {
        }
    
        public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
    
            ......
    
  4. SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例;

  5. 每一个WebApplicationInitializer都调用自己的onStartup方法;

  6. WebApplicationInitializer的实现类

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEEQtMNz-1584596734868)(https://t1.picb.cc/uploads/2020/03/02/kvvuiu.png)]

  7. 相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法

  8. SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

    protected WebApplicationContext createRootApplicationContext(
          ServletContext servletContext) {
        //1、创建SpringApplicationBuilder
       SpringApplicationBuilder builder = createSpringApplicationBuilder();
       StandardServletEnvironment environment = new StandardServletEnvironment();
       environment.initPropertySources(servletContext, null);
       builder.environment(environment);
       builder.main(getClass());
       ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
       if (parent != null) {
          this.logger.info("Root context already created (using as parent).");
          servletContext.setAttribute(
                WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
          builder.initializers(new ParentContextApplicationContextInitializer(parent));
       }
       builder.initializers(
             new ServletContextApplicationContextInitializer(servletContext));
       builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
    
        //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
       builder = configure(builder);
    
        //使用builder创建一个Spring应用
       SpringApplication application = builder.build();
       if (application.getSources().isEmpty() && AnnotationUtils
             .findAnnotation(getClass(), Configuration.class) != null) {
          application.getSources().add(getClass());
       }
       Assert.state(!application.getSources().isEmpty(),
             "No SpringApplication sources have been defined. Either override the "
                   + "configure method or add an @Configuration annotation");
       // Ensure error pages are registered
       if (this.registerErrorPageFilter) {
          application.getSources().add(ErrorPageFilterConfiguration.class);
       }
        //启动Spring应用
       return run(application);
    }
    
  9. Spring的应用就启动并且创建IOC容器

    public ConfigurableApplicationContext run(String... args) {
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       ConfigurableApplicationContext context = null;
       FailureAnalyzers analyzers = null;
       configureHeadlessProperty();
       SpringApplicationRunListeners listeners = getRunListeners(args);
       listeners.starting();
       try {
          ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
          ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
          Banner printedBanner = printBanner(environment);
          context = createApplicationContext();
          analyzers = new FailureAnalyzers(context);
          prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
    
           //刷新IOC容器
          refreshContext(context);
          afterRefresh(context, applicationArguments);
          listeners.finished(context, null);
          stopWatch.stop();
          if (this.logStartupInfo) {
             new StartupInfoLogger(this.mainApplicationClass)
                   .logStarted(getApplicationLog(), stopWatch);
          }
          return context;
       }
       catch (Throwable ex) {
          handleRunFailure(context, listeners, analyzers, ex);
          throw new IllegalStateException(ex);
       }
    }
    

十一,Docker

Docker是一个开源的应用容器引擎;轻量级的容器技术;(Go语言开发的)

Docker会将软件编译成一个镜像,然后在镜像中各个软件做好配置,将镜像发布出去,其他使用者就能够使用。

运行中的镜像称为容器,容器的启动是非常快的.

springboot学习_第10张图片

springboot学习_第11张图片

核心概念

Docker主机(Host):安装了Docker的主机,(直接安装在操作系统上)

Docker客户端(Client):连接docker主机进行操作

Docker仓库(Registry):用来保存各种打包好的软件镜像

Docker镜像:打包好的软件镜像保存在docker仓库中

Docker容器:镜像启动后的容器就称为一个实例,容器是独立运行的一个或者一组应用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3FWtlNk4-1584596734871)(https://t1.picb.cc/uploads/2020/03/04/k3Ws8L.png)]

使用Docker的步骤:

  1. 确认要安装docker的系统的linux内核高于3.10,低于3.10使用yum update更新

    uname -r
    
  2. 安装docker

    yum install docker
    
  3. 查看docker版本

    docker -v
    
  4. 查看docker状态

    service docker status
    
  5. 启动docker

    service docker start
    
  6. 停止docker

    service docker stop
    
  7. 设置docker开机自启

    systemctl enable docker
    

docker常用命令

镜像操作

操作 命令 说明
检索 docker search 关键字 eg:docker search redis 我们经常去docker hub上检索镜像的详细信息,如镜像的TAG。
拉取 docker pull 镜像名:tag :tag是可选的,tag表示标签,多为软件的版本,默认是latest
列表 docker images 查看所有本地镜像
删除 docker rmi image-id 删除指定的本地镜像

修改镜像源

修改 /etc/docker/daemon.json ,写入如下内容(如果文件不存在请新建该文件)

vim /etc/docker/daemon.json

# 内容:

{
"registry-mirrors":["http://hub-mirror.c.163.com"]
}
国内镜像源 地址
Docker 官方中国区 https://registry.docker-cn.com
网易 http://hub-mirror.c.163.com
中国科技大学 https://docker.mirrors.ustc.edu.cn
阿里云 https://pee6w651.mirror.aliyuncs.com

容器操作

以tomcat为例:

  1. 下载tomcat镜像

    docker pull tomcat
    

    如需选择具体版本,可以在https://hub.docker.com/搜索tomcat

    docker pull tomcat:7.0.96-jdk8-adoptopenjdk-hotspot
    
  2. 根据镜像启动容器,不加TAG默认latest,如果没有下载latest会先去下载再启动

    docker run --name mytomcat -d tomcat:latest
    

    --name:给容器起个名字

    -d:后台启动,不加就是前端启动,然后你就只能开一个新的窗口连接,不然就望着黑乎乎的窗口,啥也干不了,Ctrl+C即可退出,当然,容器也会关闭

  3. 查看运行中的容器

    docker ps
    
  4. 停止运行中的容器

    docker stop  容器的id
    
    # 或者
    
    docker stop  容器的名称,就是--name给起的哪个名字
    
  5. 查看所有的容器

    docker ps -a
    
  6. 启动容器

    docker start 容器id/名字
    
  7. 删除一个容器

    docker rm 容器id/名字
    
  8. 启动一个做了端口映射的tomcat

     docker run -d -p 8888:8080 tomcat
    

    -d:后台运行 -p: 将主机的端口映射到容器的一个端口 主机端口(8888):容器内部的端口(8080)

    外界通过主机的8888端口就可以访问到tomcat,前提是8888端口开放

  9. 关闭防火墙

    # 查看防火墙状态
    service firewalld status
    
    # 关闭防火墙
    service firewalld stop
    
  10. 查看容器的日志

    docker logs 容器id/名字
    

以mysql为例:

# 拉取镜像
docker pull mysql:5.7.28

# 运行mysql容器
 docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.28

--name mysql:容器的名字是mysql;

MYSQL_ROOT_PASSWORD=root:root用户的密码是root (必须指定)

连接容器内mysql

在使用 -d 参数时,容器启动后会进入后台。此时想要进入容器,可以通过以下指令进入:

  • docker attach
  • docker exec:推荐使用 docker exec 命令,因为此退出容器终端,不会导致容器的停止。
docker exec -it mysql bash

-i: 交互式操作。

-t: 终端。

mysql: 名为mysql的 镜像。

bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash,也可以用/bin/bash

连接上以后就可以正常使用mysql命令操作了

mysql -uroot -proot

直接使用端口映射更加方便

docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.28

十二,SpringBoot和数据库进行连接

依赖

	 <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.2.5.RELEASEversion>
        <relativePath/> 
    parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
    dependencies>

配置数据库连接信息

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.56.101:3306/springboot?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456

测试数据库是否连接上了

@SpringBootTest
class SpringbootjdbcApplicationTests {

    @Autowired
    private DataSource dataSource;
    @Test
    void contextLoads() throws SQLException {

        Connection connection=dataSource.getConnection();
        System.out.println(dataSource);
    }

}

springboot默认是使用com.zaxxer.hikari.HikariDataSource作为数据源,2.0以下是用org.apache.tomcat.jdbc.pool.DataSource作为数据源;

自动配置原理

数据源的相关配置都在DataSourceProperties里面;

jdbc的相关配置都在org.springframework.boot.autoconfigure.jdbc包下

参考DataSourceConfiguration,根据配置创建数据源,默认使用Hikari连接池;可以使用spring.datasource.type指定自定义的数据源类型;

  • DataSourceConfiguration类
abstract class DataSourceConfiguration {

	@SuppressWarnings("unchecked")
	protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
		return (T) properties.initializeDataSourceBuilder().type(type).build();
	}

	/**
	 * Tomcat Pool DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
			matchIfMissing = true)
	static class Tomcat {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.tomcat")
		org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
			org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
					org.apache.tomcat.jdbc.pool.DataSource.class);
			DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
			String validationQuery = databaseDriver.getValidationQuery();
			if (validationQuery != null) {
				dataSource.setTestOnBorrow(true);
				dataSource.setValidationQuery(validationQuery);
			}
			return dataSource;
		}

	}

	/**
	 * Hikari DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HikariDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
			matchIfMissing = true)
	static class Hikari {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.hikari")
		HikariDataSource dataSource(DataSourceProperties properties) {
			HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
			if (StringUtils.hasText(properties.getName())) {
				dataSource.setPoolName(properties.getName());
			}
			return dataSource;
		}

	}

	/**
	 * DBCP DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource",
			matchIfMissing = true)
	static class Dbcp2 {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
		org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
			return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
		}

	}

	/**
	 * Generic DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type")
	static class Generic {

		@Bean
		DataSource dataSource(DataSourceProperties properties) {
			return properties.initializeDataSourceBuilder().build();
		}

	}

}
  • SpringBoot默认配置的数据源
 org.apache.tomcat.jdbc.pool.DataSource
 HikariDataSource
 org.apache.commons.dbcp2.BasicDataSource
  • 也可以自定义数据源
static class Generic {

   @Bean
   DataSource dataSource(DataSourceProperties properties) {
      return properties.initializeDataSourceBuilder().build();
   }
//使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性
}

SpringBoot应用启动执行sql文件

DataSourceInitializer类
boolean createSchema() {
		List<Resource> scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
		if (!scripts.isEmpty()) {
			
			String username = this.properties.getSchemaUsername();
			String password = this.properties.getSchemaPassword();
			runScripts(scripts, username, password);
		}
		return !scripts.isEmpty();
	}
	private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
		if (resources != null) {
			return getResources(propertyName, resources, true);
		}
		String platform = this.properties.getPlatform(); //默认为all fallback默认schema
		List<String> fallbackResources = new ArrayList<>();
		//类路径下的 classpath:schema-all.sql
		fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
		//类路径下的 classpath:schema.sql
		fallbackResources.add("classpath*:" + fallback + ".sql");
		return getResources(propertyName, fallbackResources, false);
	}
	private void runScripts(List<Resource> resources, String username, String password) {
		if (resources.isEmpty()) {
			return;
		}
		ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
		populator.setContinueOnError(this.properties.isContinueOnError());
		populator.setSeparator(this.properties.getSeparator());
		if (this.properties.getSqlScriptEncoding() != null) {
			populator.setSqlScriptEncoding(this.properties.getSqlScriptEncoding().name());
		}
		for (Resource resource : resources) {
			populator.addScript(resource);
		}
		DataSource dataSource = this.dataSource;
		if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
			dataSource = DataSourceBuilder.create(this.properties.getClassLoader())
					.driverClassName(this.properties.determineDriverClassName()).url(this.properties.determineUrl())
					.username(username).password(password).build();
		}
		DatabasePopulatorUtils.execute(populator, dataSource);//运行sql文件
	}

initSchema()方法获取的是data-all.sqldata.sql

我们也可以在配置文件中配置sql文件的位置

spring:
  datasource:
    schema:
      - classpath:department.sql
      - 指定位置

测试:

在类路径下创建schema.sql,运行程序查看数据库是否存在该表

DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `departmentName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  • 启动springboot应用没有创建schema.sql
  • 原因:通过debug调试,在createSchema方法中,默认进入了下面这个选择语句最后返回false 没有执行 runScripts(scripts, username, password);所以isEnabled()方法默认返回false了
if (!isEnabled()) {
				logger.debug("Initialization disabled (not running DDL scripts)");
				return false;
			}
  • 解决我们得让这个方法返回true
private boolean isEnabled() {
   DataSourceInitializationMode mode = this.properties.getInitializationMode();
   if (mode == DataSourceInitializationMode.NEVER) {
      return false;
   }
   if (mode == DataSourceInitializationMode.EMBEDDED && !isEmbedded()) {
      return false;
   }
   return true;
}
this.properties.getInitializationMode();
public enum DataSourceInitializationMode {

	/**
	 * Always initialize the datasource.
	 */
	ALWAYS,

	/**
	 * Only initialize an embedded datasource.
	 */
	EMBEDDED,

	/**
	 * Do not initialize the datasource.
	 */
	NEVER

}
所以在配置文件中配置 和DataSourceProperties绑定
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.56.101:3306/springboot?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    initialization-mode: always

schema.sql:建表语句

data.sql:插入数据

当然混合使用也可以,愿意咋来咋来

注意:项目每次启动都会执行一次sql

配置高级数据源:Druid

选择哪个数据库连接池

  • DBCP2 是 Apache 基金会下的项目,是最早出现的数据库连接池 DBCP 的第二个版本。
  • C3P0 最早出现时是作为 Hibernate 框架的默认数据库连接池而进入市场。
  • Druid 是阿里巴巴公司开源的一款数据库连接池,其特点在于有丰富的附加功能。
  • HikariCP 相较而言比较新,它最近两年才出现,据称是速度最快的数据库连接池。最近更是被 Spring 设置为默认数据库连接池。

不选择 C3P0 的原因:

  • C3P0 的 Connection 是异步释放。这个特性会导致释放的在某些情况下 Connection 实际上 still in use ,并未真正释放掉,从而导致连接池中的 Connection 耗完,等待状况。
  • Hibernate 现在对所有数据库连接池一视同仁,官方不再指定『默认』数据库连接池。因此 C3P0 就失去了『官方』光环。

不选择 DBCP2 的原因:

  • 相较于 Druid 和 HikariCP,DBCP2 没有什么特色功能/卖点。基本上属于 能用,没毛病 的情况,地位显得略有尴尬。

2.0以前

引入依赖:

<dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.21version>
        dependency>

  • Druid的配置文件
#durid
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.druid.url = jdbc:mysql://192.168.56.101:3306/springboot?useSSL=false&serverTimezone=UTC
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.datasource.druid.initialSize=5
spring.datasource.druid.minIdle=5
spring.datasource.druid.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.druid.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=false
spring.datasource.druid.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
spring.datasource.druid.useGlobalDataSourceStat=true

  1. 使用springboot默认通过反射自定义数据源
  • Debug发现能获取数据源,就是其他设置的属性获取不了
  1. 改用自己定义数据源

  2. 成功

  3. @Configuration
    public class DruidConfig {
        
        //加入DruidDatasource
    
        @ConfigurationProperties(prefix = "spring.datasource.druid")
        @Bean
        public DataSource druidDatasource(){
    
            return new DruidDataSource();
        }
    }
    
    

Druid实现监控

@Configuration
public class DruidConfig {

    //加入DruidDatasource

    @ConfigurationProperties(prefix = "spring.datasource.druid")
    @Bean
    public DataSource druidDatasource(){

        return new DruidDataSource();
    }

    //配置Druid监控
    //1,配置一个管理后台的Servlet
    //以前是在web.xml中配置
    //现在在配置类中配置
    @Bean
    @ConditionalOnMissingClass
    public ServletRegistrationBean statViewServlet(){

        ServletRegistrationBean bean=new ServletRegistrationBean(new StatViewServlet(),"/druid/*");

        Map<String,String> initParmater=new HashMap<>();
        initParmater.put("loginUsername","admin");
        initParmater.put("loginPassword","123456");
        initParmater.put("allow","127.0.0.1");//默认所有都允许登录
        initParmater.put("resetEnable","true");//允许HTML页面上的“Reset All”功能
        //拒绝谁访问initParmater.put("deny","");
        bean.setInitParameters(initParmater);
        return bean;
    }
    //2.配置一个web监控的filter

    @Bean
    @ConditionalOnMissingClass
    public FilterRegistrationBean webStatFileter(){

        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter());
        Map<String,String> initParams=new HashMap<>();
        initParams.put("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");//设置不拦截资源
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));

        return bean;

    }
}
  • 通过http://localhost:8080/druid/访问

2.0以后

引入依赖


<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>1.1.21version>
dependency>

配置文件

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://192.168.56.101:3306/springboot?useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 连接池配置
      # 配置初始化大小、最小、最大
      initial-size: 1
      min-idle: 1
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 3000
      validation-query: SELECT 1 FROM DUAL
      test-on-borrow: false
      test-on-return: false
      test-while-idle: true
      pool-prepared-statements: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      filters: stat,wall,slf4j
      # 配置web监控,默认配置也和下面相同(除用户名密码,enabled默认false外),其他可以不配
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: admin
        login-password: 123456
        allow: 127.0.0.1

十三,整合Mybatis

引入依赖

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.2.5.RELEASEversion>
    <relativePath/> 
parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jdbcartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.1.1version>
    dependency>

    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <scope>runtimescope>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintagegroupId>
                <artifactId>junit-vintage-engineartifactId>
            exclusion>
        exclusions>
    dependency>
dependencies>

配置Druid数据源

全注解的Mybaitis开发

配置映射器:@Mapper,和@MapperScan

@Repository
@Mapper
public interface DepartmentDao {

    @Select("select * from department")
    public List<Department> selectAll();

    @Select("select * from department where id = #{id}")
    public Department selectById(Integer id);

    @Options(useGeneratedKeys = true,keyProperty = "id")
    @Insert("insert into department (departmentName) values (#{departmentName})")
    public void save(Department department);

    @Update("update department set departmentName=#{departmentName} where id=#{id}")
    public void update(Department department);

    @Delete("delete from department where id =#{id}")
    public int delete(Integer id);
}

Mybatis配置

开启驼峰命名法

将数据库的字段名改为department_name

由于列表和属性名不一致,所以就没有封装进去,我们表中的列名和实体类属性名都是遵循驼峰命名规则的,可以开启mybatis的开启驼峰命名配置

法一:

在application.yml文件中配置

mybatis:
  configuration:
    map-underscore-to-camel-case: true

法二:

通过实现ConfigurationCustomizer接口的customizer方法就行了

@org.springframework.context.annotation.Configuration
public class MyBatisConfig {


    @Bean
    public ConfigurationCustomizer configurationCustomizer(){

        return new ConfigurationCustomizer() {
            @Override
            public void customize(Configuration configuration) {

                configuration.setMapUnderscoreToCamelCase(true);
            }
        };
    }
}

Mapper扫描

使用@mapper注解的类可以被扫描到容器中,但是每个Mapper都要加上这个注解就是一个繁琐的工作,能不能直接扫描某个包下的所有Mapper接口呢,当然可以,在springboot启动类上加上@MapperScan

@MapperScan("com.gjw.springbootmybatis.dao")
@SpringBootApplication
public class SpringbootMybatisApplication {

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

}

使用XML的方式配置Mybatis

  1. Mybatis全局配置文件


<configuration>
    <typeAliases>
        <package name="cn.gjw.springbootmybatis.entity"/>
    typeAliases>
configuration>
  1. 创建EmployeeDao接口
  2. 创建EmployeeDao映射文件


<mapper namespace="cn.gjw.springbootmybatis.mapper.EmployeeMapper">
    <select id="selectAll" resultType="employee">
        SELECT * FROM employee
    select>
    <insert id="save" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
       INSERT INTO employee(lastName,email,gender,d_id) VALUES (#{lastName},#{email},#{gender},#{d_id})
    insert>
mapper>
  1. 配置文件(application.yaml)中指定配置文件和映射文件的位置

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

你可能感兴趣的:(springboot学习)