SpringBoot2 学习记录

SpringBoot2 入门

要求

Java 8 & 兼容java14
Maven 3.3+
idea 2019.1.2

配置maven

  • 默认jdk版本
<profile>    
    <id>jdk-1.8</id>    
    <activation>    
        <activeByDefault>true</activeByDefault>    
        <jdk>1.8</jdk>    
    </activation>    
    <properties>    
        <maven.compiler.source>1.8</maven.compiler.source>    
        <maven.compiler.target>1.8</maven.compiler.target>    
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>    
    </properties>    
</profile>
  • 阿里云镜像
<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
</mirror>

HelloWorld

配置pom

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

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
  • MainApplication
@SpringBootApplication // 这是一个应用
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}
  • HelloController
@RestController
public class HelloController {
    @RequestMapping("/")
    public String hello(){
        return "hello SpringBoot2";
    }
}

main -> run

关于application.properties

可以修改配置 如 修改端口号

aserver.port = 8888

其他配置可选项详见官方文档

打包成jar

pom配置,无需配置xml,简化部署

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

运行jar

执行

java -jar xxx.jar

即可访问

pom依赖图解

Ctrl + Alt + Shift + U

@ComponentScan

默认扫描主程序所在层级的包,可以修改自定义包

@ComponentScan("com.xxx.boot")

@Configuration

示例

  • 原来的配置方式
<?xml ... ?>
<beans ...>
    <bean id="user01" class=" com.xxx.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>
  • 使用注解 @Configuration

    • 创建两个实体类,User ,Pet

    • MyConfig

      @Configuration // 这是一个配置类 相当于 配置了一个xml文件
      public class MyConfig {
          /*
           * @Bean => 往容器中加组件
           * 组件类型 => User,  组件id => user01 (函数名 或 自定义@Bean("xxx"))
           * */
          @Bean("userXiaoMing")
          public User user01(){
              return new User("小明", 18);
          }
      
          @Bean
          public Pet pet01(){
              return new Pet("小猫咪");
          }
      }
      
    • MainApplication

      @SpringBootApplication // 这是一个应用
      public class MainApplication {
          public static void main(String[] args) {
              // IOC容器
              ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
              String[] names = run.getBeanDefinitionNames();
              // 查看组件
              for (String name : names) {
                  System.out.println(name);
              }
          }
      }
      
  • 运行结果

      ...
      org.springframework.context.event.internalEventListenerFactory
      mainApplication
      org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
      myConfig
      helloController
      userXiaoMing // 《-
      pet01 // 《-
      org.springframework.boot.autoconfigure.AutoConfigurationPackages
      org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
      ...
    
  • 可以看到MyConfig本身也是一个组件

组件是单实例的

示例

  • 示例一
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        // 获取实例
        User bean1 = run.getBean("userXiaoMing", User.class);
        User bean2 = run.getBean("userXiaoMing", User.class);
        System.out.println(bean1 == bean2);
        System.out.println(bean1.equals(bean2));
    }
}

输出

true
true
  • 示例二
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        // 获取MyConfig组件
        MyConfig beans = run.getBean(MyConfig.class);
        // 获取子组件
        User user = beans.user01();
        User user1 = beans.user01();
        // 输出
        System.out.println(user == user1);
        System.out.println(user.equals(user1));
    }
}

输出

true
true

为什么

Ctrl + 鼠标左键 @Configuration 注解 可以看到

public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

proxyBeanMethods 默认为 true ,也就是说使用代理方法,每次请求组件检查是否已经存在该组件,进行重用

修改

@Configuration(proxyBeanMethods = false)

再次运行

结果

false
false

同样,

User有Pet变量时,

return User实例,

User实例拿到的Pet实例 MyConfig的Pet实例 不相同

总结

Full -> 全模式 proxyBeanMethods = true 【需要检查,慢】

Lite -> 轻量级模式 proxyBeanMethods = false 【不需要检查,快】

@Import

MyConfig添加注解

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

MainApplication

DBHelper bean = run.getBean(DBHelper.class);
String[] beans = run.getBeanNamesForType(User.class);
System.out.println(bean);
for (String s : beans) {
    System.out.println(s);
}

输出

ch.qos.logback.classic.db.DBHelper@36453307
com.abc.boot.bean.User
userXiaoMing

@Conditional

条件装配,满足条件时才进行组件注入

示例

@ConditionalOnBean(name = "dog")
@Bean("userXiaoMing")
public User user01(){
    return new User("小明", 18);
}

@Bean("cat")
public Pet pet01(){
    return new Pet("小猫咪");
}

存在cat组件时才往容器注入userXiaoMing

run.containsBean("userXiaoMing") // false

@ImportResource

导入Spring配置文件

MyConfig添加注解

@ImportResource("classpath:beans.xml")

beans.xml下的bean会被注册到该容器

@ConfigurationProperties

如果使用myCar会报错,

application.properties

mycar.brand = YADEA
mycar.price = 2000

HelloController

@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
    private String brand;
    private Integer price;
	...
}

HelloController

@RestController
public class HelloController {
    @Autowired // 自动注入 
    Car car;
    @RequestMapping("/car")
    public Car car(){
        return car;
    }
    @RequestMapping("/")
    public String hello(){
        return "hello SpringBoot2";
    }
}

启动程序并访问/car

{"brand":"YADEA","price":2000}

@EnableConfigurationProperties

springboot.properties

mycar.brand = YADEA
mycar.price = 2000

MyConfig

@EnableConfigurationProperties(Car.class) // 开启配置绑定功能 自动注册到容器

Car

@ConfigurationProperties(prefix = "mycar") //  此时只需 @ConfigurationProperties

小技巧

Lombok

安装

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

file -> settings -> plugins 搜索并安装lombok插件

使用

标注了@Data的类自动生成setter、getter方法,

标注了@toString的类自动生成toString方法

@NoArgsConstructor -> 无参构造器

@AllArgsConstructor -> 全参构造器

@EqualsAndHashCode -> 按照属性重写哈希值

@Slf4j 的使用

@Slf4j  // <<
@RestController
public class HelloController {
    @RequestMapping("/")
    public String hello() {
        log.info("收到请求...");  // <<
        return "hello SpringBoot2";
    }
}

dev-tools

Restart

安装

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

使用

代码更新后,Ctrl + F9Buid -> Build Project

Spring Initailizr

安装

idea商业版自带

使用

file -> new project -> Spring Initailizr

可选配置

YAML

格式

k: v

k: (k1: v1,k2: v2) 

或

k: 

	k1: v1

	k2: v2

数组

k: [v1, v2, v3]

或

k:

	-  v1

    -  v2

	-  v3

单双引号有区别,特殊字符处理

单引号下转义(行为改变)

双引号下不转义(行为不改变)

示例

java

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private String gender;
    private Integer age;
    private Integer height;
    private String[] interests;
    private Map<String,Object> score;
    private Map<String, List<Pet>> allPets;
}

yml

person:
  name: 张三
  gender: 男
  age: 18
  height: 180
  interests: [游泳,篮球]
  score:
    java: 80
    c++: 81
    html: 82
  allPets:
    pet1:
      - dog
      - cat

yml提示-processor

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

排除-processor

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-configuration-processor</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

目录结构

image-20220905215606582

Web开发

静态资源路径默认配置

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

区分静态资源

静态资源与web请求区分开

增加前缀/res

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

或者

# 修改默认
spring:
  resources:
    static-locations: classpath:/static

多个

resources:
  static-locations: [classpath:/static, classpath:/public]

webjars

引入jar

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>

引用

http://localhost:8080/webjars/jquery/3.5.1/jquery.js

welcome与favicon

  • 静态资源路径下index.html

    • 可以配置静态资源路径

    • 但是不可以配置静态资源的访问前缀。否则导致index.html不能被默认访问

      spring:
      # mvC:
      #	static-path-pattern: /res/** 这个会 导致welcome page 和 favicon 功能失效
        resources:
          static- locations: [classpath: /haha/]
      
  • controller能处理/index

源码分析

静态资源管理

配置文件相关属性绑定

ResourceProperties => spring.resources

WebMvcProperties => spring.mvc

...

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    ...
    @Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
        ...
        public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
		}
        ...
    }
    ...
}

静态资源访问处理代码

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

Rest映射

OrderedHiddenHttpMethodFilter

HiddenHttpMethodFilter

页面表单的PUT、DELETE请求等需要开启过滤(不开启表单只需要携带_method参数过去)

spring:
  mvc:
    hiddenmethod:
      filter:
	    enabled: true
#	开启页面表单的Rest功能

默认的_method可修改,代码如下

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_custom");
        return methodFilter;
    }
}

请求映射x

请求处理

常用注解

普通参数与基本注解

  • 注解:
    @PathVariable、@RequestHeader、 @ModelAttribute、 @RequestParam、 @MatrixVariable、 @CookieValue、
    @RequestBody
  • Servlet API:
    WebRequest、ServletRequest、 MultipartRequest、 HttpSession、 javax.servlet.http.PushBuilder、 Principal、
    InputStream、Reader、 HttpMethod、 Locald、 TimeZone、 Zoneld
  • 复杂参数:
    Map、Errors/BindingResult、 Model、 RedirectAttributes、 ServletResponse、 SessionStatus、
    UriComponentsBuilder、ServletUriComponentsBuilder
  • 自定义对象参数:
    可以自动类型转换与格式化,可以级联封装。

Thymeleaf

Thymeleaf 官网

引入

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

配置

自动配置

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {}

SpringTemplateEngine...

路径

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

   private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

   public static final String DEFAULT_PREFIX = "classpath:/templates/";

   public static final String DEFAULT_SUFFIX = ".html";
    
   ...
}

简单体验

模板代码

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">默认H1内容</h1>
<h2>
    <a href="http://www.bilibili.com" th:href="${link}">$去B站</a><br>
    <a href="http://www.bilibili.com" th:href="@{link}">@去B站</a><br>
</h2>
</body>
</html>

结果

<h1>hello Thymeleaf</h1>
<a href="http://www.bilibili.com">$去B站</a><br>
<a href="link">@去B站</a><br>

拦截器

登录拦截与静态资源放行

拦截示例

@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")// 拦截所有
                .excludePathPatterns("/", "/login");
    }
}
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        System.out.println("loginUser == null ?" + (loginUser == null));
        if (loginUser != null){
            return true;
        }
        response.sendRedirect("/");
        return false;
    }
    ...
}

放行静态资源示例

.excludePathPatterns("/", "/login","/css/**","/js/**","/img/**","/font/**");

postHandle 处理完preHandle执行

afterCompletion 渲染完页面后执行

learn from https://www.bilibili.com/video/BV19K4y1L7MT/

posted @ 2022-10-30 22:58  夏末秋初~  阅读(25)  评论(0编辑  收藏  举报