前言: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: V2、对象:键值对的集合。
行内写法: 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):整数位和小数位位数限制格式类
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的值