ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从Andorid攻城狮转Java后端开发(2)

从Andorid攻城狮转Java后端开发(2)

前言:SpringBoot的项目结构

上一篇已经讲了如何创建一个SpringBoot项目。我们回忆一下它的项目结构:

resources文件夹中目录结构

static:保存所有的静态资源;js ,css, image
templates:保存所有的模板页面。(SpringBoot默认jar包使用嵌入式的tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker,thymeleaf)

application.yaml:SpringBoot应用的配置文件,可以修改一些默认值,配置文件的值最终都是会绑定到某个类上,这个类会在容器中创建对象.

一、配置文件yaml的使用

(1)组件的绑定

理解这句话:

application.yaml——SpringBoot应用的配置文件,可以修改一些默认值,配置文件的值最终都是会绑定到某个类上,这个类会在容器中创建对象.

我们打开一个新项目,application.yaml中会有这么一段配置,可以看到这是配置ApplicationId属性的。

如果我们想改端口号怎么做?加入下面这段话到yaml文件中

server: port: 8000

当我们点击port进入到源码时,可以看到这个属性也是赋值到了ServerProperties类中的port属性上。

所以说配置文件的值最终都是会绑定到某个类上,这个类会在容器中创建对象。

(2)yaml语法学习

1、单个的,不可再分的值

K: V

2、对象:键值对的集合。

行内写法: K: {K1: v1,K2: v2,K3: v3} 或者 K: K1: v1 K2: v2 K3: v3

需要注意:yaml文件中,大小写敏感的。

3、数组:一组按次序排列的值

行内写法: k: [v1,v2,v3] 或者 k: - v1 - v2 - v3

示例:

注意:每个冒号后需要跟一个空格,每一个“-”后面也需要跟着一个空格。

(3)配置文件与类的绑定

在SpringBoot中,我们使用@Component + @ConfigurationProperties注解的方式,让配置文件yaml中的属性与类属性一一对应并赋值上去。

@ConfigurationProperties(prefix = "person") @Component public class Person { private String name; private int age; private Boolean boss; private Date birthday; private String[] interests; private List<String> animal; private Map<String,Object> score; private Pet pet; private Set<Double> salarys; private Map<String, List<Pet>> pets; ... 下面是所有属性的get和set方法,这就不写出来了 ... }

@Component + @ConfigurationProperties:告诉springBoot将本类中的所有属性和配置文件中相关的配置进行绑定。
prefix = "person":指定使用yaml文件中的person属性来和本类进行绑定。

1、ConfigurationProperties的两种联合使用

  • @Component+@ConfigurationProperties

只有这个组件是容器中的组件,才能使用容器中提供的ConfigurationProperties功能。所以加上@Component注解

  • @EnableConfigurationProperties + @ConfigurationProperties

可以在注入Person类的目标类上写@EnableConfigurationProperties(Person.class),这代表开启Person类的属性配置功能且把这个Person组件自动注册到容器中

  • @ConfigurationProperties

报错,编译不通过,只写它是不行的,因为没有把组件注册到容器中。@Component与@EnableConfigurationProperties都是来干把Person类注册到容器这件事的。

2、yaml文件的代码提示

在yaml文件中书写类中的属性时是没有代码提示的,如果想要在配置文件中有对应类的属性代码提示,可以加下面这个依赖即可。

<!--导入配置文件处理器,配置文件进行绑定就会有提示--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!--打包成jar包时,记得去掉这个,只是方便我们开发的,打包进去反而扩大了jar的体积--> <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>

(4)@Value注解

@Value是注解在单个属性上的;@ConfigurationProperties是注解在类名上的

运行后,请求接口看输出:

@Value与@ConfigurationProperties的区别:
1、在功能上,@ConfigurationProperties是批量注入属性;@Value是一个个指定
2、在语法上,@ConfigurationProperties支持松散语法绑定(例如:lastName与last-name,last_name代表一个意思)大小写不敏感;@Value不行,必须按照配置文件中的名字来写,大小写敏感
3、在表达式方面,@ConfigurationProperties不支持表达式,@Value是可以的(#{11*2}可以支持表达式语言)
4、在JSR303数据校验上,@ConfigurationProperties支持,@Value不支持
5、在复杂类型封装,@ConfigurationProperties支持,@Value不支持————例如获取配置文件中的maps值,@Value不支持

第五点,Value在书写代码提示里,也可以看到他把所有的数组都是获取单个数据,并不会返回整个数组对象。所以Value只适合基础数据类型的属性。

@ConfigurationProperties与@Value的优先级:
@ConfigurationProperties会覆盖@Value的值,但若配置文件中没有对应值,那么@Value赋的值就会得以保留,所以不是"@ConfigurationProperties 全盘接管",而是逐字段、有值才覆盖

官方推荐使用@ConfigurationProperties代替@Value使用。但当我们只需要获取一个基础属性时,可以直接使用@Value。

(5)@Validated注解

在JSR303数据校验上,@ConfigurationProperties支持,@Value不支持

在使用JSR303数据校验时,需要在pom.xml文件中导入以下依赖,Spring Boot 2.3 以后,校验依赖不再随 web 启动器一起带出来,必须手动加

pom.xml文件 <!--JSR-303 / Jakarta Bean Validation 校验启动器:@Email、@NotNull、@Valid 等都靠它 注意:Spring Boot 2.3 以后,校验依赖不再随 web 启动器一起带出来,必须手动加--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> Person.class文件 @ConfigurationProperties(prefix = "person") @Component @Validated public class Person { @Email private String name; private int age; private Boolean boss; private Date birthday; private String[] interests; private List<String> animal; private Map<String,Object> score; private Pet pet; private Set<Double> salarys; private Map<String, List<Pet>> pets; ... }

运行服务器时,就会报错,因为name在application.yaml中是非邮箱形式,所以报错如下:

@Validated注解——它告诉 Spring:「这个类/这个方法的参数,请帮我跑 Bean Validation 校验」,真正的一堆校验注解(@NotNull、@Email 这些)是被@Validated这个开关触发。

空值类

  • @Null/@NotNull:是否为 null
  • @NotBlank:非空且去空格后长度 > 0(只用于字符串,最常用)
  • @NotEmpty:非空且长度/个数 > 0(String、Collection、Map、数组都能用)

长度/大小类

  • @Size(min, max):长度/元素个数区间(String、集合、Map、数组)
  • @Min(value)/@Max(value):数值下限/上限(int/long 等整数型,不含 double/float)
  • @DecimalMin/@DecimalMax:同上但支持小数(inclusive=控制是否含边界)

数值正负类(Hibernate Validator 提供的额外约束)

  • @Positive/@PositiveOrZero/@Negative/@NegativeOrZero
  • @Digits(integer, fraction):整数位和小数位位数限制

格式类

  • @Email:邮箱格式(可加regexp自定义)
  • @Pattern(regexp = "..."):正则匹配(手机号、身份证号都靠它)
  • @URL(Hibernate 扩展)

日期类

  • @Past/@PastOrPresent:必须是过去/过去或今天
  • @Future/@FutureOrPresent:必须是未来

布尔类

  • @AssertTrue/@AssertFalse:必须为 true/false(比如"是否同意协议")

1、@Validated与@Valid联合使用达到级联效果

@Valid是 Java 官方标准注解,@Validated是 Spring 自己造的。日常用@Validated,但嵌套对象校验必须靠@Valid

@RestController @Validated // ① 类上:开启方法级校验(@RequestParam 上的约束归它管) public class HelloController { @PostMapping("/users") // ② 参数上 @Valid:跑 User 内部 + 级联钻进 Address public String create( @Valid @RequestBody User user) { return "ok"; } @GetMapping("/hello") // ③ 方法参数的约束,靠类上的 @Validated 触发 public String hello(@NotBlank @RequestParam String name) { return "hi"; } } public class User { @NotBlank private String name; // 注意:这里如果没标 @Valid,下面 Address 类内部的约束根本不会跑! @Valid private Address address; } public class Address { @NotBlank(message = "城市必填") private String city; }
  • 标了@Valid→ 校验User时会自动钻进address,把Address.city的@NotBlank也跑了。
  • 漏了@Valid→User.name会校验,但Address.city被跳过。
  • @Validated在类上→ 负责方法参数(如@RequestParam)的校验;
  • @Valid在@RequestBody参数上→ 负责请求体对象 + 钻进它嵌套的子对象。

2、进阶:分组 + 级联一起上

public interface Add {} // 新增场景 public interface Update {} // 修改场景 public class User { @NotNull(groups = Update.class) private Long id; @NotBlank(groups = Add.class) private String name; @Valid // 级联 private Address address; } public class Address { @NotBlank(groups = Add.class) private String city; // 分组会传到这里 } @PostMapping("/users") public String add(@Validated(Add.class) @Valid @RequestBody User u) {...} // 会走Add组:name + city 都校验

@Validated(组)决定跑哪组规则,@Valid决定规则要不要往子对象里传。

(6)@PropertySource注解

思考:@ConfigurationProperties是默认加载在application.yaml中写的配置信息。如果整个项目所有的配置信息都写在application.yaml中,那么这个文件会非常的庞大。这就产生了一个需求:生成多个配置文件,每个配置文件管理不一样的配置信息。

@PropertySource:加载指定的配置文件。当我们绑定的配置信息不在默认的application.yaml文件中,则需要写这个注解来指定目标配置文件。

注意:@PropertySource(value = {"classpath:person.properties"})这里配置的文件是xxx.properties结尾的文件。如果新建的配置文件想要使用xxx.yaml,那么这里会报错。@PropertySource注解只适用于properties结尾的配置文件。自定义的yaml文件想要生效可以这样做:

application.yaml文件下 spring: application: name: moxiaoting01 config: import: - classpath:/my_config/person.yaml person.yaml文件下 Person: name: ssss boss: true birthday: 2019/12/09 age: 18 # interests: [篮球,足球] interests: - 篮球 - 足球 - 111 # animal: [狗狗,猫咪] animal: - 狗狗 - 猫咪 # score: {english: 80,math: 90} score: english: 80 math: 90 salarys: - 99.99 - 23.4 pet: name: 小李 weight: 99.99 pets: sick: - {name: 阿狗,weight: 30} - name: 阿猫 weight: 99.99 - name: 奥冲 weight: 77.77 health: - {name: 阿花,weight: 30} - {name: 阿1,weight: 30} @Configuration @ConfigurationProperties(prefix = "person") public class Person { private String name; private int age; private Boolean boss; private Date birthday; private String[] interests; private List<String> animal; private Map<String,Object> score; private Pet pet; private Set<Double> salarys; private Map<String, List<Pet>> pets; ... }

自定义的yaml文件想要生效有2种方式:

  • 在默认配置文件下,导入其他配置文件路径:在application.yaml里加一行spring.config.import: classpath:person.yaml(Spring Boot 2.4+ 原生支持)
  • 依旧使用PropertySource注解,@PropertySource+ 自定义 factory的方式也是可以的。yaml 文件必须显式指定 factory 。
/** * 让 @PropertySource 能够读取 .yaml / .yml 文件。 * * <p>@PropertySource 默认使用的工厂是 * {@link org.springframework.core.io.support.DefaultPropertySourceFactory}, * 它内部走的是 {@code ResourcePropertySource},只按 .properties 的语法逐行解析。 * 把一个 yaml 文件丢给它,嵌套结构会全部塌平成一层 key, * 也拿不到 {@code person.name} 这样的前缀,因此 @ConfigurationProperties 一个字段都绑不上。 */ public class YamlPropertySourceFactory implements PropertySourceFactory { private static final YamlPropertySourceLoader LOADER = new YamlPropertySourceLoader(); @Override public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { String sourceName = (name != null) ? name : resource.getResource().getFilename(); List<PropertySource<?>> sources = LOADER.load(sourceName, resource.getResource()); if (sources.isEmpty()) { return new PropertiesPropertySource(sourceName != null ? sourceName : "yaml", new Properties()); } return sources.get(0); } } @PropertySource(value = {"classpath:/my_config/person.yaml"}, factory = YamlPropertySourceFactory.class) @Configuration @ConfigurationProperties(prefix = "person") public class Person { private String name; private int age; private Boolean boss; private Date birthday; private String[] interests; private List<String> animal; private Map<String,Object> score; private Pet pet; private Set<Double> salarys; private Map<String, List<Pet>> pets; ... }

(7)配置文件占位符

#配置文件占位符 #可以在配置文件中引用前面配置过的属性(优先级前面配置过的这里都能用) # ${app.name:默认值}来指定找不到属性时的默认值,注意冒号前后不能有空格 # ${random.value}、${random.int}、${random.long}、${random.int(10)}配置文件中可以使用随机数 Person: boss: true birthday: 2019/12/09 age: ${random.int} interests: - ${person.name:xiaoting} 结果 Person{name='null', age=-2135055719, boss=true, birthday=Mon Dec 09 00:00:00 CST 2019, interests=[xiaoting], animal=null, score=null, pet=null, salarys=null, pets=null}

(8)Lombok的使用

这是一个简化JavaBean的工具。

第一步:工程里导入依赖

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

第二步:安装Lombok插件并重启IDEA

第三步:使用

注意:只提供了全参与无参构造器方法,如果需要非全参构造器就跟以前一样,自己代码写就好了。

二、SpringBoot的常用注解

(1).主项目的自动配置原理(@SpringBootApplication= 三注解合成)

@SpringBootApplication ==@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan

@SpringBootApplication其实就是这三个注解的合成。

1、@SpringBootConfiguration

这个是用来表示这个类是Springboot的配置类,即可以理解为以前的配置文件

2、@EnableAutoConfiguration

1、@AutoConfigurationPackage :将本项目的主配置类(被@SpringBootApplication标记的类)的所在包以及下面所有子包里面的所有组件扫描到spring容器中。

如果想改动扫描路径,@SpringBootApplication(scanBasePackages = "org.example")或者@ComponentScan("org.example")指定扫描路径

2、@Import(AutoConfigurationImportSelector.class):会给容器中导入非常多的自动配置类(xxxxAutoConfiguration),就是给容器中导入这个场景所需要的全部组件,并配置好这些组件。

3、@ComponentScan

包扫描注解:只要标注了@Controller、@service、@Repository、@component都会自动扫描,是一个可以在同一个类上多次重复用的标注。

@ComponentScan(value = "org.example", includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}), @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) },useDefaultFilters = false)

value:指定扫描的路径是包名为"org.example"下的所有包以及其子包。

includeFilters:指定扫描的时候只需要包含哪些组件。需要注意要让includeFilters生效需要禁用默认的配置规则————userDefaultFilters=false

@ComponentScan(value = "org.example", excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}), })

excludeFilters指定扫描的时候按照什么规则排除那些组件,如上是指定排除Controller标注的组件。type的值有:

  • FilterType.ANNOTATION :注解
  • FilterType.ASSIGNABLE_TYPE :类
  • FilterType.ASPECTJ :按ASPECTJ表达式
  • FilterType.REGEX :按正则表达式
  • FilterType.CUSTOM :使用自定义规则,必须是TypeFilter的实现类
public class MyTypeFilter implements TypeFilter { /** * 每扫描到一个组件,都会走这个方法,去判断是否符合匹配条件,若符合则加入容器,不符合则过滤掉 * 匹配成功返回true即加入容器中,失败返回false即过滤掉 * * @param metadataReader 读取到的当前正在扫描的类的信息 * 注意这是指<当前正在扫描的类>而不是<当前是它注解的类>,这是一个自定义规则而非自定义注解,它不可以当成一个注解用 * * @param metadataReaderFactory 可以获取到其他任何类的信息 */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的类信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径) Resource resource = metadataReader.getResource(); return false; } } 使用如下: @ComponentScan(value = "org.example", includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) },useDefaultFilters = false)

(2).给容器中加入组件的相关注解

1、@ImportResource+@Configuration

导入Spring的配置文件(指xml文件),让配置文件里面的内容生效。

当第三方框架或者很老的项目,就是使用的xml定义的组件,我们一个一个转成注解形式太麻烦,不转springBoot不认识。则可以使用该注解去申明目标xml,让目标xml中的所有组件都注入到容器中。注意:该注解需要写在配置类上,即@Configuration上。

新建bean.xml,代码如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user01" class="com.example.moxiaoting01.User"></bean> </beans> 新建一个user类,代码如下: public class User { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } 在HelloController类中自动注入user(自动注入的前提是user得是一个组件) @RestController public class HelloController { @Autowired User user;(发现编译器爆红,指向user) @RequestMapping("/person") public String person() { return ""; } }

也就是说,spring的xml文件方式注入组件在springboot中是无法识别的。那一些老项目xml配置文件中的组件很多很多,拆解很麻烦,@ImportResource注解就能解决这个麻烦。在随便一个配置类上写:

@SpringBootApplication @ImportResource(value = {"classpath:bean.xml"}) //写上这句话以后就不会爆红了,user就是组件了 public class Moxiaoting01Application { public static void main(String[] args) { SpringApplication.run(Moxiaoting01Application.class, args); } }

2、@Configuration + @Bean

在springBoot中创建一个组件,已经不用xml方式了。使用的是@Configuration + @Bean的方式。

A1、@Configuration

告诉SpringBoot这是一个配置类 == 以前的.xml配置文件,要注意的是配置类也是一个组件。

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration {...}

需要关注的是Configuration注解里有一个属性proxyBeanMethods,在springBoot2.0以上配置类有一个默认属性proxyBeanMethods它默认是true

proxyBeanMethods:代理bean的方法

Full(proxyBeanMethods = true 全量级模式

Lite(proxyBeanMethods = false 轻量级模式

配置类可以使用@Bean标注在方法上给容器注册组件,默认注册的组件是单实例的,就是因为属性proxyBeanMethods它默认是true,也就是全量级模式。如果改为false那么注册的组件就不是单例的,这就是轻量级模式。

User类: public class User { private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } MyComponent类: @Configuration(proxyBeanMethods = false) public class MyComponent { @Bean public User user01(){ User user = new User("zhangshan" , 18); return user; } } 项目启动类 @SpringBootApplication public class Moxiaoting01Application { public static void main(String[] args) { //将SpringBoot的应用启动起来 //这里返回的是IOC容器 ConfigurableApplicationContext run = SpringApplication.run(Moxiaoting01Application.class, args); /** * @Configuration(proxyBeanMethods=true)时获取到的是代理对象调用方法:springBoot总会检查这个组件是否在容器中有,有就不会创建,保持组件单实例 * @Configuration(proxyBeanMethods=false)时获取到的就不是代理对象,那么通过这个对象调用方法时,就是直接走方法而非去容器中拿组件。 */ MyComponent lookComponent = run.getBean(MyComponent.class); User user1 = lookComponent.user01(); User user02 = lookComponent.user01(); System.out.println("组件: " + (user1 == user02)); } }

proxyBeanMethods=false有一个好处就是,SpringBoot会跳过检查容器中是否含有该实例,所以加载就会更快一些。

那么推荐:当我们只是单纯的定义一个组件,别人并不会用到它——我的理解就是这个组件可以不是单例可以多个实例,那么我们可以设置为false,提高项目性能

A2、@Configuration + @Bean
@Configuration public class MyComponent { @Bean public User user01(){ User user = new User("zhangshan" , 18); return user; } }

上面这段代码的含义:将方法的返回值添加到容器中。以方法名作为这个组件的id/名字。方法返回的值就是组件在容器中的实例。

默认情况下,外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象。

@Configuration public class MyComponent { @Bean public User user01(){ User user = new User("zhangshan" , 18); return user; } @Bean("tom") //组件名就不是方法名了,就叫tom了 public User user02(){ return new User("moxiaoting" , 20); } }

使用@Bean("tom"),能修改组件的名字。介绍以下相关组件的查询方法:

@SpringBootApplication public class Moxiaoting01Application { public static void main(String[] args) { //将SpringBoot的应用启动起来 //这里返回的是IOC容器 ConfigurableApplicationContext run = SpringApplication.run(Moxiaoting01Application.class, args); //查看容器里面的所有组件 String[] name = run.getBeanDefinitionNames(); for (String name1 : name) { System.out.println(name1); } //从容器中获取对应组件,名字叫user01,类形式User.class的 User user = run.getBean("user01", User.class); User user01 = run.getBean("user01", User.class); System.out.println("组件: " + (user == user01)); //搜索指定类User.class的所有组件 String[] beanNamesForType = run.getBeanNamesForType(User.class); for (String beanName : beanNamesForType) { System.out.println(beanName); } //用来判断容器中是否含有name叫user01的组件 返回true就是有,false就是没有 boolean user0 = run.containsBean("user01"); System.out.println(user0); } }

3、@Conditional条件装配

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

它有很派生注解,常用的:

  • ConditionalOnBean/ConditionalOnClass——当容器中存在指定Bean/class时,则干xxx事情
  • ConditionalMissingBean/ConditionalMissingClass——当容器中不存在指定Bean/class时,则干xxx事情
  • ConditionalOnResource——当容器中存在指定资源时,则干xxx事情
  • ConditionalOnJava——当容器中存在指定java版本号时,则干xxx事情
  • ConditionalOnWebApplication——当我们的应用是一个web应用时,则干xxx事情
  • ConditionalOnNotWebApplication——当我们的应用不是一个web应用时,则干xxx事情
@Configuration public class LookComponent { //只有当容器中包含有pet01组件,才把user01组件注入容器 @ConditionalOnBean(name = "pet01") @Bean public User user01(){ User user = new User("zhangshan" , 18); user.setPet(pet01()); return user; } @Bean public Pet pet01(){ return new Pet("cat"); } }

@ConditionalOnBean这个可以作用于一个方法,则只对这一个方法有条件注入。若写在类名处即配置类上,则对该配置类下的所有方法都有这个条件注入。

重点注意:

上面这段代码看似是没有问题,但是运行后你会发现依旧没有user01组件。因为@ConditionalOnBean 对 @Bean 方法的声明顺序敏感,评估 user01 时 pet01 还不存在,条件不满足,于是 user01 被跳过。你把 pet01() 挪到 user01() 前面就能解决。

4、@Import注解

@Import({User.class , DefaultShutdownHook.class}) @Configuration(proxyBeanMethods = true) public class LookComponent {...}

给容器中自动创建出这两个类型的组件,调用对应类的无参构造器创建,组件名字为全类名。这个适用于我们给第三方sdk中的某个类注册为组件,因为第三方sdk中的类有些我们可能无法手动new其对象出来。

注意:@Import注解必须写在某个组件上,随便找个组件的类上就可以。

(3).@RestController注解

@RestController == @Controller + @ResponseBody

@Controller:表现层 / Web 层;是接收 HTTP 请求、路由、返回响应。Spring MVC 会专门扫描它,把里面的@RequestMapping注册成路由。

@ResponseBody:标注在类名上,则代表整个类下的所有方法返回值都直接写给客户端,如果是对象则直接返回json串;若标注在方法名上,则代表该方法返回值都直接写给客户端,如果是对象则直接返回json串。

A1、@Component、@Controller、@Service、@Repository的理解

注解属于哪一层干什么有什么"特殊待遇"
@Component通用(哪层都能用)最基础的组件,给任意类贴无特殊处理,就是"注册进容器"
@Controller表现层 / Web 层接收 HTTP 请求、路由、返回响应Spring MVC 会专门扫描它,把里面的@RequestMapping注册成路由
@Service业务层写业务逻辑(判断、计算、组装)纯语义标签——功能上和@Component一模一样
@Repository数据访问层跟数据库打交道(增删改查)把数据库抛的原始异常自动翻译成 Spring 的统一异常(DataAccessException),换数据库时上层代码不用改

它们四个本质是同一个东西(Spring 组件/Bean),只是贴了不同"角色标签"。@Controller、@Service、@Repository三个注解的源码里都"包着"@Component。

// ① @Repository:只管数据存取(就是你 demo 里那个 users 列表,下一步换成 MySQL) @Repository public class UserRepository { private final List<User> users = new ArrayList<>(); // 模拟数据库 public User findById(String id) { return users.stream() .filter(u -> id.equals(u.id)) .findFirst().orElse(null); } } // ② @Service:只管业务逻辑(判断"能不能查"、"查完要补什么") @Service public class UserService { @Autowired private UserRepository userRepository; // Spring 自动注入 public User getById(String id) { if (id == null || id.trim().isEmpty()) { // ← 业务规则在这层 throw new IllegalArgumentException("id 不能为空"); } return userRepository.findById(id); // 不自己查库,交给 Repository } } // ③ @RestController:只管收发请求 @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/user/{id}") public User getUser(@PathVariable String id) { return userService.getById(id); // 返回值自动转 JSON(不用再 String.format 了!) } }

这就是项目中的三层架构。稍大点的项目都这么分的:表现层、业务层、数据访问层。这里需要和spring MVC的概念区分一下。

MVC 模式三层架构
管多大范围只管表现层这一层管整个应用
角色/分层Model、View、Controller表现层、业务层、数据访问层
解决什么问题请求进来后怎么组织、数据怎么展示代码按职责分工,方便维护和替换
对应注解只有@Controller(它就是 C)@Controller+@Service+@Repository
什么时候需要做 Web 页面/接口才需要稍大点的项目都这么分

@Service和@Repository压根不在 MVC 里——MVC 是个"表现层内部"的设计模式,管不到业务层和数据层去。

补充:Spring MVC 里 M 和 V 具体是啥
  • C(Controller)​ =@Controller/@RestController—— 你写UserController的那个
  • M(Model)​ = 要交给视图去渲染的数据。用@RestController时,"M"就是你的返回值对象(比如User)
  • V(View)​ = 视图。老式是 JSP/Thymeleaf 页面;返回 JSON 时,"V"退化成 Jackson 的序列化(就是你 A3 卡里那句"原生得手拼 JSON"被框架接管的地方)

A2、@GetMapping、@PostMapping、 @PutMapping、@DeleteMapping注解

@GetMapping("/hello") 就是 @RequestMapping(value = "/hello" , method = RequestMethod.GET)

@PostMapping、 @PutMapping、@DeleteMapping和@GetMapping是一样的。都是@RequestMapping(value = "xxxx" , method = RequestMethod.xxx)

@RestController public class HelloController { @RequestMapping("/person") public String person() { return "aaaa"; } @GetMapping("/hello") public String hello() { return "hello"; } }

A3、 @PathVariable获取接口的路径变量值

A4、@RequestHeader获取接口请求头参数

A5、@RequestParam 获取接口的请求参数,写在url?后面的参数

注意接口的地址不需要写明参数:

@GetMapping("xxxx/xxxx/xxx") 就可以了,不需要写成: @GetMapping("xxxx/xxxx/xxx?参数1={变量}&参数2={变量}")

A6、@PostMapping 获取接口的请求体

这个只有post方法才有请求体,请求体可以是一个表单结果,也可以是json串。取决于content-type的值

A7、@CookieValue 获取接口的Cookie参数

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进