SpringBoot3快速入门

 快速开始

    <parent>
<!--        所有的springboot项目都必须继承自spring-boot-starter-parent-->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.4</version>
        <relativePath/>
    </parent>

<!--        web开发场景的启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

添加打包插件

<!--            springboot的应用打包插件-->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

打包后获得下面的文件

 

打开该文件所在的文件夹,并通过命令行运行即可 

直接在 springboot3demo-0.0.1-SNAPSHOT.jar 相同目录下新建application.properties文件既可以设置相应的配置

直接在文件中修改端口

 应用分析

依赖管理机制

自动配置机制 

 

 

总结:导入场景启动器、触发spring-boot-autoconfigure这个包的自动配置生效 

常用注解

组件注解

@SpringBootApplication(scanBasePackages = "com.example")//自定义扫描包路径

@Bean("userhaha")导入第三方的组件,或者自己定义的类,默认名字是方法名,也可以手动定义,且都是单实例

@Scope("prototype") //通过该注解可以设置为多实例

@Configuration //configuration也是容器中常用的注解

@Import(FastsqlException.class) //导入第三方组件,这个方法获取的是类名是全类名com.alibaba.druid.FastsqlException

@Import(FastsqlException.class) //导入第三方组件,这个方法获取的是类名是全类名com.alibaba.druid.FastsqlException
@SpringBootApplication(scanBasePackages = "com.example")//自定义扫描包路径
public class Springboot3demoApplication {

    public static void main(String[] args) {

        ConfigurableApplicationContext ioc = SpringApplication.run(Springboot3demoApplication.class, args);

        String[] beanNamesForType = ioc.getBeanNamesForType(FastsqlException.class);
        for (String s : beanNamesForType) {
            System.out.println(s);//结果输出:user01
        }
        Object userhaha = ioc.getBean("userhaha");
        Object userhaha1 = ioc.getBean("userhaha");
        System.out.println(userhaha1 == userhaha);
    }
}
@Configuration //configuration也是容器中常用的注解
public class TestConfig {

    /**
     *除了通过@Component向容器中放我们需要的组件,我们也可以使用@Bean,
     * 默认是方法名,
     * 且都是单实例,
     * 我们可以在Bean中手动设置名字
     */
    @Bean("userhaha")
    @Scope("prototype") //通过该注解可以设置为多实例
    public User user01(){
        return new User();
    }


    /**
     * 导入第三方组件,同样可以使用@Bean
     */
//    @Bean
    public FastsqlException fastsqlException(){
        return new FastsqlException();
    }
}

条件注解

@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为

@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为

@ConditionOnBean:如果容器中存在这个组件,则触发指定行为

@ConditionOnMissionBean:如果容器中不存在这个Bean,则触发指定行为

@ConditionalOnClass(value = FastsqlException.class)//如果放在类级别,注解判断生效,整个配置才会生效
@Configuration
public class AppConfig {

    @ConditionalOnClass(value = FastsqlException.class)//如果放在方法上,只会单独对这个方法生效
    @Bean("haveFast")
    public User user(){
        return new User();
    }

    @ConditionalOnMissingClass(value = "com.alibaba.druid.FastsqlException")
    @Bean("noFast")
    public User user1(){
        return new User();
    }

    @ConditionalOnBean(User.class)
    @Bean("haveUser")
    public Cat cat(){
        return new Cat();
    }

    @ConditionalOnMissingBean(User.class)
    @Bean("noUser")
    public Cat cat1(){
        return new Cat();
    }
}

属性绑定

@ConfigurationProperties:声明组件的属性和配置文件哪些前缀开始进行绑定必须配合@Bean使用,或者使用@Component将其装入ioc容器内

@EnableConfigurationProperties:快速注册注解,相当于代替@Bean或者@Component,导入第三方的主键进行属性绑定;springboot默认只扫描自己主程序所在的包,如果导入第三方包,即使组件上有@ConfigurationProperties和@Component也没有用

方法一:使用@Bean导入组件:

@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "pig") //匹配前缀
public class Cat {
    private String name;
    private Integer age;

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//将其放入容器内,会被覆盖属性    
@Bean
public Cat cat1(){
    Cat cat = new Cat();
    cat.setName("佩奇1");
    cat.setAge(25);
    return cat;
}

方法二:使用@EnableConfigurationProperties

@ConfigurationProperties(prefix = "dog")//配置绑定属性的前缀
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
    private String name;
    private Integer age;
}
@Import(FastsqlException.class) //导入第三方组件,这个方法获取的是类名是全类名com.alibaba.druid.FastsqlException
@SpringBootApplication(scanBasePackages = "com.example")//自定义扫描包路径
@EnableConfigurationProperties(Dog.class)
public class Springboot3demoApplication {}

深入理解自动配置原理

 

 怎么学好SpringBoot

配置文件中复杂对象的表示 

使用application.properties进行属性绑定

@Component
@Data
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Data birtyDay;
    private Boolean like;
    private Child child;//嵌套对象
    private List<Dog> dogs;//嵌套数组
    private Map<String , Cat> cats;//嵌套map
}
person.name=张三
person.age=18
person.birthDay=2010/10/28
person.like=true
#嵌套对象
person.child.name=李四
person.child.age=22
person.child.birthDay=2018/10/20
person.child.text[0]=abc
person.child.text[1]=def
#嵌套列表
person.dogs[0].name=小黑
person.dogs[0].age=2
person.dogs[1].name=小白
person.dogs[1].age=3
#嵌套map
person.cats.c1.name=小蓝
person.cats.c1.age=13
person.cats.c2.name=小灰
person.cats.c2.age=23

使用yml文件进行绑定

person:
  name: 张三
  age: 14
  birthDay: 2010/10/10
  like: true
  child:
    name: 李四
    age: 20
    birtyDay: 2018/20/10
#    使用-表示列表
    text:
      - abc
      - def
  dogs:
    - name: 小黑
      age: 3
    - name: 小白
      age: 2
  cats:
#    使用该方式表示map
    c1:
      name: 小蓝
      age: 3

使用yml的细节

日志 

SpringBoot的默认日志配置

快速入门

配置控制台日志输出格式

#配置控制台日志显示格式
logging.pattern.console=%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n

手动向控制台输出信息:@Slf4j+log.info("要输出的信息")

@Slf4j
@RestController
public class demoController {

    @GetMapping("/hello")
    public String testDemo(){
        log.info("控制台输出");
        return "hello, SpringBoot 3!";
    }
}

日志级别

SpringBoot的默认日志级别为:INFO 

修改系统输出级别和指定包的输出级别

#修改系统输出的日志级别
logging.level.root=debug
#精确调整某个包下的日志级别
logging.level.com.example.springboot3demo.controller=debug

常用的几个输出级别

@Slf4j
@RestController
public class demoController {

    @GetMapping("/hello")
    public String testDemo(){
        log.trace("trace...");
        log.debug("debug...");
        log.info("默认的输出级别");
        log.warn("warn...");
        log.error("error");
        return "hello, SpringBoot 3!";
    }
}

日志分组

向上面给每一个包设置日志级别相当麻烦,我们可是设置一个日志组

#日志分组
logging.group.abc=com.example.springboot3demo.controller,com.example.springboot3demo.pojo
logging.level.abc=debug

文件输出

#指定日志文件的路径,日志文件的默认名是spring.log
logging.file.path=D:\\
#指定日志文件的名
#1.只写名字,就能生成当前项目同位置的demo.log
#2.写名字+路径:生成指定位置的指定文件
logging.file.name=D:\\demo.log

归档和切割

归档:将每天的日志放在一起

切割:日志文件超过一定大小,将其切割

logging.logback.rollingpolicy.max-file-size:效果是当日志文件超过大小后生成新的文件

${LOG_FILE}就是我们前面设置的文件名称%d{yyyy-MM-dd}表示日期%i表示今天的第几个文件

gz表示linux中的压缩文件 

自定日志系统

SpringBoot中默认使用的是logback,因此我们只需要在文件系统中新建log4j2-spring.xml文件,系统就可以利用里面的配置,而不一定在application.properties中设置 

假如我们要使用log4j2的场景那我们应该要怎么处理呢?

切换日志场景:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

在resources中直接定义log4j2-spring.xml文件系统就可以扫描到配置

WEB开发

默认效果 

使用RestController:如果是Bean的话会转为json数据,如果是String类型数据则直接返回 

WebMvcAutoConfiguration原理

生效条件

效果

WebMvcConfigurer接口 

 静态资源源码

由上面就可以知道,WebMvcConfigurer中的方法addResourceHandlers就是关于静态资源的源码

public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
                this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                    if (this.servletContext != null) {
                        ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                        registration.addResourceLocations(new Resource[]{resource});
                    }

                });
            }
        }

规则二:访问/**路径就去静态资源默认的四个位置找资源(这里的classpath相当于resources)

a.classpath:/META-INF/resources/

b.classpath:/resources/

c.classpath:/static/

d.classpath:/public/

规则三:静态资源都有缓存规则的设置

a.所有的缓存的设置,直接通过配置文件:spring.web

b.cachePeriod:缓存周期,多久不用找服务器要新的,默认没有

c.cacheControl:HTTP缓存控制

d.useLastModified:是否使用最后一次修改,配合HTTP Cache规则

配置式设置静态资源

代码式设置静态资源 

//@EnableWebMvc //禁用默认模式,我们一般不用这个实现手自一体
@Configuration
public class MyConfig implements WebMvcConfigurer {

    //代码式配置静态资源规则
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //保留以前的静态资源规则
        WebMvcConfigurer.super.addResourceHandlers(registry);

        //自己设置静态资源规则
        registry.addResourceHandler("/static/**")//当我们从网页访问static的时候
                .addResourceLocations("classpath:/a/", "classpath:/b/")//我们从下面两个路径下找资源
                .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));//最大缓存时间
    }
}

代码方式的第二种方式:(容器中只要有一个WebMvcConfigure组件,其配置就会生效)

@Configuration
public class MyConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                WebMvcConfigurer.super.addResourceHandlers(registry);

                //        //自己设置静态资源规则
                registry.addResourceHandler("/static/**")//当我们从网页访问static的时候
                        .addResourceLocations("classpath:/a/", "classpath:/b/")//我们从下面两个路径下找资源
                        .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));//最大缓存时间
            }
        };
    }
}

欢迎页规则

只要在上述的四个位置有index.html,我们系统一启动就会展示这个index.html 

Favicon规则

我们在静态资源路径上设置favicon.ico自然就会在网页上面显示这个图标

 HTTP缓存机制测试

 路径匹配

新版PathPatternParser

  • PathPatternParser在jmn 基测试下,有 6~8 倍吞吐量提升,降低 30%~40%空间分配率
  • PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
  • PathPatternParser"**"多段匹配的支持仅允许在模式末尾使用 
  • 不能匹配**在中间的情况,需要手动设置spring.mvc.pathmatch.matching-strategy=ant_path_matcher
//使用新版路径匹配规则PathPatternParser
@GetMapping("/a*/b?/{p1:[a-f]+}/**")
public void  hello(HttpServletRequest request, @PathVariable("p1") String path){
    log.info("路径变量:{}", path);
    String uri = request.getRequestURI();
    log.info("路径:{}", uri);
}

内容协商 

内容协商:对于同一个接口,我们要求不同的请求返回不同类型的数据,如:json,xml等 

默认支持把对象写为json。因为默认web场景导入了jackson处理json的包:jackson-core

当我们需要将对象以xml形式返回怎么处理呢?

添加依赖:

<!-- 返回xml文件 -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.15.2</version>
</dependency>

给对象添加注解@JacksonXmlRootElement

@Component
@Data
@ConfigurationProperties(prefix = "person")
@JacksonXmlRootElement //可以写出为xml
public class Person {
    private String name;
    private Integer age;
    private Data birtyDay;
    private Boolean like;
    private Child child;//嵌套对象
    private List<Dog> dogs;//嵌套数组
    private Map<String , Cat> cats;//嵌套map
}

创建接口

/**
 * RestController默认返回的是json数据
 * @return
 */
@GetMapping("/person")
 public Person person(){
    return person;
}

使用POSTMAN默认的方式返回返回json和xml

 通过请求参数的内容协商

添加配置

#开启基于请求参数的内容协商功能,默认参数为:format
spring.mvc.contentnegotiation.favor-parameter=true
#可以手动修改参数名为type
spring.mvc.contentnegotiation.parameter-name=type

自定义内容协商YAML

添加依赖 

<!-- 放回支持yaml格式 -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>2.15.2</version>
</dependency>

测试将对象转为yaml

@SpringBootTest
class Springboot3demoApplicationTests {

    @Autowired
    Person person;

    @Test
    void contextLoads() {
    }

    //测试将对象返回为yaml
    @Test
    public void testYaml() throws JsonProcessingException {
        //去掉上面的---
        YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        ObjectMapper mapper = new ObjectMapper(factory);

        String s = mapper.writeValueAsString(person);
        System.out.println(s);
        /**
         * 效果如下:
         * name: "张三"
         * age: 14
         * birtyDay: null
         * like: true
         * child:
         *   name: "李四"
         *   age: 20
         *   birthDay: null
         *   text:
         *   - "abc"
         *   - "def"
         * dogs:
         * - name: "小黑"
         *   age: 3
         * - name: "小白"
         *   age: 2
         * cats:
         *   c1:
         *     name: "小蓝"
         *     age: 3
         */
    }
}

新建自己的HttpMessageConvert将对象转为xml

public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private ObjectMapper objectMapper = null;//把对象转为yaml

    public MyYamlHttpMessageConverter(){
        //和配置文件中的text/yaml进行配对
        super(new MediaType("application", "yaml", Charset.forName("UTF-8")));
        //去掉上面的---
        YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        this.objectMapper = new ObjectMapper(factory);
    }
    @Override
    protected boolean supports(Class<?> clazz) {
        //只要是对象类型,不是基本数据类型
        return true;
    }

    @Override //RequestBody
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override//ResponseBody怎么把对象写出去
    protected void writeInternal(Object methodReturnValue, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //try-with写法自动关流
        try(OutputStream os = outputMessage.getBody()){
            this.objectMapper.writeValue(os, methodReturnValue);
        }
    }
}

在配置类中将自己的convert组装到MVCconfigure中去

//@EnableWebMvc //禁用默认模式,我们一般不用这个实现手自一体
@Configuration
public class MyConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                WebMvcConfigurer.super.addResourceHandlers(registry);

                //        //自己设置静态资源规则
                registry.addResourceHandler("/static/**")//当我们从网页访问static的时候
                        .addResourceLocations("classpath:/a/", "classpath:/b/")//我们从下面两个路径下找资源
                        .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));//最大缓存时间
            }

            @Override //配置一个能将对象转为yaml的messageConverter
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new MyYamlHttpMessageConverter());
            }
        };
    }
}

修改配置文件,新增加一种内容类型

#增加一种新的内容类型
spring.mvc.contentnegotiation.media-types.yaml=application/yaml

测试效果如下:

 

Thymeleaf 

快速入门

添加依赖

<!-- 默认效果thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>3.1.3</version>
</dependency>

添加controller

//@RestController表示的是前后端分离技术
@Controller //这里表示的是前后端不分离的技术
public class ThymeleafTest {
    @GetMapping("/well")
    public String hello(@RequestParam(value = "name") String name,
                        Model model){
        //模板的逻辑视图名
        //物理视图 = 前缀 + 逻辑视图名 + 后缀
        //真实地址 = classpath:/templates/welcome.html

        //将页面需要共享的数据保存在model
        model.addAttribute("msg", name);
        return "welcome";
    }
}

默认效果:1.所有的模板页面在classpath:/templates/下面找 2.找后缀为.html的页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thmeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>哈哈:<span th:text="${msg}"></span></h1>
</body>
</html>

核心语法

th:utext:替换标签的内容,不会转义html标签,显示为正真html该有的样式

th:任意html属性 动态替换任意属性的值

th:attr:任意属性指定

th:其他标签if

th:src="@{${imgUrl}}" 自动补全图片路径为/demo/static/1.png

字符串拼接 || <h1>哈哈:<span th:utext="|前缀:${text} :后缀|"></span></h1>

@Controller //这里表示的是前后端不分离的技术
public class ThymeleafTest {
    @GetMapping("/well")
    public String hello(@RequestParam(value = "name") String name,
                        Model model){
        //模板的逻辑视图名
        //物理视图 = 前缀 + 逻辑视图名 + 后缀
        //真实地址 = classpath:/templates/welcome.html

        //将页面需要共享的数据保存在model
        model.addAttribute("msg", name);

        //th:utext:替换标签的内容,不会转义html标签,显示为正真html该有的样式
        String text = "<span style='color:red'>"+name+"</span>";
        model.addAttribute("text",text);

        //th:任意html属性 动态替换任意属性的值
        String imgUrl = "/static/1.png";
        model.addAttribute("imgUrl", imgUrl);
        //th:attr:任意属性指定
        model.addAttribute("style","width: 400px");

        //th:其他标签if
        model.addAttribute("show", true);

        //th:src="@{${imgUrl}}" 自动补全图片路径为/demo/static/1.png

        //字符串拼接 || <h1>哈哈:<span th:utext="|前缀:${text} :后缀|"></span></h1>

        return "welcome";
    }
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thmeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>哈哈:<span th:text="${msg}"></span></h1>
<h1>哈哈:<span th:utext="${text}"></span></h1>
<br/>
<img th:src="@{${imgUrl}}" style="width:300px;"/>
<br/>
<img th:attr="src=@{${imgUrl}}, style=${style}"/>
<br/>
<img th:src="@{${imgUrl}}" style="width:300px;" th:if="${show}"/>
<br/>
<h1>哈哈:<span th:utext="|前缀:${text} :后缀|"></span></h1>
</body>
</html>

遍历

为我们的项目添加bootstrp的样式

我们找一个table的样式

 

 我们新增加页面list.html

<table class="table">
  <thead>
  <tr>
    <th scope="col">id</th>
    <th scope="col">name</th>
    <th scope="col">age</th>
    <th scope="col">email</th>
  </tr>
  </thead>
  <tbody>
  <tr th:each="user, stats: ${users}">
    <th scope="row" th:text="${user.id}">1</th>
    <td th:text="${user.name}">Mark</td>
    <td th:text=[[user.age]]>Otto</td>
    <td th:text=[[user.email]]>@mdo</td>
    <td>
      index: [[${stats.index}]] <br/>
      count: [[${stats.count}]] <br/>
      size(总数量): [[${stats.size}]] <br/>
      current(当前对象):[[${stats.current}]]<br/>
      even(true)/odd(false): [[${stats.even}]]<br/>
      first: [[${stats.first}]]<br/>
      last: [[${stats.last}]]<br/>
    </td>
  </tr>
  </tbody>
</table>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>

 效果如下:

 判断

<td th:if="${#strings.isEmpty(user.email)}" th:text="联系不上">@mdo</td>
<td th:if="${not #strings.isEmpty(user.email)}" th:text="${user.email}">@mdo</td>
<td th:text= "| ${user.age} - ${user.age >= 18 ? '成年' : '未成年'}|">Otto</td>
<td th:switch="${user.role}">
  <button th:case="'admin'" type="button" class="btn btn-primary">管理员</button>
  <button th:case="'pm'" type="button" class="btn btn-secondary">项目经理</button>
  <button th:case="'hr'" type="button" class="btn btn-success">人事</button>
</td>

 这里的button我们可以直接从bootstrap中找

<td th:switch="${user.role}">
  <button th:case="'admin'" type="button" class="btn btn-primary">管理员</button>
  <button th:case="'pm'" type="button" class="btn btn-secondary">项目经理</button>
  <button th:case="'hr'" type="button" class="btn btn-success">人事</button>
</td>

属性优先级

<!--  将会只展示大于22岁的-->
<tr th:each="user, stats: ${users}" th:if="${user.age > 22}">
  <th scope="row" th:text="${user.id}">1</th>
  <td th:text="${user.name}">Mark</td>
  <td th:text= "| ${user.age} - ${user.age >= 18 ? '成年' : '未成年'}|">Otto</td>
  <td th:if="${#strings.isEmpty(user.email)}" th:text="联系不上">@mdo</td>
  <td th:if="${not #strings.isEmpty(user.email)}" th:text="${user.email}">@mdo</td>
  <td th:switch="${user.role}">
    <button th:case="'admin'" type="button" class="btn btn-primary">管理员</button>
    <button th:case="'pm'" type="button" class="btn btn-secondary">项目经理</button>
    <button th:case="'hr'" type="button" class="btn btn-success">人事</button>
  </td>
  <td>
    index: [[${stats.index}]] <br/>
    count: [[${stats.count}]] <br/>
    size(总数量): [[${stats.size}]] <br/>
    current(当前对象):[[${stats.current}]]<br/>
    even(true)/odd(false): [[${stats.even}]]<br/>
    first: [[${stats.first}]]<br/>
    last: [[${stats.last}]]<br/>
  </td>
</tr>

 开启属性绑定

<tr th:each="user, stats: ${users}" th:if="${user.age > 22}" th:object="${user}">
  <th scope="row" th:text="*{id}">1</th>

我们引用user.id的时候可以直接通过*{id}引用

模板引用

假如我们在每一页都有这样一个头需要我们展示,我们要怎么处理呢?

 我们在bootstrap中找到header这样的头元素,按F12复制该元素代码

 我们使用一个common.html页面里面专门放置我们公共的片段

th:fragment="myheader"设置片段名

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thmeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>公共内容</title>
</head>
<body>

<header th:fragment="myheader" class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">
  <div class="col-md-3 mb-2 mb-md-0">
    <a href="/" class="d-inline-flex link-body-emphasis text-decoration-none">
      <svg class="bi" width="40" height="32" role="img" aria-label="Bootstrap"><use xlink:href="#bootstrap"></use></svg>
    </a>
  </div>

  <ul class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">
    <li><a href="#" class="nav-link px-2 link-secondary">Home</a></li>
    <li><a href="#" class="nav-link px-2">Features</a></li>
    <li><a href="#" class="nav-link px-2">Pricing</a></li>
    <li><a href="#" class="nav-link px-2">FAQs</a></li>
    <li><a href="#" class="nav-link px-2">About</a></li>
  </ul>

  <div class="col-md-3 text-end">
    <button type="button" class="btn btn-outline-primary me-2">Login</button>
    <button type="button" class="btn btn-primary">Sign-up</button>
  </div>
</header>
</body>
</html>

 在我们需要的页面引用该片段

th:replace="~{common :: myheader} 判断哪个页面的哪个片段

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thmeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>list</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body>
<!--判断是哪个页面的哪个片段-->
<div th:replace="~{common :: myheader}"></div>
<div class="container">
  <a th:href="@{/list}">列表展示</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>

 其他

#开发期间关闭,上线以后开启

spring.thymeleaf.cache=false

错误处理 

springMVC中错误处理方式

@ExceptionHandler(Exception.class) 在controller中统一处理类的错误

@Slf4j
@RestController
public class demoController {

    @Autowired
    Person person;

    @GetMapping("/hello")
    public String testDemo(){
        log.trace("trace...");
        log.debug("debug...");
        log.info("默认的输出级别");
        log.warn("warn...");
        log.error("error");
        int i = 10 / 0;
        return "hello, SpringBoot 3!";
    }

    //使用新版路径匹配规则PathPatternParser
    @GetMapping("/a*/b?/{p1:[a-f]+}/**")
    public void  hello(HttpServletRequest request, @PathVariable("p1") String path){
        log.info("路径变量:{}", path);
        String uri = request.getRequestURI();
        log.info("路径:{}", uri);
    }

    //内容协商

    /**
     * RestController默认返回的是json数据
     * @return
     */
    @GetMapping("/person")
     public Person person(){
        return person;
    }

    /**
     * 1.@ExceptionHandler 统一处理指定的错误
     * 2.只能处理这个类的错误
     */
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e){
        return "Ohh~~~, 原因:" + e.getMessage();
    }
}

 @ControllerAdvice //这个类是集中处理所有@Controller发生的错误

@ControllerAdvice //这个类是集中处理所有@Controller发生的错误
public class GlobalExceptionHandler {
    /**
     * 1.@ExceptionHandler 统一处理指定的错误
     * 2.只能处理这个类的错误
     */
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e){
        return "Ohh~~~统一处理错误, 原因:" + e.getMessage();
    }
}

 SpringBoot中错误处理方式

 总结:当springMVC中不能处理错误时,springboot会从templates/error或者静态资源找精确码或者4XX 5XX的错误处理页,都没有找到的时候就去找error错误处理页

错误实战

 

 基础特征

banner的设置

#banner的有关设置 设置banner.txt文件的位置
spring.banner.location=classpath:banner.txt
#打开还是关闭baner
spring.main.banner-mode=console

可以在BootSchool中设置自己的banner.txt,如下我们在resources/banner.txt设置文件

             ██                   ██                 ██                          ██
            ░██    █████         ░░   █████         ░██                         ░██
  ██████   ██████ ██░░░██ ██   ██ ██ ██░░░██ ██   ██░██       ██████   ██████  ██████
 ░░░░░░██ ░░░██░ ░██  ░██░██  ░██░██░██  ░██░██  ░██░██████  ██░░░░██ ██░░░░██░░░██░
  ███████   ░██  ░░██████░██  ░██░██░░██████░██  ░██░██░░░██░██   ░██░██   ░██  ░██
 ██░░░░██   ░██   ░░░░░██░██  ░██░██ ░░░░░██░██  ░██░██  ░██░██   ░██░██   ░██  ░██
░░████████  ░░██   █████ ░░██████░██  █████ ░░██████░██████ ░░██████ ░░██████   ░░██
 ░░░░░░░░    ░░   ░░░░░   ░░░░░░ ░░  ░░░░░   ░░░░░░ ░░░░░    ░░░░░░   ░░░░░░     ░░

Profiles环境隔离用法

1.标识环境

        1)区分几个环境:dev(开发环境)test(测试环境)prod(生产环境)default(默认环境)

        2)指定组件在哪个环境下生效

                通过:@Profile({"test"})标注

                组件没有标注的时候在任何时候都会生效

        3)默认只有激活指定的环境,组件才会生效

2.激活环境

        配置文件激活:spring.profiles.active=dev

        命令行激活:java -jar xxx.jar --spring.profiles.active=dev

        包含指定环境,不管激活哪个环境,这个都要有。总是生效的环境:

        spring.profiles.include=dev, test

        生效的环境:激活的环境/默认环境 + 包含的环境

        总结:基础的环境---》包含的环境 动态切换的环境---》激活的环境

任何@Component, @Configuration 或 @ConfigurationProperties 可以使用 @Profile 标记,来指定何时被加载。[容器中的组件都可以被 @Prlfile 标记]

@Profile("dev")//设置当前环境为dev
@Component//只有当前被@Component的组件被控制
public class TestBean {
}

 我们使用命令行激活环境

3.配置文件怎么使用profile 

断工文件怎么使用Profile功能
1) 、application.properties:主配置文件。任何情况下都生效

2)、其他Profile环境下命名规范:application-{profile标识}.properties:                                                         比如: application-dev.properties

3)激活指定环境即可:配置文件激活、命令行激活

4)效果:
项目的所有生效配置项 = 激活环境配置文件的所有项 + 配置文件和激活文件不冲突的所有项如果发生了配置冲突,以激活的环境配置文件为准。                                                                 application-{profile标识}.properties 优先级 application,properties

我们在application.proerties环境中激活环境dev

dev配置文件中设置端口9999

 启动项目最终显示端口为9999

外部化配置

规则一:命令行 》 配置文件 》 代码

规则二:application-{环境}.properties 》 application.properties

规则三:包外》包内

规则四:config子目录下 》 config目录下 》根目录

规则五:配置文件优先级 》 导入优先级

单元测试

注解

断言 

 核心原理

生命周期监听

Listener先要从 META-INF/spring.factories 读到
1. 引导: 利用 BootstrapContext 引导整个项目启动
starting: 应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行 

environmentPrepared: 环境准备好 (把启动参数等绑定到环境变量中) ,但是ioc还没有创建,[调一次]
2. 启动:
contextPrepared: ioc容器创建并准备好,但是sources (主配置类)没加载。并关闭引导上下文,组件都没创建[调用一次]

contextLoaded: ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新 (我们的bean没创建)。

=======截止以前,ioc容器里面还没造bean呢=======
started: ioc容器刷新了 (所有bean造好了),但是 runner 没调用
ready: ioc容器刷新了 (所有bean造好了) ,所有runner调用完了
3. 运行
以前步骤都正确执行,代表容器running.

创建我们自己的Listener

@Slf4j
public class MyAppListener implements SpringApplicationRunListener {

    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        log.info("=============starting=============项目正在启动===========");
    }


    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                     ConfigurableEnvironment environment) {
        log.info("=============environmentPrepared=============环境准备完成===========");
    }


    public void contextPrepared(ConfigurableApplicationContext context) {
        log.info("=======contextPrepared=============ioc准备完成===========");
    }


    public void contextLoaded(ConfigurableApplicationContext context) {
        log.info("=======contextLoaded=============ioc加载完成===========");
    }


    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        log.info("=======started=============启动完成===========");
    }


    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        log.info("=======ready=============准备就绪===========");
    }


    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        log.info("=======failed=============应用启动失败===========");
    }
}

 添加配置

org.springframework.boot.SpringApplicationRunListener=com.example.springboot3demo.listener.MyAppListener

9大事件与探针

 事件的完整流程

事件和监听器的完整顺序 

各种回调监听器

 最佳实战

创建我们自己的事件监听器

@Slf4j
public class MyEvent implements ApplicationListener<ApplicationEvent> {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        log.info("=======事件=========到达============" + event);
    }
}

 在resources/META-INF/spring.factories中添加配置

org.springframework.context.ApplicationListener=com.example.springboot3demo.listener.MyEvent

创建我们自己的Runner

//添加Runner
@Bean
public ApplicationRunner applicationRunner(){
    return new ApplicationRunner() {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            log.info("==========applicationRunner=========运行了=============");
        }
    };
}

@Bean
public CommandLineRunner commandLineRunner(){
    return new CommandLineRunner() {
        @Override
        public void run(String... args) throws Exception {
            log.info("==========commandLineRunner=========运行了=============");
        }
    };
}

 事件驱动开发

创建事件

//创建事件,将信息封装在我们事件的source中去
public class LoginEvent extends ApplicationEvent {

    public LoginEvent(Person source) {
        super(source);
    }
}

 事件的发布者

//事件发布者
@Service
public class EventPublisher implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        //通过该实现该接口,把真正实现事件发布的底层组件注入
        this.applicationEventPublisher = applicationEventPublisher;
    }
    public void sendEvent(ApplicationEvent event){
        applicationEventPublisher.publishEvent(event);
    }
}

事件接收的两种方式:①@EventListener ②implements ApplicationEventPublisherAware

@Slf4j
@Service
public class ConsumeService {

    //通过该注解监听事件
    @EventListener
    public void onEvent(LoginEvent event){
        Object source = event.getSource();
        Person person = (Person) source;
        consume(person);
    }

    public void consume(Person person){
        log.info("================ConsumeService接收到事件=====================");
        log.info(person.getName() + "=================正在消费==================");
    }
}
//通过继承接口的方式接收事件
@Service
@Slf4j
public class StudyService implements ApplicationListener<LoginEvent> {
    public void study(Person p){
        log.info("==============StudyService正在接收信息=============");
        log.info("===========" + p.getName() + "正在学习==========");
    }

    @Override
    public void onApplicationEvent(LoginEvent event) {
        Object source = event.getSource();
        Person p = (Person)source;
        study(p);
    }
}

 测试

@GetMapping("/login")
public String login(@RequestParam("username") String username,
                  @RequestParam("age") Integer age){
    Person p = new Person();
    p.setName(username);
    p.setAge(age);
    LoginEvent loginEvent = new LoginEvent(p);
    //事件发布
    eventPublisher.sendEvent(loginEvent);
    return "TestLoginEvent";
}

@SpringBootApplication源码分析

@SpringBootApplication注解包含以下几个注解:

第二部门

等待更新。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值