> 文章列表 > Spring理论学习

Spring理论学习

Spring理论学习

1、什么是IOC

IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。

为什么叫控制反转?

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器)

没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或运行到某一点时,自己必须主动创建对象B或者使用已经创建的对象B,无论是创建还是使用对象B,控制权都在自己手里。

引入IOC容器之后,对象A和对象B失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

对象A获得依赖对象B的过程,由主动变成被动,控制权颠倒,这就是控制反转

IOC容器:在项目启动的时候读取项目中的bean节点,根据全限定类名(全限定类名是指一个类的完整名称,包括它所在的包名和类名,用点号分隔。例如,java.lang.String就是一个全限定类名,表示String类位于java.lang包中。)使用反射创建对象放到map里,扫描到有(@service\\@Compent等注解)的类也是通过反射创建对象放到map里。

将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。

在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

接下来我没在代码中需要用到里面的对象时候,通过注入(@Autowired/@Resourse)等,项目启动时会根据类型或id注入;id为对象名,为类的首字母小写形式

Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。

2、什么是Spring Bean

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。

我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。

<!-- Constructor-arg with 'value' attribute -->
<bean id="..." class="..."><constructor-arg value="..."/>
</bean>

下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。

org.springframework.beans和 org.springframework.context 这两个包是 IoC 实现的基础,如果想要研究 IoC 相关的源码的话,可以去看看

3、将一个类声明为bean的注解有哪些?

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

4、@Compent和@bean的相同与区别

  • 区别:
    • @Compent注解作用于类上,表示该类是一个Spring的组件,可以被自动扫描和装配到容器中。
    • @Bean注解作用于方法上,表示该方法返回一个对象可以被注册到容器中。该方法所在的类必须被@Configuration或@Compent注解标注。
    • @Compent注解通常用于自定义的类,而@Bean注解通常用于第三方库中的类或者需要自定义创建逻辑的类。
    • @Compent注解只能创建单例的Bean(在多线程的条件下容易出并发问题),而@Bean注解可以通过scope属性指定创建单例或多例的Bean。
  • 相同点:
    • @Compent和@Bean注解都可以将一个类声明为Spring的Bean,让容器管理其生命周期和依赖关系。
    • @Compent和@Bean注解都可以通过name属性指定Bean的名称,如果不指定,默认使用类名或方法名作为名称。

@Bean注解使用示例:

@Bean注解只能作用于方法上,表示该方法返回一个对象,可以被注册到容器中。如果要给类注解,可以使用@Compent或其他派生的注解,如@Repository,@Service,@Controller等。

@Configuration
public class AppConfig {@Beanpublic TransferService transferService() {return new TransferServiceImpl();}}

上面的代码相当于下面的 xml 配置

<beans><bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

下面这个例子是通过 @Component 无法实现的。

@Bean
public OneService getService(status) {case (status)  {when 1:return new serviceImpl1();when 2:return new serviceImpl2();when 3:return new serviceImpl3();}
}

 5、注入Bean的注解有哪些?

Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。

Annotaion Package Source
@Autowired org.springframework.bean.factory Spring 2.5+
@Resource javax.annotation Java JSR-250
@Inject javax.inject Java JSR-330

@Autowired 和@Resource使用的比较多一些。

6、@Autowired和@Resource的区别是什么

Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。

这会有什么问题呢? 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。

这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。就比如说下面代码中的 smsService 就是我这里所说的名称,这样应该比较好理解了吧。

// smsService 就是我们上面所说的名称
@Autowired
private SmsService smsService;

举个例子,SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。

// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入  SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;

我们还是建议通过 @Qualifier 注解来显式指定名称而不是依赖变量的名称。

@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType

@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。

public @interface Resource {String name() default "";Class<?> type() default Object.class;
}

如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName

// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;

简单总结一下:

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired 和@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显式指定名称,@Resource可以通过 name 属性来显式指定名称。

 7、Bean的作用域通常有哪些?

Spring 中 Bean 的作用域通常有下面几种:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : session作用域的Bean实例是针对每个http会话创建的,只在当前会话有效,当会话结束后,Bean实例将销毁。
  • application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

HTTP请求和HTTP会话的区别是:

HTTP请求是一种用于获取网络资源的通信协议,它由客户端(或浏览器)发起,由服务端(如Web服务器)响应,每个请求都包含一个方法(如Get或post),一个URL,一些标头和一个可选消息体。

HTTP会话是一种用于在多个请求之间保持状态信息的机制,它由客户端和服务端共同维护,通常使用HTTP Cookie来实现,每个会话都有唯一的标识符。

·  

如何配置 bean 的作用域呢?

xml 方式:

<bean id="..." class="..." scope="singleton"></bean>

注解方式:

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {return new Person();
}

8、springAOP

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

 什么是动态代理?

动态代理是一种编程技术,它可以在运行时动态地创建和修改代理类,而不需要事先为每个被代理的类编写固定的代理类。动态代理可以实现对目标对象的功能增强,如添加日志、事务、权限等。

动态代理的实现原理是利用Java的反射机制,通过字节码操作或者接口实现,来生成代理类的字节码文件,并加载到内存中,从而创建代理对象。动态代理可以在编译期、类加载期或运行期进行。

AOP 切面编程设计到的一些专业术语:

术语 含义
目标(Target) 被通知的对象
代理(Proxy) 向目标对象应用通知之后创建的代理对象
连接点(JoinPoint) 目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut) 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice) 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect) 切入点(Pointcut)+通知(Advice)
Weaving(织入) 将通知应用到目标对象,进而生成代理对象的过程动作

 切入点和连接点的区别:

  • 连接点是在程序执行过程中能够插入切面的一个点,如方法执行、构造器调用、字段赋值等。在Spring AOP中,连接点只表示方法执行。
  • 切入点是一些特定的连接点,是切面要织入的地方。切入点可以通过表达式或注解来匹配连接点。在Spring AOP中,切入点使用AspectJ的语法来定义。
  • 连接点是切面可以应用的所有可能性,而切入点是切面实际应用的具体位置。因此,切入点一定是连接点,但连接点不一定是切入点。

举个例子,假设有一个类A,它有三个方法:m1(), m2(), m3()。那么这三个方法都是连接点,因为它们都可以被切面拦截。但是如果我们定义一个切入点,只匹配m1()方法,那么只有m1()方法是切入点,而m2()和m3()方法不是切入点。

通知类型有哪些?

  • Before(前置通知):目标对象的方法调用之前触发
  • After (后置通知):目标对象的方法调用之后触发
  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing(异常通知) :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

9、springboot注解

9.1 @SpringBootApplication

我们可以把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合。

@SpringBootApplication注解源码

package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {......
}package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

@Target注解(修饰注解的注解)是一个元注解,也就是说,它只能用于修饰其他的注解。它的作用是指定一个注解可以应用于哪些程序元素,如类、方法、字段、参数等。它的唯一参数是一个ElementType枚举类型的数组,表示该注解可以修饰的元素类型。​​​​​​

@Retention注解是一个元注解,用于指定一个注解的保留策略,即该注解在什么时候被丢弃。它的唯一参数是一个RetentionPolicy枚举类型的值,表示该注解的生命周期。有三种保留策略:

RetentionPolicy.SOURCE: 该注解只保留在源代码中,编译器会忽略它。 RetentionPolicy.CLASS: 该注解保留在.class文件中,但是虚拟机会忽略它。这是默认的保留策略。 RetentionPolicy.RUNTIME: 该注解保留在.class文件中,并且可以在运行时被反射机制读取。

@EnableAutoConfiguration:是一个Spring Boot提供的注解,用于启用Spring应用上下文的自动配置功能。它会根据类路径中的组件和用户定义的Bean,自动创建和注册Bean。这样可以简化开发者的工作,无需手动配置各种Bean。这个注解通常包含在@SpringBootApplication注解中

  • 如果类路径中有HSQLDB这个jar文件,而用户没有手动配置任何数据库连接的Bean,那么Spring Boot会自动配置一个内存数据库的Bean
  • 如果类路径中有tomcat-embedded.jar这个jar文件,而用户没有手动配置任何ServletWebServerFactory的Bean,那么Spring Boot会自动配置一个TomcatServletWebServerFactory的Bean
  • 如果类路径中有spring-webmvc.jar这个jar文件,而用户没有手动配置任何DispatcherServlet的Bean,那么Spring Boot会自动配置一个DispatcherServlet的Bean

@ComponentScan: 扫描被@Component (@Repository,@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。

@Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类

9.2 @Autowired

注入bean

9.3 @Component、@Repository、@Service、@Controller

我们一般使用 @Autowired 注解让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,可以采用以下注解实现:

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

9.4 @RestController

@RestController注解是@Controller@ResponseBody的合集,表示这是个控制器 bean,并且是将函数的返回值直接填入 HTTP 响应体中,是 REST 风格的控制器。

代码示例:

单独使用@controller

@Controller
public class HelloController {@GetMapping("/hello")public String greeting(@RequestParam(name = "name", required = false, defaultValue = "World") String name, Model model) {model.addAttribute("name", name);return "hello";}
}

使用@Controller与@ResponseBody注解返回json格式数据

@Controller
public class HelloController {@PostMapping("/hello")@ResponseBodypublic Person greeting(@RequestBody Person person) {return person;}}

直接使用@RestController注解 

@RestController
public class HelloController {@PostMapping("/hello")public Person greeting(@RequestBody Person person) {return person;}}

单独使用 @Controller 不加 @ResponseBody的话一般是用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。@Controller +@ResponseBody 返回 JSON 或 XML 形式数据

Controller 返回一个页面

单独使用 @Controller 不加 @ResponseBody的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况。

SpringMVC 传统工作流程

@RestController 返回JSON 或 XML 形式数据

@RestController只返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。

SpringMVC+RestController

@Controller +@ResponseBody 返回JSON 或 XML 形式数据

如果你需要在Spring4之前开发 RESTful Web服务的话,你需要使用@Controller 并结合@ResponseBody注解,也就是说@Controller +@ResponseBody@RestController(Spring 4 之后新加的注解)。

@ResponseBody 注解的作用是将 Controller 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到HTTP 响应(Response)对象的 body 中,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。

Spring3.xMVC RESTfulWeb服务工作流程

@RestController vs @Controller

9.5 @Scope

声明 Spring Bean 的作用域,使用方法:

@Bean
@Scope("singleton")
public Person personSingleton() {return new Person();
}

四种常见的 Spring Bean 的作用域:

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一个 HTTP Session 会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

9.6 @Configuration

一般用来声明配置类(第三方类),可以使用 @Component注解替代,不过使用@Configuration注解声明配置类更加语义化。

@Configuration
public class AppConfig {@Beanpublic TransferService transferService() {return new TransferServiceImpl();}}

9.7 处理常见的http请求类型

5 种常见的请求类型:

  • GET :请求从服务器获取特定资源。举个例子:GET /users(获取所有学生)
  • POST :在服务器上创建一个新的资源。举个例子:POST /users(创建学生)
  • PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /users/12(更新编号为 12 的学生)
  • DELETE :从服务器删除特定的资源。举个例子:DELETE /users/12(删除编号为 12 的学生)
  • PATCH :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。

9.7.1 Get请求

@GetMapping("users") 等价于@RequestMapping(value="/users",method=RequestMethod.GET)

@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {return userRepository.findAll();
}

 9.7.2 POST请求

@PostMapping("users") 等价于@RequestMapping(value="/users",method=RequestMethod.POST)

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {return userRespository.save(userCreateRequest);
}

9.7.3 PUT请求

@PutMapping("/users/{userId}") 等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)

@PutMapping("/users/{userId}")
public ResponseEntity<User> updateUser(@PathVariable(value = "userId") Long userId,@Valid @RequestBody UserUpdateRequest userUpdateRequest) {......
}

9.7.4 Delete请求

@DeleteMapping("/users/{userId}")等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)

@DeleteMapping("/users/{userId}")
public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){......
}

9.7.5 PATCH请求

一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。

  @PatchMapping("/profile")public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) {studentRepository.updateDetail(studentUpdateRequest);return ResponseEntity.ok().build();}

9.8 前后端传值

9.8.1 @PathVariable和@RequestParam

@PathVariable和@RequestParam都是Spring MVC中用于从请求中获取参数值的注解,但是它们有一些相同和区别:

  • 相同点:它们都可以用在控制器方法的参数上,它们都可以指定参数的名称,它们都可以设置参数是否必须。
  • 区别点:@PathVariable用于获取路径中的占位符值,例如/user/ {id}而@RequestParam用于获取查询字符串中的参数值,例如/user?id=123。@PathVariable获取的值不会被URL解码,而@RequestParam获取的值会被URL解码。@PathVariable可以使用正则表达式来限制匹配的范围,而@RequestParam不能。@PathVariable可以从Spring 4.3.3开始设置为可选,而@RequestParam一直可以设置为可选。

@RequestParam 的使用示例

@RequestParam注解有一些属性可以设置,例如:

  • value: 指定参数的名称,如果不指定,默认使用方法参数的名称。
  • required: 指定参数是否必须,默认为true,如果为false,那么当参数缺失时不会报错,而是赋值为null。
  • defaultValue: 指定参数的默认值,如果参数缺失或者无法转换为目标类型,那么使用该值。

@RequestParam用于获取查询字符串的中的参数值,

前端代码示例,当用户在表单中输入姓名和年龄,并点击提交按钮时,前端会向后端发送一个GET请求,例如:http://localhost:8080/user?name=Tom&age=20

<form action="/user" method="get"><label for="name">Name:</label><input type="text" id="name" name="name"><label for="age">Age:</label><input type="number" id="age" name="age"><button type="submit">Submit</button>
</form>

后端控制器有一个方法,用于接收前端传来的姓名和年龄,并返回一个欢迎信息。后端控制器的代码如下:

// 在控制器类中使用@RequestMapping注解
@Controller
@RequestMapping("/user")
public class UserController {// 在控制器方法中使用@RequestParam注解@RequestMapping (method = RequestMethod.GET)public String welcomeUser(@RequestParam (value = "name") String userName,@RequestParam (value = "age") int userAge,Model model) {// 将用户信息添加到模型中model.addAttribute("name", userName);model.addAttribute("age", userAge);// 返回一个视图名称return "welcome";}
}

后端会根据@RequestParam注解获取name和age的值,并将它们添加到模型中,然后返回一个视图名称welcome。视图层会根据模型中的数据渲染一个欢迎页面。

@PathVariable的使用示例

@PathVariable注解有一些属性可以设置,例如:

  • value: 指定占位符的名称,如果不指定,默认使用方法参数的名称。
  • required: 指定占位符是否必须,默认为true,如果为false,那么当占位符缺失时不会报错,而是赋值为null。这个属性从Spring 4.3.3开始支持。
  • name: 指定占位符的名称,和value属性相同。这个属性从Spring 4.3.3开始支持

传参案例:

前端:前端页面有一个列表,显示用户的姓名和年龄,并且每个用户都有一个详情按钮,点击后可以查看用户的详细信息。前端页面的代码如下:

<ul><li>Name: Tom, Age: 20 <a href="/user/1">Details</a></li><li>Name: Alice, Age: 18 <a href="/user/2">Details</a></li><li>Name: Bob, Age: 22 <a href="/user/3">Details</a></li>
</ul>

后端控制器有一个方法,用于接收前端传来的用户id,并返回一个用户详情页面。后端控制器的代码如下:

// 在控制器类中使用@RequestMapping注解
@Controller
@RequestMapping("/user")
public class UserController {// 在控制器方法中使用@PathVariable注解@RequestMapping (value = "/ {id}", method = RequestMethod.GET)public String getUserDetails(@PathVariable (value = "id") int userId,Model model) {// 根据userId获取用户详情User user = userService.getUserById(userId);// 将用户详情添加到模型中model.addAttribute("user", user);// 返回一个视图名称return "details";}
}

当用户点击某个用户的详情按钮时,前端会向后端发送一个GET请求,例如:

http://localhost:8080/user/1

后端会根据@PathVariable注解获取1这个值,并根据它获取用户详情,并将它添加到模型中,然后返回一个视图名称details。视图层会根据模型中的数据渲染一个用户详情页面

9.8.2 @RequestBody

用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象

 需要注意的是:**一个请求方法只可以有一个@RequestBody,但是可以有多个@RequestParam@PathVariable**。 如果你的方法必须要用两个 @RequestBody来接受数据的话,大概率是你的数据库设计或者系统设计出问题了!

9.9 读取配置信息的注解

很多时候我们需要将一些常用的配置信息比如阿里云 oss、发送短信、微信认证的相关配置信息等等放到配置文件中。

下面我们来看一下 Spring 为我们提供了哪些方式帮助我们从配置文件中读取这些配置信息。

我们的数据源application.yml内容如下:

wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油!my-profile:name: Guide哥email: koushuangbwcx@163.comlibrary:location: 湖北武汉加油中国加油books:- name: 天才基本法description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。- name: 时间的秩序description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。- name: 了不起的我description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?

9.9.1 @Value(常用)

使用 @Value("${property}") 读取比较简单的配置信息:

@Value("${wuhan2020}")
String wuhan2020;

9.9.2 @ConfigurationProperties(常用)

@ConfigurationProperties 的功能是将配置文件中的属性绑定到一个 Java 类的字段上,从而方便地获取和使用这些属性

使用 @ConfigurationProperties 的步骤如下:

  1. 在配置文件中定义一些属性,例如 application.properties 或 application.yml,可以使用前缀来区分不同的属性组。
  2. 在 Java 类中添加 @ConfigurationProperties 注解,并指定前缀,然后为每个要绑定的属性提供一个带有公共 setter 方法的字段。
  3. 在 Spring Boot 应用中激活 @ConfigurationProperties 类,可以通过以下几种方式之一:
    • 在类上添加 @Component 注解,让其被 Component Scan 扫描到。
    • 在类上添加 @Configuration 注解,并在方法上添加 @Bean 注解,返回一个 @ConfigurationProperties 类型的对象。
    • 在其他类上添加 @EnableConfigurationProperties 注解,并指定要激活的 @ConfigurationProperties 类型。

使用示例:

myapp.mail.enabled=true
myapp.mail.default-subject=Test Mail

 我们可以创建一个 MailModuleProperties 类来绑定这些参数:

@Component
@ConfigurationProperties(prefix = "myapp.mail")
public class MailModuleProperties {private boolean enabled;private String defaultSubject;// getter and setter methods
}

然后我们可以在其他类中注入 MailModuleProperties 类型的 bean,并使用它的字段来访问配置文件中的属性值: 

@Service
public class MailService {@Autowiredprivate MailModuleProperties mailModuleProperties;public void sendMail(String to, String content) {if (mailModuleProperties.isEnabled()) {// send mail with default subject and content
String enabled=mailModuleProperties.getEnabled();}}
}

10、参数校验注解

10.1 一些常用字段的注解

通常用于校验服务端传过来的参数是否合法

@NotEmpty 被注释的字符串的不能为 null 也不能为空
@NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式
@Email 被注释的元素必须是 Email 格式。
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=)被注释的元素的大小必须在指定的范围内
@Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期

10.2 验证请求体

即校验一个类是否合法

即在一个类中,用注解表明其属性的一些规范,接收客户端那边传过来的json对象,可以校验其参数是否合法

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {@NotNull(message = "classId 不能为空")private String classId;@Size(max = 33)@NotNull(message = "name 不能为空")private String name;@Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")@NotNull(message = "sex 不能为空")private String sex;@Email(message = "email 格式不正确")@NotNull(message = "email 不能为空")private String email;}

我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException

@RestController
@RequestMapping("/api")
public class PersonController {@PostMapping("/person")public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {return ResponseEntity.ok().body(person);}
}

10.3 验证请求参数

一定一定不要忘记在类上加上 @Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。

示例如下

@RestController
@RequestMapping("/api")
@Validated
public class PersonController {@GetMapping("/person/{id}")public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {return ResponseEntity.ok().body(id);}
}

11、全局异常处理

可见牛客项目,有具体的使用