@Compent 作用就相当于 XML配置
@Component public class Student { private String name = "lkm"; public String getName() { return name; } public void setName(String name) { this.name = name; } }@Bean 需要在配置类中使用,即类上需要加上@Configuration注解
@Configuration public class WebSocketConfig { @Bean public Student student(){ return new Student(); } }下面这个例子是通过 @Component 无法实现的。
@Bean public OneService getService(status) { case (status) { when 1: return new serviceImpl1(); when 2: return new serviceImpl2(); when 3: return new serviceImpl3(); } }我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:
@Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component 注解标注。@Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。@Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。@Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。@Autowired 是用在 JavaBean 中的注解,通过 byType 形式,用来给指定的字段或方法注入所需的外部资源。
先看一下 bean 实例化和@Autowired 装配过程:
一切都是从 bean 工厂的 getBean 方法开始的,一旦该方法调用总会返回一个bean实例,无论当前是否存在,不存在就实例化一个并装配,否则直接返回。实例化和装配过程中会多次递归调用getBean方法来解决类之间的依赖。Spring几乎考虑了所有可能性,所以方法特别复杂但完整有条理。@Autowired最终是根据类型来查找和装配元素的,但是我们设置了后会影响最终的类型匹配查找。因为在前面有根据 BeanDefinition 的 autowire 类型设置 PropertyValue 值得一步,其中会有新实例的创建和注册。就是那个autowireByName方法。下面通过@Autowired来说明一下用法
Setter 方法中的 @Autowired 你可以在 JavaBean 中的 setter 方法中使用 @Autowired 注解。当 Spring 遇到一个在 setter 方法中使用的 @Autowired 注解,它会在方法中执行 byType 自动装配。
属性中的 @Autowired 你可以在属性中使用 @Autowired 注解来除去 setter 方法。当时使用为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性。
构造函数中的 @Autowired 你也可以在构造函数中使用 @Autowired。一个构造函数 @Autowired 说明当创建 bean 时,即使在 XML 文件中没有使用 元素配置 bean ,构造函数也会被自动连接。
@Autowired 的(required=false)选项 默认情况下,@Autowired 注解意味着依赖是必须的,它类似于 @Required 注解,然而,你可以使用 @Autowired 的 (required=false) 选项关闭默认行为。
@Autowired 是用在 JavaBean 中的注解,通过 byType 形式,用来给指定的字段或方法注入所需的外部资源。 如果容器中有多个相同类型的 bean,则框架将抛出 NoUniqueBeanDefinitionException, 以提示有多个满足条件的 bean 进行自动装配。
在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。 即自动注入的策略就从 byType 转变成 byName 了。
@Autowired 可以对成员变量、方法以及构造函数进行注释,而@Qualifier 的标注对象是成员变量、方法入参、构造函数入参。正是由于注释对象的不同,所以 Spring 不将 @Autowired 和@Qualifier 统一成一个注释类。
Controller 返回一个页面
单独使用 @Controller 不加 @ResponseBody 的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75Vs5Cpy-1603287889868)(https://camo.githubusercontent.com/52faa10d798a9e6515336bd671489a7ba4148246/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f323031392d372f537072696e674d56432545342542432541302545372542422539462545352542372541352545342542442539432545362542352538312545372541382538422e706e67)]
@RestController 返回 JSON 或 XML 形式数据
但@RestController只返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gH9Hn0QG-1603287889869)(https://camo.githubusercontent.com/a6239d8cef1ae38f55ce0b5dd5c7cbe2aa8a18e7/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f323031392d372f537072696e674d564352657374436f6e74726f6c6c65722e706e67)]
@Controller +@ResponseBody 返回JSON 或 XML 形式数据
如果你需要在 Spring4之前开发 RESTful Web 服务的话,你需要使用@Controller 并结合@ResponseBody注解,也就是说@Controller +@ResponseBody= @RestController(Spring 4 之后新加的注解)。
@ResponseBody 注解的作用是将 Controller 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到HTTP 响应(Response)对象的 body 中,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jxWSYcPb-1603287889871)(https://camo.githubusercontent.com/c982692143e38f147e46a24b16ea9125e7e634a8/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f323031392d372f537072696e67332e784d56435245535466756c5765622545362539432538442545352538412541312545352542372541352545342542442539432545362542352538312545372541382538422e706e67)]
@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
如果一个类或者一个类中的 public 方法上被标注@Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。
若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。
这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。
推荐阅读:Spring事务 和 Spring事务失效的 8 大原因,这次可以吊打面试官了!
Spring事务同步机制,推荐阅读:Spring是如何保证同一事务获取同一个Connection的?使用Spring的事务同步机制解决:数据库刚插入的记录却查询不到的问题【享学Spring】
首先,在项目启动类上添加 @EnableScheduling 注解,开启对定时任务的支持
@SpringBootApplication @EnableScheduling public class ScheduledApplication { public static void main(String[] args) { SpringApplication.run(ScheduledApplication.class, args); } }其中 @EnableScheduling 注解的作用是发现注解@Scheduled 的任务并后台执行。
其次,编写定时任务类和方法,定时任务类通过 Spring IOC 加载,使用 @Component 注解,定时方法使用 @Scheduled 注解。
@Component public class ScheduledTask { @Scheduled(fixedRate = 3000) public void scheduledTask() { System.out.println("任务执行时间:" + LocalDateTime.now()); } }fixedRate 是 long 类型,表示任务执行的间隔毫秒数,以上代码中的定时任务每 3 秒执行一次。
在上面的入门例子中,使用了@Scheduled(fixedRate = 3000) 注解来定义每过 3 秒执行的任务,对于 @Scheduled 的使用可以总结如下几种方式:
@Scheduled(fixedRate = 3000) :上一次开始执行时间点之后 3 秒再执行(fixedRate 属性:定时任务开始后再次执行定时任务的延时(需等待上次定时任务完成),单位毫秒)@Scheduled(fixedDelay = 3000) :上一次执行完毕时间点之后 3 秒再执行(fixedDelay 属性:定时任务执行完成后再次执行定时任务的延时(需等待上次定时任务完成),单位毫秒)@Scheduled(initialDelay = 1000, fixedRate = 3000) :第一次延迟1秒后执行,之后按fixedRate的规则每 3 秒执行一次(initialDelay 属性:第一次执行定时任务的延迟时间,需配合fixedDelay或者fixedRate来使用)@Scheduled(cron=“0 0 2 1 * ? *”) :通过cron表达式定义规则其中,常用的cron表达式有:
0 0 2 1 * ? * :表示在每月 1 日的凌晨 2 点执行0 15 10 ? * MON-FRI :表示周一到周五每天上午 10:15 执行0 15 10 ? 6L 2019-2020 :表示 2019-2020 年的每个月的最后一个星期五上午 10:15 执行0 0 10,14,16 * * ? :每天上午 10 点,下午 2 点,4 点执行0 0/30 9-17 * * ? :朝九晚五工作时间内每半小时执行0 0 12 ? * WED :表示每个星期三中午 12 点执行0 0 12 * * ? :每天中午 12点执行0 15 10 ? * * :每天上午 10:15 执行0 15 10 * * ? :每天上午 10:15 执行0 15 10 * * ? * :每天上午 10:15 执行0 15 10 * * ? 2019 :2019 年的每天上午 10:15 执行5 种常见的请求类型:
GET :请求从服务器获取特定资源。举个例子:GET /users(获取所有学生)POST :在服务器上创建一个新的资源。举个例子:POST /users(创建学生)PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /users/12(更新编号为 12 的学生)DELETE :从服务器删除特定的资源。举个例子:DELETE /users/12(删除编号为 12 的学生)PATCH :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。@GetMapping("users") 等价于@RequestMapping(value="/users",method=RequestMethod.GET)
@GetMapping("/users") public ResponseEntity<List<User>> getAllUsers() { return userRepository.findAll(); }@PostMapping("users") 等价于@RequestMapping(value="/users",method=RequestMethod.POST)
@PostMapping("/users") public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) { return userRespository.save(user); }@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) { ...... }@DeleteMapping("/users/{userId}")等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)
@DeleteMapping("/users/{userId}") public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){ ...... }一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。
@PatchMapping("/profile") public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) { studentRepository.updateDetail(studentUpdateRequest); return ResponseEntity.ok().build(); }@PathVariable用于获取路径参数,@RequestParam用于获取查询参数。
@GetMapping("/klasses/{klassId}/teachers") public List<Teacher> getKlassRelatedTeachers( @PathVariable("klassId") Long klassId, @RequestParam(value = "type", required = false) String type ) { ... }如果我们请求的 url 是:/klasses/{123456}/teachers?type=web
那么我们服务获取到的数据就是:klassId=123456,type=web。
用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。
我们有一个注册的接口:
@PostMapping("/sign-up") public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) { userService.save(userRegisterRequest); return ResponseEntity.ok().build(); }UserRegisterRequest对象:
@Data @AllArgsConstructor @NoArgsConstructor public class UserRegisterRequest { @NotBlank private String userName; @NotBlank private String password; @FullName @NotBlank private String fullName; }我们发送 post 请求到这个接口,并且 body 携带 JSON 数据:
{"userName":"coder","fullName":"shuangkou","password":"123456"}这样我们的后端就可以直接把 json 格式的数据映射到我们的 UserRegisterRequest 类上。
如果没有对应的封装类,则可以将数据绑定到 Map 对象上,如下述代码所示:
@RequestMapping("/getProductsByXx") public List<Product> queryByXx(@RequestBody Map<String,Object> objectMap){ String productName = null; if(objectMap.get("eqType") != null){ productName = objectMap.get("eqType").toString(); } Integer productId = null; if(objectMap.get("materialCode") != null){ productId = Integer.parseInt(objectMap.get("materialCode").toString()); } return productMapper.queryByXx(productId,productName); }数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!
校验的时候我们实际用的是 Hibernate Validator 框架。SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。
需要注意的是: 所有的注解,推荐使用 JSR 注解,即javax.validation.constraints,而不是org.hibernate.validator.constraints
我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException。
@RestController @RequestMapping("/api") public class PersonController { @PostMapping("/person") public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) { return ResponseEntity.ok().body(person); } }一定一定不要忘记在类上加上 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); } }更多关于如何在 Spring 项目中进行参数校验的内容,请看《如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里!》这篇文章。
介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。
相关注解:
@ControllerAdvice :注解定义全局异常处理类@ExceptionHandler :注解声明异常处理方法如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出MethodArgumentNotValidException,我们来处理这个异常。
@ControllerAdvice @ResponseBody public class GlobalExceptionHandler { /** * 请求参数异常处理 */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { ...... } }更多关于 Spring Boot 异常处理的内容,请看我的这两篇文章:
SpringBoot 处理异常的几种常见姿势使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!根据注解使用的位置,分为字段注解、方法、类注解。
字段注解一般是用于校验字段是否满足要求,hibernate-validate依赖就提供了很多校验注解 ,如@NotNull、@Range等,但是这些注解并不是能够满足所有业务场景的。
首先通过@interface 声明一个注解类,以及类上的 @Target 、 @Retention 和 @Constraint 注解;
创建一个验证器类,需要实现 ConstraintValidator 泛型接口。第一个泛型参数类型Check:注解,第二个泛型参数Object:校验字段类型。需要实现initialize和isValid方法,isValid方法为校验逻辑,initialize方法初始化工作。
在开发过程中遇到过这样的需求,如只有有权限的用户的才能访问这个类中的方法或某个具体的方法、查找数据的时候先不从数据库查找,先从guava cache中查找,在从redis查找,最后查找mysql(多级缓存)。
首先通过@interface 声明一个注解类,以及类上的 @Target 、 @Retention注解;
在拦截器中获取该注解配置的参数,然后在 preHandle() 方法中对参数值进行判断,如果满足则通过。
推荐阅读:Spring自定义注解从入门到精通
https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/spring/spring-annotations.md