1 注解
1.1 Spring
01.分类1
@Controller 组合注解(组合了@Component注解),应用在MVC层(控制层),DispatcherServlet会自动扫描注解了此注解的类,然后将web请求映射到注解了@RequestMapping的方法上。
@Service 组合注解(组合了@Component注解),应用在service层(业务逻辑层)
@Reponsitory 组合注解(组合了@Component注解),应用在dao层(数据访问层)
@Component 表示一个带注释的类是一个“组件”,成为Spring管理的Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解。
@Autowired @Autowired可以用于属性注入、setter注入、构造方法注入的依赖注入形式
@Resource JSR-250提供的注解
@Inject JSR-330提供的注解
@Configuration 声明当前类是一个配置类(相当于一个Spring配置的xml文件)
@ComponentScan 自动扫描指定包下所有使用@Service,@Component,@Controller,@Repository的类并注册
02.分类2
@Bean 注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy。
@Aspect 声明一个切面(就是说这是一个额外功能)
@After 后置建言(advice),在原方法前执行。
@Before 前置建言(advice),在原方法后执行。
@Around 环绕建言(advice),在原方法执行前执行,在原方法执行后再执行(@Around可以实现其他两种advice)
@PointCut 声明切点,即定义拦截规则,确定有哪些方法会被切入
@Transactional 声明事务(一般默认配置即可满足要求,当然也可以自定义)
@Cacheable 声明数据缓存
@EnableAspectJAutoProxy 开启Spring对AspectJ的支持
@Value 值得注入。经常与Sping EL表达式语言一起使用,注入普通字符,系统属性,表达式运算结果,其他Bean的属性,文件内容,网址请求内容,配置文件属性值等等
@PropertySource 指定文件地址。提供了一种方便的、声明性的机制,用于向Spring的环境添加PropertySource。与@configuration类一起使用。
@PostConstruct 标注在方法上,该方法在构造函数执行完成之后执行。
@PreDestroy 标注在方法上,该方法在对象销毁之前执行。
03.分类3
@Profile 表示当一个或多个指定的文件是活动的时,一个组件是有资格注册的。使用@Profile注解类或者方法,达到在不同情况下选择实例化不同的Bean。@Profile(“dev”)表示为dev时实例化。
@EnableAsync 开启异步任务支持。注解在配置类上。
@Async 注解在方法上标示这是一个异步方法,在类上标示这个类所有的方法都是异步方法。
@EnableScheduling 注解在配置类上,开启对计划任务的支持。
@Scheduled 注解在方法上,声明该方法是计划任务。支持多种类型的计划任务:cron,fixDelay,fixRate
@Conditional 根据满足某一特定条件创建特定的Bean
@Enable* 通过简单的@Enable*来开启一项功能的支持。所有@Enable*注解都有一个@Import注解,@Import是用来导入配置类的,这也就意味着这些自动开启的实现其实是导入了一些自动配置的Bean(1.直接导入配置类2.依据条件选择配置类3.动态注册配置类)
@RunWith 这个是Junit的注解,springboot集成了junit。一般在测试类里使用:@RunWith(SpringJUnit4ClassRunner.class) — SpringJUnit4ClassRunner在JUnit环境下提供Sprng TestContext Framework的功能
@ContextConfiguration 用来加载配置ApplicationContext,其中classes属性用来加载配置类:@ContextConfiguration(classes = {TestConfig.class(自定义的一个配置类)})
@ActiveProfiles 用来声明活动的profile–@ActiveProfiles(“prod”(这个prod定义在配置类中))
@EnableWebMvc 用在配置类上,开启SpringMvc的Mvc的一些默认配置:如ViewResolver,MessageConverter等。同时在自己定制SpringMvc的相关配置时需要做到两点:1.配置类继承WebMvcConfigurerAdapter类2.就是必须使用这个@EnableWebMvc注解。
04.分类4
@RequestMapping 用来映射web请求(访问路径和参数),处理类和方法的。可以注解在类和方法上,注解在方法上的@RequestMapping路径会继承注解在类上的路径。同时支持Serlvet的request和response作为参数,也支持对request和response的媒体类型进行配置。其中有value(路径),produces(定义返回的媒体类型和字符集),method(指定请求方式)等属性。 |
@ResponseBody 将返回值放在response体内。返回的是数据而不是页面
@RequestBody 允许request的参数在request体中,而不是在直接链接在地址的后面。此注解放置在参数前。
@PathVariable 放置在参数前,用来接受路径参数。
@RestController 组合注解,组合了@Controller和@ResponseBody,当我们只开发一个和页面交互数据的控制层的时候可以使用此注解。
@ControllerAdvice 用在类上,声明一个控制器建言,它也组合了@Component注解,会自动注册为Spring的Bean。
@ExceptionHandler 用在方法上定义全局处理,通过他的value属性可以过滤拦截的条件:@ExceptionHandler(value=Exception.class)–表示拦截所有的Exception。
@ModelAttribute 将键值对添加到全局,所有注解了@RequestMapping的方法可获得次键值对(就是在请求到达之前,往model里addAttribute一对name-value而已)。
@InitBinder 通过@InitBinder注解定制WebDataBinder(用在方法上,方法有一个WebDataBinder作为参数,用WebDataBinder在方法内定制数据绑定,例如可以忽略request传过来的参数Id等)。
@WebAppConfiguration 一般用在测试上,注解在类上,用来声明加载的ApplicationContext是一个WebApplicationContext。他的属性指定的是Web资源的位置,默认为src/main/webapp,我们可以修改为:@WebAppConfiguration(“src/main/resources”)。
@EnableAutoConfiguration 此注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。该注解组合了@Import注解,@Import注解导入了EnableAutoCofigurationImportSelector类,它使用SpringFactoriesLoader.loaderFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。而spring.factories里声明了有哪些自动配置。
@SpingBootApplication SpringBoot的核心注解,主要目的是开启自动配置。它也是一个组合注解,主要组合了@Configurer,@EnableAutoConfiguration(核心)和@ComponentScan。可以通过@SpringBootApplication(exclude={想要关闭的自动配置的类名.class})来关闭特定的自动配置。
@ImportResource 虽然Spring提倡零配置,但是还是提供了对xml文件的支持,这个注解就是用来加载xml配置的。例:@ImportResource({“classpath
@ConfigurationProperties 将properties属性与一个Bean及其属性相关联,从而实现类型安全的配置。例:@ConfigurationProperties(prefix=”authot”,locations={“classpath
05.分类5
@ConditionalOnBean 条件注解。当容器里有指定Bean的条件下。
@ConditionalOnClass 条件注解。当类路径下有指定的类的条件下。
@ConditionalOnExpression 条件注解。基于SpEL表达式作为判断条件。
@ConditionalOnJava 条件注解。基于JVM版本作为判断条件。
@ConditionalOnJndi 条件注解。在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean 条件注解。当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass 条件注解。当类路径下没有指定的类的情况下。
@ConditionalOnNotWebApplication 条件注解。当前项目不是web项目的条件下。
@ConditionalOnResource 条件注解。类路径是否有指定的值。
@ConditionalOnSingleCandidate 条件注解。当指定Bean在容器中只有一个,后者虽然有多个但是指定首选的Bean。
@ConditionalOnWebApplication 条件注解。当前项目是web项目的情况下。
@EnableConfigurationProperties 注解在类上,声明开启属性注入,使用@Autowired注入。例:@EnableConfigurationProperties(HttpEncodingProperties.class)。
@AutoConfigureAfter 在指定的自动配置类之后再配置。例:@AutoConfigureAfter(WebMvcAutoConfiguration.class)
1.2 SpringMVC
00.总结
a.第1种:前后端不分离设计
开发者直接操作 HttpServletRequest、HttpSession 和 Model,将数据交由服务器端模版引擎渲染 HTML 响应。
b.第2种:RESTful风格
倾向于利用参数绑定注解,返回 JSON 数据,通过前端框架实现页面渲染
c.@Controller注解
在Spring MVC 中,控制器Controller负责处理由DispatcherServlet分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,
然后再把该Model返回给对应的View进行展示。在SpringMVC中提供了一个非常简便的定义Controller的方法,
你无需继承特定的类或实现特定的接口,只需使用@Controller标记一个类是Controller ,
然后使用@RequestMapping和@RequestParam等一些注解用以定义URL请求和Controller方法之间的映射,这样的Controller就能被外界访问到。
此外Controller 不会直接依赖于HttpServletRequest和HttpServletResponse等HttpServlet对象,它们可以通过Controller的方法参数灵活的获取到。
-----------------------------------------------------------------------------------------------------
@Controller 用于标记在一个类上,使用它标记的类就是一个Spring MVC Controller对象。
分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping注解。
@Controller只是定义了一个控制器类,而使用@RequestMapping注解的方法才是真正处理请求的处理器。
单单使用@Controller标记在一个类上还不能真正意义上的说它就是Spring MVC的一个控制器类,
因为这个时候Spring还不认识它。那么要如何做Spring 才能认识它呢?
-----------------------------------------------------------------------------------------------------
这个时候就需要我们把这个控制器类交给Spring来管理
有两种方式:第1种:在SpringMVC的配置文件中定义MyController的bean对象
第2种:在SpringMVC的配置文件中告诉Spring该到哪里去找标记为@Controller的Controller控制器
01.rest风格
a.第1步:类注解
@Controller / @RestController = @Controller + @ResponseBody --第1步:只定义了一个控制器类
@RequestMapping("/sys/dataLog") --第2步:真正处理请求的处理器
-----------------------------------------------------------------------------------------------------
全部小写,并采用短横线分隔(如 /sys/data-log)
包扫描文件位置:@SpringBootApplication(scanBasePackages = {"package1", "package2"})明确指定扫描的包
b.第2步:方法注解
@RequestMapping(value = "/list", method = RequestMethod.GET / RequestMethod.POST)
-----------------------------------------------------------------------------------------------------
Spring 4.x 后新增了一系列快捷注解,这些注解是 @RequestMapping 的特化版本,直接绑定到对应 HTTP 方法
@GetMapping @RequestMapping(value = "/{id}", method = RequestMethod.GET)
@PostMapping @RequestMapping(value = "/{id}", method = RequestMethod.POST)
@PutMapping @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
@DeleteMapping @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
c.第3步:映射 请求参数/路径/请求体
@RequestParam 用于将请求参数映射到方法参数
@PathVariable 用于将URL路径中的变量映射到方法参数
@RequestBody 用于将【HTTP请求的正文内容】转换为【Java对象】,通常用于【接收】客户端发送的数据。
d.第4步:定义返回内容
@ResponseBody 用于将【控制器方法的返回值】直接写入【HTTP响应正文】中,通常用于【返回数据】给客户端。
@RestController @Controller + @ResponseBody;Spring MVC会根据客户端发送的"ACCEPT"请求头来决定返回的数据格式,常见的格式包括JSON、XML
e.其他
@RequestHeader 注解用于获取HTTP请求的头部信息
@CookieValue 用于获取HTTP请求的Cookie值
-----------------------------------------------------------------------------------------------------
@ModelAttribute 用于绑定方法参数或方法返回值到模型中
@SessionAttributes 用于指定模型属性的值应该被存储在会话中,以便在多个请求之间共享
f.示例:rest风格
a.@RequestParam
curl -X GET "http://localhost:8080/user?name=Tom"
-------------------------------------------------------------------------------------------------
@GetMapping("/user")
public String getUser(@RequestParam("name") String name) {
// 根据请求参数 name 处理业务
return "User: " + name;
}
b.@PathVariable
curl -X GET "http://localhost:8080/user/123"
-------------------------------------------------------------------------------------------------
@GetMapping("/user/{id}")
public String getUser(@PathVariable("id") Long id) {
// 根据 URL 中的 id 处理业务
return "User ID: " + id;
}
c.@RequestBody
curl -X POST "http://localhost:8080/user" \
-H "Content-Type: application/json" \
-d '{"id":123, "name":"Alice", "age":30}'
-------------------------------------------------------------------------------------------------
@PostMapping("/user")
public ResponseEntity<?> createUser(@RequestBody User user) {
// 处理请求体中的 JSON 数据,转换为 User 对象
return ResponseEntity.ok(user);
}
02.前后端不分离风格
a.数据交互与返回值
a.前后端不分离
返回视图:控制器返回视图名称,服务器负责将 Model 中的数据渲染到 HTML 模板中。
数据传递:数据封装在 Model 中,直接传递给服务器渲染的网页。
b.RESTful 风格
返回 JSON / XML:通常使用注解 @RestController(或 @Controller 加 @ResponseBody)返回 JSON 格式数据。
数据传递:前端通过 Ajax 或 API 调用获取数据,前后端相对解耦,前端模板或 SPA 框架负责页面渲染。
b.参数传递方式
a.前后端不分离
常通过 HttpServletRequest 自动获取参数,直接在方法内部手工转换。
比如:String username = request.getParameter("username");
参数绑定方式较为“原始”,需要开发者自行做类型转换、校验等工作。
b.RESTful 风格
利用 Spring MVC 的自动绑定特性,如:
@RequestParam(绑定 URL 参数)
@PathVariable(绑定 URL 路径中的变量)
@RequestBody(将请求体中的 JSON 转换为 Java 对象)
这种方式更简洁、易读,也便于统一做异常处理和数据验证。
c.会话管理
a.前后端不分离时
直接使用 HttpSession 进行状态管理,常见于传统 Web 应用。
会话状态与服务器紧密耦合,适用于服务端渲染场景。
b.RESTful 风格
尽量保持无状态(stateless),通常采用 token(如 JWT)的方式来进行身份验证,服务器不维护太多会话状态。
前后端分离架构倾向于 REST API 的无状态特性,更适合横向扩展与微服务架构。
d.视图解析与前端技术栈
a.前后端不分离
依赖服务器端模板(JSP、Thymeleaf 等)生成 HTML,进行页面渲染。
前端逻辑较简单,主要是接受服务器渲染好的页面。
b.RESTful 风格
由前端(如 React、Vue、Angular 等)渲染页面,后端仅负责提供数据接口。
前后端职责明确,资源路径和状态码等信息以 REST 方式进行统一管理。
e.示例:前后端不分离风格
@Controller
@RequestMapping(value = "/Admin")
public class AdminController {
@GetMapping(value = "/deleteCompanyProfile")
public String deleteCompanyProfile(HttpServletRequest request, HttpSession session, Model model) {
model.addAttribute("configs", config());
String admin = (String) session.getAttribute("admin");
String id = request.getParameter("id");
if (null != admin) {
if (admin.equals("admin")) {
Integer status = aboutJgService.deleteCompanyProfile(id);
return redirectIndexPage(model);
} else {
return "admin/login.html";
}
} else {
return "admin/login.html";
}
}
@PostMapping(value = "/loginAction")
public String loginAction(HttpServletRequest request, HttpSession session, Model model) {
model.addAttribute("configs", config());
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username.equals(systemUsername) && password.equals(systemPassword)){
session.setAttribute("admin","admin");
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
Browser browser = userAgent.getBrowser();
OperatingSystem os = userAgent.getOperatingSystem();
Timestamp ts = new Timestamp(new Date().getTime());
try {
adminService.recordLog(request.getRemoteAddr(), os.toString(), browser.toString(), "admin", ts);
} catch (Exception e) {
// Todo
} finally {
return "redirect:index.html";
}
}
else{
model.addAttribute("msg","账号或密码错误!");
return "admin/login.html";
}
}
}
03.类级别注解的选择
a.控制器注解:
@Controller:用于传统的 MVC 控制器(返回视图),需要在需要返回 JSON 时配合 @ResponseBody 使用。
@RestController:是 @Controller + @ResponseBody 的组合,常用于 REST API,所有方法默认返回 JSON 数据。
b.类级别 URL 绑定(可选)
a.单路径映射
使用 @RequestMapping 为控制器类设置统一的 URL 前缀
@RestController
@RequestMapping("/sys/dataLog")
public class DataLogController { ... }
b.多路径映射
@RestController
@RequestMapping({"/api", "/service"})
public class CommonController { ... }
04.方法级别映射注解的选择
a.通用映射注解:@RequestMapping
a.基本用法
a.指定 URL
@RequestMapping(value = "/list")
public ResponseEntity<?> list() { ... }
b.指定 HTTP 方法
@RequestMapping(value = "/list", method = RequestMethod.GET)
public ResponseEntity<?> list() { ... }
c.多路径/数组方式
@RequestMapping(value = {"/list", "/getList"}, method = RequestMethod.GET)
public ResponseEntity<?> list() { ... }
b.条件映射
a.参数条件
@RequestMapping(value = "/info", params = "version=1")
public ResponseEntity<?> info() { ... }
b.Header 条件
@RequestMapping(value = "/info", headers = "X-API-VERSION=1")
public ResponseEntity<?> info() { ... }
c.Consumes/Produces
@RequestMapping(value = "/upload",
method = RequestMethod.POST,
consumes = "multipart/form-data",
produces = "application/json")
public ResponseEntity<?> upload() { ... }
b.快捷映射注解(Spring 4.x 后新增了一系列快捷注解,这些注解是 @RequestMapping 的特化版本,直接绑定到对应 HTTP 方法)
a.@GetMapping:仅匹配 GET 请求
@GetMapping("/list")
public ResponseEntity<?> list() { ... }
b.@PostMapping:仅匹配 POST 请求
@PostMapping("/upload")
public ResponseEntity<?> upload() { ... }
c.@PutMapping:仅匹配 PUT 请求
@PutMapping("/update")
public ResponseEntity<?> update() { ... }
d.@DeleteMapping:仅匹配 DELETE 请求
@DeleteMapping("/delete")
public ResponseEntity<?> delete() { ... }
e.@PatchMapping:仅匹配 PATCH 请求
@PatchMapping("/modify")
public ResponseEntity<?> modify() { ... }
c.路径定义的其他方式
a.路径变量:使用 {} 来定义 URL 模板变量,并通过方法参数接收。
@GetMapping("/item/{id}")
public ResponseEntity<?> getItem(@PathVariable("id") Long id) { ... }
b.通配符匹配:如 * 和 ** 用于 Ant 风格路径匹配。
@GetMapping("/static/**")
public ResponseEntity<?> getStatic() { ... }
c.混合使用类级与方法级:
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping("/{userId}")
public ResponseEntity<?> getUser(@PathVariable Long userId) { ... }
@PostMapping("/create")
public ResponseEntity<?> createUser(@RequestBody User user) { ... }
// 使用通用方式同时支持多种 HTTP 方法
@RequestMapping(value = "/list", method = {RequestMethod.GET, RequestMethod.POST})
public ResponseEntity<?> listUsers() { ... }
}
1.3 SpringBoot
01.SpringBoot自动装配原理:
SpringBoot通过@SpringBootApplication核心注解进行启动
@SpringBootApplication = @SpringBootConfiguration + @ComponentScan + @EnableAutoConfiguration:
其中,@SpringBootConfiguration:指定该类是SpringBoot的配置类
@ComponentScan:扫描该类所在的包下所有的类,并把符合扫描规则的类自动装配到容器
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项;
如关闭数据源配置,@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
根据pom.xml中依赖,自动推测出所需配置,并自动配置好。
例如,pom.xml中有spring-boot-starter-web,自动将web和SpringMVC配置好。
02.@SpringBootApplication:
@SpringBootConfiguration:@Configuration配置类,自动纳入Spring容器
@EnableAutoConfiguration:开启自动化配置,也可以自定义配置代替自动化配置中的某一个配置
@AutoConfigurationPackage:自己写的,自动将该包及其所有的子包纳入Spring容器
@Import(AutoConfigurationImportSelector.class):自动装配META-INF/spring.factories
@ComponentScan:扫描包
@Service:业务层
@Repository:数据层
@Controller:控制层
@RestController:控制层
@Component:泛指各种组件
@value:单值注入,更加精准,而不是让Spring自动执行它
@Configuration:配置类,一般和@value组合使用
03.@ConfigurationProperties(prefix = "student")与@value:二者可以互补使用
@ConfigurationProperties:同时绑定properties和yml方式的注值,支持批量注值
@value:支持单个注值
04.Spring、SpringMVC、SpringBoot常见注解
a.元注解
@Documented 将会在被此注解注解的元素的javadoc文档中列出注解,一般都打上这个注解没坏处
@Target 注解能被应用的目标元素,比如类、方法、属性、参数等等,需要仔细思考
@Retention 仅在源码保留,还是保留到编译后的字节码,还是到运行时也去加载,超过90%的应用会在运行时去解析注解进行额外的处理,所以大部分情况我们都会设置配置为RetentionPolicy.RUNTIME
@Inherited 如果子类没有定义注解的话,能自动从父类获取定义了继承属性的注解,比如Spring的@Service是没有继承特性的,但是@Transactional是有继承特性的,在OO继承体系中使用Spring注解的时候请特别注意这点,理所当然认为注解是能被子类继承的话可能会引起不必要的Bug,需要仔细斟酌是否开启继承
@Repeatable Java 8引入的特性,通过关联注解容器定义可重复注解,小小语法糖提高了代码可读性,对于元素有多个重复注解其实是很常见的事情,比如某方法可以是A角色可以访问也可以是B角色可以访问,某方法需要定时任务执行,要在A条件执行也需要在B条件执行
@Native 是否在.h头文件中生成被标记的字段,除非原生程序需要和Java程序交互,否则很少会用到这个元注解
b.启动注解
@SpringBootApplication 包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解
@SpringBootConfiguration 等同于spring的XML配置文件;使用Java代码可以检查类型安全
@ComponentScan 让spring Boot扫描到Configuration类并把它加入到程序上下文
@EnableAutoConfiguration 自动配置
c.配置导入功能
@Configuration 等同Spring的XML配置文件,【指明该类是Bean配置的信息源】,相当于<beans></beans> 仅主类
@Bean 等同Spring的XML配置文件,【产生一个bean交给Spring管理】,相当于<bean></bean> 仅方法
-----------------------------------------------------------------------------------------------------
@Import 导入其他配置类
@PropertySource 加载非默认配置文件的数据
@ImportResource 识别除application.properties/yml等其他配置文件,默认Springboot不识别
-----------------------------------------------------------------------------------------------------
@Autowired 自动导入依赖的bean,byType方式,完成属性、方法的组装,对类成员变量、方法、构造函数声明
@Resource(name="name", type="type") 没有括号内内容的话,默认byName方式,作用同@Autowired
@inject 等价于默认的@Autowired,只是没有required属性
d.业务层功能
@Component 泛指组件,当组件不好归类时,使用该注解
@Repository DAO层
@Service Service层
@Controller Controller层
-----------------------------------------------------------------------------------------------------
@Controller 定义控制器类,配合注解@RequestMapping
@RestController @RestController = @ResponseBody + @Controller
@RequestMapping 提供路由信息,负责URL到Controller中的具体函数的映射
params:指定request中必须包含某些参数值是,才让该方法处理
headers:指定request中必须包含某些指定的header值,才能让该方法处理请求
value:指定请求的实际地址,指定的地址可以是URI Template 模式
method:指定请求的method类型, GET、POST、PUT、DELETE等
consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html
produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
@ResponseBody 表示该方法的返回结果直接写入HTTP response body中,
一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,
加上@ResponseBody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中
比如异步获取json数据,加上@Responsebody后,会直接返回json数据。
-----------------------------------------------------------------------------------------------------
@Value 注入 application.properties 或 application.yml 配置的属性的值
@PathVariable 路径变量,参数与大括号里的名字一样要相同
@Profiles 配置在dev、test、prod环境下生效,任何@Component或@Configuration都能被@Profiles标记
@ConfigurationProperties SpringBoot将尝试校验外部的配置,默认使用JSR-303进行校验配置
e.HTTP注解
@RequestBody HTTP请求获取请求体(处理复杂数据,比如JSON)
@RequestHeader HTTP请求获取请求头
@CookieValue HTTP请求获取cookie
@SessionAttribute HTTP请求获取会话
@RequestAttribute HTTP请求获取请求的Attribute中(比如过滤器和拦截器手动设置的一些临时数据),
@RequestParam HTTP请求获取请求参数(处理简单数据,键值对)
@PathVariable HTTP请求获取路径片段
@MatrixAttribute HTTP请求获取矩阵变量允许我们采用特殊的规则在URL路径后加参数(分号区分不同参数,逗号为参数增加多个值)
f.全局异常处理
@ControllerAdvice 包含@Component,统一处理异常,处理控制器类抛出的所有异常
@ExceptionHandler(Exception.class) 用在方法上面,表示遇到这个异常就执行以下方法
g.其他注解
@Transient 表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性
@ConfigurationProperties 给对象赋值,将注解转换成对象
@RequestMapping 和请求报文是做对应的
@EnableCaching 注解驱动的缓存管理功能
@GeneratedValue 用于标注主键的生成策略,通过 strategy 属性指定
@JsonIgnore 作用是json序列化时将Java bean中的一些属性忽略掉,序列化和反序列化都受影响
@JoinColumn(name=”loginId”) 一对一:本表中指向另一个表的外键。一对多:另一个表指向本表的外键
1.4 MyBatis
00.汇总
a.映射
@Arg: 用于指定构造函数参数的映射
@AutomapConstructor: 自动映射构造函数参数
@ConstructorArgs: 指定构造函数参数的映射
@MapKey: 指定返回的 Map 集合中使用的键
@Result: 指定查询结果的映射
@ResultMap: 引用预定义的结果映射
@Results: 定义一组结果映射
@ResultType: 指定结果类型
@TypeDiscriminator: 用于多态结果映射
b.缓存
@CacheNamespace: 配置命名空间的缓存
@CacheNamespaceRef: 引用另一个命名空间的缓存配置
c.SQL
@Delete: 用于执行删除操作的 SQL 语句
@DeleteProvider: 提供动态删除 SQL 语句
@Insert: 用于执行插入操作的 SQL 语句
@InsertProvider: 提供动态插入 SQL 语句
@Select: 用于执行查询操作的 SQL 语句
@SelectKey: 在插入操作前或后执行的 SQL 语句
@SelectProvider: 提供动态查询 SQL 语句
@Update: 用于执行更新操作的 SQL 语句
@UpdateProvider: 提供动态更新 SQL 语句
d.其他
@Flush: 用于刷新缓存
@Lang: 指定使用的语言驱动
@Many: 指定一对多关系的映射
@Mapper: 标记接口为 MyBatis 映射器
@One: 指定一对一关系的映射
@Options: 配置 SQL 语句的执行选项
@Param: 指定参数名称
@Property: 配置属性
01.SQL语句映射
a.@Insert:实现新增功能
a.代码
@Insert("insert into user(id,name) values(#{id},#{name})")
public int insert(User user);
b.@Select:实现查询功能
a.代码
@Select("Select * from user")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "sex", property = "sex"),
@Result(column = "age", property = "age")
})
List<User> queryAllUser();
c.@SelectKey:插入后,获取id的值
a.说明
以 MySQL 为例,MySQL 在插入一条数据后,使用 select last_insert_id() 可以获取到自增 id 的值
b.代码
@Insert("insert into user(id,name) values(#{id},#{name})")
@SelectKey(statement = "select last_insert_id()",
keyProperty = "id",
keyColumn = "id",
resultType = int,
before = false)
public int insert(User user);
c.@SelectKey各个属性含义如下
statement:表示要运行的 SQL 语句
keyProperty:可选项,表示将查询结果赋值给代码中的哪个对象
keyColumn:可选项,表示将查询结果赋值给数据表中的哪一列
resultType:指定 SQL 语句的返回值
before:默认值为 true,在执行插入语句之前,执行 select last_insert_id()。值为 flase,则在执行插入语句之后,执行 select last_insert_id()
d.@Insert:实现插入功能
a.代码
@Insert("insert into user(name,sex,age) values(#{name},#{sex},#{age}")
int saveUser(User user);
e.@Update:实现更新功能
a.代码
@Update("update user set name= #{name},sex = #{sex},age =#{age} where id = #{id}")
void updateUserById(User user);
f.@Delete:实现删除功能
a.代码
@Delete("delete from user where id =#{id}")
void deleteById(Integer id);
g.@Param:映射多个参数
a.说明
@Param 用于在 Mapper 接口中映射多个参数
@Param 中的 value 属性可省略,用于指定参数的别名
b.代码
int saveUser(@Param(value="user") User user,@Param("name") String name,@Param("age") Int age);
02.结果集映射:@Result、@Results、@ResultMap
a.说明
@Result 注解用于定义单个字段的映射关系
@Results 注解用于定义多个字段的映射关系,并可以通过 id 属性为该映射关系指定一个唯一标识符
@ResultMap 注解用于引用已经定义好的结果集映射,避免重复定义相同的映射关系,提高代码的复用性
b.示例代码
@Select({"select id, name, class_id from student"})
@Results(id="studentMap", value={
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="name", property="name", jdbcType=JdbcType.VARCHAR),
@Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();
@Select({"select id, name, class_id from student where id = #{id}"})
@ResultMap("studentMap")
Student selectById(Integer id);
c.属性说明
id:@Results 注解的 id 属性用于标识该结果集映射的唯一标识符
value:@Results 注解的 value 属性用于定义多个 @Result 注解的集合
@Result:column:数据库表中的列名
property:实体类中的属性名
jdbcType:数据库字段的 JDBC 类型
id:标识该字段是否为主键,默认为 false
03.关系映射
a.@one:用于一对一关系映射
@Select("select * from student")
@Results({
@Result(id=true,property="id",column="id"),
@Result(property="name",column="name"),
@Result(property="age",column="age"),
@Result(property="address",column="address_id",one=@One(select="net.biancheng.mapper.AddressMapper.getAddress"))
})
public List<Student> getAllStudents();
b.@many:用于一对多关系映射
@Select("select * from t_class where id=#{id}")
@Results({
@Result(id=true,column="id",property="id"),
@Result(column="class_name",property="className"),
@Result(property="students", column="id", many=@Many(select="net.biancheng.mapper.StudentMapper.getStudentsByClassId"))
})
public Class getClass(int id);
1.5 MyBatisPlus
01.MyBatisPlus
a.对比MyBatis
1.更换成MybatisSqlSessionFactoryBean
2.继承一个父接口extends BaseMapper<Student>,之后就可以使用该接口中已经存在的CRUD方法
3.操作,通过注解将表(字段)-类(属性)
b.接口继承BaseMapper<>,无SQL映射文件
JDBC Dao接口 Dao实现类
MyBatis Mapper接口 SQL映射文件
MyBatis-PLus Mapper接口 extends BaseMapper<Student>,无需编写SQL映射文件
c.扫描器,将MyBatis所有接口转换为Mapper类,默认自动将接口实现
applicationContext.xml
-----------------------------------------------------------------------------------------------------
<!--MyBatis:只写接口,不写实现类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--MyBatis会自动将接口实现-->
<property name="basePackage" value="org.myslayers.mapper"/>
</bean>
d.获取StudentMapper对象
1.先获取springloc容器再读取StudentMapper.class
2.强制转换
-----------------------------------------------------------------------------------------------------
public static void query(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentMapper studentMapper = context.getBean("studentMapper", StudentMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("stu_no",4);
map.put("stu_name","fsf");
//select * from student where stu_no=? and stu_name=?
List<Student> students = studentMapper.selectByMap(map);
System.out.println(students);
}
e.数据设计,直接使用注解,不再像mybatis利用mapper.xml文件指定字段
a.对象属性--表的字段
@Tableld
@TableField
b.对象属性-表的字段:默认表名和类名一致
@TableName("tb_student")
f.命名规范
a.示例
private int stuNo;
private String stuName;
private int stuAge;
SQL:INSERT INTO student stu_no,stu_name,stu_age VALUES (?,?,?)
b.约定
类的是属性:stuName
表的字段:stu_name
遵循"约定”,自动stuName->stu_name
c.自增策略:增加数据,还会将db中自增的主键”回写“到原对象
Studentstudent new Student(stuName:"zz",stuAge:23);
int count studentMapper.insert(student);
System.out.println(student);
-------------------------------------------------------------------------------------------------
Student{stuNo=6,stuName='zz',stuAge=23}
02.MyBatisPlus注解
a.汇总
@TableName
@Tableld
@TableField
-----------------------------------------------------------------------------------------------------
@Version
@EnumValue
@TableLogic
@SqlParser
@KeySequence
@Interceptorlgnore
@OrderBy
b.@TableName
使用:用于指定实体类对应的数据库表名
示例:
@TableName("student")
public class Student {
// 类属性
}
说明:@TableName 注解用于将实体类与数据库表进行映射,指定表名为 student
c.@TableId
使用:用于指定实体类中的主键字段
示例:
@TableId(value = "id", type = IdType.AUTO)
private Long id;
说明:@TableId 注解指定 id 为主键,并使用自动增长策略
d.@TableField
使用:用于指定实体类字段与数据库表字段的映射关系
示例:
@TableField("name")
private String name;
说明:@TableField 注解将实体类的 name 属性映射到数据库表的 name 字段
e.@Version
使用:用于实现乐观锁
示例:
@Version
private Integer version;
说明:@Version 注解用于标识乐观锁版本字段,更新时自动增加版本号
f.@EnumValue
使用:用于枚举类型字段的映射
示例:
public enum Status {
@EnumValue
ACTIVE(1),
INACTIVE(0);
private final int value;
Status(int value) {
this.value = value;
}
}
说明:@EnumValue 注解用于指定枚举值在数据库中的存储值
g.@TableLogic
使用:用于实现逻辑删除
示例:
@TableLogic
private Integer deleted;
说明:@TableLogic 注解用于标识逻辑删除字段,删除操作时不会物理删除数据,而是更新该字段
h.@SqlParser
使用:用于控制 SQL 解析行为
示例:
@SqlParser(filter = true)
public void customMethod() {
// 自定义方法
}
说明:@SqlParser 注解用于指定方法是否应用 SQL 解析器
i.@KeySequence
使用:用于指定序列主键生成策略
示例:
@KeySequence("seq_user")
public class User {
// 类属性
}
说明:@KeySequence 注解用于指定使用数据库序列生成主键
j.@InterceptorIgnore
使用:用于忽略拦截器
示例:
@InterceptorIgnore(tenantLine = "true")
public void someMethod() {
// 方法体
}
说明:@InterceptorIgnore 注解用于指定方法忽略某些拦截器,如租户拦截器
k.@OrderBy
使用:用于指定排序字段
示例:
@OrderBy("age DESC")
private Integer age;
说明:@OrderBy 注解用于指定查询结果的排序规则
1.6 Lombok1
00.全部注解:默认使用非静态,非瞬态的属性,不会涉及到父类的属性和方法
a.@Date
@Getter --类/属性
@Setter --类/属性
@RequiredArgsConstructor --类,包含final和@NonNull注解的成员变量的构造器
@ToString --类
@EqualsAndHashCode(callSuper = false) --类(默认值为false,默认情况下仅使用子类中定义的属性来生成hashCode() 和equals()方法,且在生成的hashCode() 和equals()方法中不会调用父类的方法。)
-----------------------------------------------------------------------------------------------------
默认生成的equals和hashcode方法(不包含继承的父类属性方法),不能对“子类”生成,不适合map、set等集合使用
默认实现没有使用父类属性,若想调用父类的方法,则需要指定callSuper,@EqualsAndHashCode(callSuper=true)
b.常见注解
@NoArgsConstructor --类,无参
@AllArgsConstructor --类,有参
@Builder --类,构造器
@Log4j/@Slf4j --类,提供一个的Logger对象,变量名为log
c.@Accessors
@Data
@Accessors(fluent = true) --类,getter和setter是基础属性名,且setter返回当前对象
public class User {
private Long id;
private String name;
// 生成的getter和setter方法如下,方法体略
public Long id() {}
public User id(Long id) {}
public String name() {}
public User name(String name) {}
}
-----------------------------------------------------------------------------------------------------
@Data
@Accessors(chain = true) --类,链式方法,setter方法返回当前对象
public class User {
private Long id;
private String name;
// 生成的setter方法如下,方法体略
public User setId(Long id) {}
public User setName(String name) {}
}
-----------------------------------------------------------------------------------------------------
@Data
@Accessors(prefix = "p") --类,getter和setter忽视指定前缀(遵守驼峰命名)
class User {
private Long pId;
private String pName;
// 生成的getter和setter方法如下,方法体略
public Long getId() {}
public void setId(Long id) {}
public String getName() {}
public void setName(String name) {}
}
d.@SuperBuilder,1.18版引入
支持继承层次结构中的构建器模式。
自动生成标准的getter、setter、equals()、hashCode()和toString()方法
与Lombok的其他注解兼容
-----------------------------------------------------------------------------------------------------
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
@Getter
@ToString
@SuperBuilder
public class Parent {
private long id;
private String name;
@Getter
@ToString
@SuperBuilder
public static class Child extends Parent {
private String value;
}
}
e.推荐注解
@ApiModel(value = "org-myslayers-entity-RoleMenu")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@TableName(value = "sys_role_menu")
public class RoleMenu implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.INPUT)
@ApiModelProperty(value = "主键ID")
private Integer id;
/**
* 角色-菜单【角色ID】
*/
@TableField(value = "role_id")
@ApiModelProperty(value = "角色-菜单【角色ID】")
private Long roleId;
/**
* 角色-菜单【菜单ID】
*/
@TableField(value = "menu_id")
@ApiModelProperty(value = "角色-菜单【菜单ID】")
private Long menuId;
}
01.Setter-Getter方法
a.描述
Lombok生成的getter和setter方法在某些情况下与IDE(如IntelliJ IDEA)或框架(如MyBatis)生成的方法不一致,
特别是当属性名的第一个字母小写且第二个字母大写时
b.代码示例
@Data
public class DemoDto {
private String xName;
// 其他属性
}
public class DemoApp {
public static void main(String[] args) {
// 使用 @Data 注解生成的 get, set 方法, X 是大写的
DemoDto dto = new DemoDto();
dto.getXName(); // Lombok生成的方法
// 用IDEA生成的 get, set 方法, x 是小写的
dto.getxName(); // IDEA生成的方法
}
}
c.代码说明
Lombok生成的getter方法是getXName(),而IDEA生成的getter方法是getxName()
这种不一致可能导致框架(如MyBatis)无法正确识别getter和setter方法,导致数据读写失败
d.解决办法
修改属性名字,让第二个字母小写
对于这种特殊的属性,使用IDEA生成getter和setter方法
02.@EqualsAndHashCode和equals()方法
a.描述
当使用@EqualsAndHashCode(callSuper=true)注解时,如果父类是Object,Lombok生成的equals()方法可能会导致意外行为
b.代码示例
@Data
@EqualsAndHashCode(callSuper=true)
public class BaseVO {
private int id;
}
@Data
public class DerivedVO extends BaseVO {
private String name;
}
c.代码说明
如果父类是Object,Lombok生成的equals()方法只会在两个对象是同一个对象时返回true,否则总是返回false,无论它们的属性是否相同
d.解决办法
不使用@EqualsAndHashCode(callSuper=true)注解
去掉callSuper=true,如果父类是Object,推荐使用
重写父类的equals()方法,确保父类不会调用或使用类似实现的Object的equals()
03.@Data注解
a.描述
@Data注解包含@EqualsAndHashCode注解,可能导致子类忽略父类属性的比较
b.代码描述
@Data
public class Parent {
private int a;
}
@Data
public class Child extends Parent {
private int b;
public static void main(String[] args) {
Child child1 = new Child();
Child child2 = new Child();
child1.setA(1);
child2.setA(2);
child1.setB(1);
child2.setB(1);
System.out.println(child1.equals(child2)); // true
}
}
c.代码说明
Lombok生成的equals()方法只比较子类特有的属性,忽略了父类属性的比较,导致两个对象的equals()方法返回true
d.解决办法
使用@EqualsAndHashCode(callSuper=true)注解
自己重写equals()方法
04.@Builder注解
a.描述
当父类和子类都使用@Builder注解时,可能会导致编译错误
b.代码示例
@Getter
@ToString
public class Parent {
private long id;
private String name;
@Builder
@Getter
@ToString
static class Child extends Parent {
private String value;
}
}
c.代码说明
Lombok未考虑父类的字段,只考虑当前子类的字段,导致编译错误
d.解决方案
使用@SuperBuilder注解,同时注解父类和子类。
注意@SuperBuilder和@Builder在父类和子类中不能混用
05.@Data注解的hashCode和equals方法
a.描述
@Data注解默认包含@EqualsAndHashCode注解,可能导致hashCode和equals方法行为不符合预期
b.代码示例
@Data
public class DataTest {
private int code;
private String name;
public DataTest(int code, String name) {
this.code = code;
this.name = name;
}
public static void main(String[] args) {
DataTest dataTest1 = new DataTest(1, "name");
DataTest dataTest2 = new DataTest(1, "name");
System.out.println(dataTest1 == dataTest2); // false
Map<DataTest, String> map = new HashMap<>();
map.put(dataTest1, dataTest1.getName());
map.put(dataTest2, dataTest2.getName());
System.out.println(map.size()); // 1
}
}
c.代码说明
两个对象地址不一样,但由于@Data注解默认包含@EqualsAndHashCode注解,
重写了hashCode和equals方法,导致所有属性相同情况下hashCode相同,hashMap认为是同一个key。
d.解决方案
在需要比较父类属性时,显式使用@EqualsAndHashCode(callSuper=true)注解。
按需使用@Getter和@Setter注解,而不是使用@Data注解
1.7 Lombok2
00.Lombok的工作原理
a.编译时代码生成
Lombok 通过注解处理器(Annotation Processor)在编译时生成代码
它利用 Java 编译器的扩展机制,在编译阶段扫描代码中的 Lombok 注解,并根据注解生成相应的代码
b.字节码操作
Lombok 使用 ASM 字节码操作库,直接修改生成的字节码,确保生成的代码与手动编写的代码在运行时行为一致
01.自动生成 Getter 和 Setter
a.放置位置的灵活性
a.类级别注解
a.说明
@Getter 和 @Setter 注解可以放在类级别上,为类中所有非静态字段自动生成 getter 或 setter 方法
b.代码
@Getter
@Setter
public class Account {
private String name;
private int age;
}
c.说明
在这个例子中,Account 类会自动生成 getName()、getAge()、setName(String name) 和 setAge(int age) 方法
b.字段级别注解
a.说明
@Getter 和 @Setter 注解可以单独放在某个字段上,只为指定的字段生成 getter 或 setter 方法
b.代码
public class Account {
@Getter
@Setter
private String name;
private int age; // 不会为 age 生成 getter 或 setter 方法
}
b.AccessLevel:自定义访问权限
a.说明
默认情况下,Lombok 生成的 getter 和 setter 方法都是 public 的
如果需要自定义访问权限,可以通过 AccessLevel 属性来指定
b.代码
@Getter(AccessLevel.PRIVATE)
public class Account {
private String name;
private int age;
}
c.AccessLevel 的可选值包括
PUBLIC:生成 public 方法(默认值)
PACKAGE:生成无访问修饰符的方法(即包级私有)
PROTECTED:生成 protected 方法
PRIVATE:生成 private 方法
MODULE:仅限模块内使用(与 PACKAGE 类似)
NONE:不生成对应的方法,适合排除某些字段
c.onMethod:为生成的方法添加注解
a.说明
通过 onMethod 属性,可以在生成的 getter 或 setter 方法上添加额外的注解
b.代码
@Getter(onMethod_ = {@Deprecated})
private String name;
c.生成的代码如下
@Deprecated
@Generated
private String getName() {
return this.name;
}
d.onParam:为方法参数添加注解
a.说明
通过 onParam 属性,可以在生成的 setter 方法的参数上添加注解
b.代码
public class Example {
@Getter(onParam = @__({@Override}))
private String name;
@Getter(onParam = @__({@Deprecated}))
private int age;
}
c.说明
这会在生成的 setter 方法的参数上添加相应的注解
不过需要注意的是,onParam 属性的使用场景相对较少,且需要谨慎使用
e.懒初始化(Lazy)
a.说明
@Getter 和 @Setter 还支持 lazy 属性,用于实现懒初始化
b.代码
@Getter(lazy = true)
private final List<String> expensiveResource = initializeExpensiveResource();
private List<String> initializeExpensiveResource() {
// 模拟耗时的初始化操作
System.out.println("Initializing expensive resource...");
return new ArrayList<>();
}
c.说明
在这个例子中,expensiveResource 字段的值只有在第一次调用 getExpensiveResource() 方法时才会被初始化
从而避免了在对象创建时就进行耗时的操作
02.生成构造函数
a.@NoArgsConstructor:无参构造函数
a.说明
@NoArgsConstructor 注解用于生成一个无参构造函数
b.代码
@NoArgsConstructor(force = true)
public class Account {
private final String name;
private int age;
}
c.生成的代码如下
public class Account {
private final String name;
private int age;
public Account() {
this.name = null; // 默认值
this.age = 0; // 默认值
}
}
b.@RequiredArgsConstructor:生成必需参数的构造函数
a.说明
@RequiredArgsConstructor 注解用于生成一个构造函数,该构造函数包含所有 final 字段和标记为 @NonNull 的字段
b.代码
@RequiredArgsConstructor
public class Account {
private final String name;
private final int age;
private String email; // 非 final 字段,不会出现在构造函数中
}
c.生成的代码如下
public class Account {
private final String name;
private final int age;
private String email;
public Account(String name, int age) {
this.name = name;
this.age = age;
}
}
c.@AllArgsConstructor:全参构造函数
a.说明
@AllArgsConstructor 注解用于生成一个包含所有字段的构造函数
b.代码
@AllArgsConstructor(staticName = "with")
public class Account {
private String name;
private int age;
private int id;
}
c.生成的代码如下
public class Account {
private String name;
private int age;
private int id;
public Account(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
public static Account with(String name, int age, int id) {
return new Account(name, age, id);
}
}
d.使用静态工厂方法
Account account = Account.with("小明", 18, 1);
03.@ToString:重写 toString 方法
a.基本用法
@ToString 注解用于自动生成 toString 方法,让对象的字符串表示更加清晰易读
@ToString
@AllArgsConstructor
public class Account {
private String name;
private int age;
private int id;
}
public static void main(String[] args) {
Account account = new Account("小明", 18, 1);
System.out.println(account);
// 输出:Account(name=小明, age=18, id=1)
}
b.参数说明
a.includeFieldNames
是否在打印内容中带上对应字段名字。默认值为 true
@ToString(includeFieldNames = false)
public class Account {
private String name;
private int age;
}
输出:Account(小明, 18)
b.exclude
排除不需要打印的字段
@ToString(exclude = "id")
public class Account {
private String name;
private int age;
private int id;
}
输出:Account(name=小明, age=18)
c.of
设置哪些字段需要打印
@ToString(of = {"name", "age"})
public class Account {
private String name;
private int age;
private int id;
}
输出:Account(name=小明, age=18)
d. callSuper
不仅为当前类中所有字段生成 toString,同时还调用父类的 toString 方法进行拼接
@ToString(callSuper = true)
public class Account extends BaseAccount {
private String name;
private int age;
}
假设 BaseAccount 类的 toString 方法返回 BaseAccount(...),则输出可能是:Account(BaseAccount(...), name=小明, age=18)
e. doNotUseGetters
默认情况下,生成的 toString 方法会尽可能使用 getter 方法获取字段值
@ToString(doNotUseGetters = true)
public class Account {
private String name;
private int age;
}
f.onlyExplicitlyIncluded
开启后,将只为字段或 getter 方法上添加了 @ToString.Include 注解的属性生成 toString 方法
@ToString(onlyExplicitlyIncluded = true)
public class Account {
@ToString.Include
private String name;
private int age; // 不会出现在 toString 中
}
输出:Account(name=小明)
04.@EqualsAndHashCode:重写 equals 和 hashCode 方法
a. 基本用法
@EqualsAndHashCode 注解用于自动生成 equals 和 hashCode 方法
@EqualsAndHashCode
public class Account {
private String name;
private int age;
private int id;
}
b.参数说明
a.of
指定哪些字段需要包含在 equals 和 hashCode 方法中
@EqualsAndHashCode(of = {"name", "age"})
public class Account {
private String name;
private int age;
private int id; // 不会被包含在 equals 和 hashCode 中
}
b.exclude
排除某些字段,使其不被包含在 equals 和 hashCode 方法中
@EqualsAndHashCode(exclude = "id")
public class Account {
private String name;
private int age;
private int id; // 不会被包含在 equals 和 hashCode 中
}
c.callSuper
是否调用父类的 equals 和 hashCode 方法
@EqualsAndHashCode(callSuper = true)
public class Account extends BaseAccount {
private String name;
private int age;
}
d.doNotUseGetters
是否使用 getter 方法获取字段值
@EqualsAndHashCode(doNotUseGetters = true)
public class Account {
private String name;
private int age;
}
05.@Data:综合注解
a.基本用法
@Data 注解是一个非常强大的综合注解,它等价于同时使用 @ToString、@EqualsAndHashCode、@Getter、@Setter 和 @RequiredArgsConstructor
@Data
public class Account {
private String name;
private int age;
private int id;
}
-----------------------------------------------------------------------------------------------------
使用 @Data 后,Account 类会自动生成以下内容
name、age 和 id 的 getter 和 setter 方法
equals 和 hashCode 方法
toString 方法
一个包含所有 final 字段和 @NonNull 字段的构造函数
06.@Builder:建造者模式
a. 基本用法
@Builder 注解用于实现建造者模式,让对象的创建更加灵活和可读
@Builder
public class Account {
private String name;
private int age;
private int id;
}
-----------------------------------------------------------------------------------------------------
使用建造者模式创建对象:
Account account = Account.builder().name("小明").age(18).id(1).build();
b.参数说明
a.builderMethodName
设置生成的 builder 方法的名称
@Builder(builderMethodName = "create")
public class Account {
private String name;
private int age;
private int id;
}
-------------------------------------------------------------------------------------------------
使用自定义的 builder 方法:
Account account = Account.create().name("小明").age(18).id(1).build();
b.buildMethodName
设置生成的 build 方法的名称
@Builder(buildMethodName = "assemble")
public class Account {
private String name;
private int age;
private int id;
}
-------------------------------------------------------------------------------------------------
使用自定义的 build 方法
Account account = Account.builder().name("小明").age(18).id(1).assemble();
c.builderClassName
设置生成的建造者类的名称
@Builder(builderClassName = "AccountBuilder")
public class Account {
private String name;
private int age;
private int id;
}
d.toBuilder
生成一个用于将对象转回 Builder 的方法
@Builder(toBuilder = true)
public class Account {
private String name;
private int age;
private int id;
}
-------------------------------------------------------------------------------------------------
使用 toBuilder 方法
Account account = Account.builder().name("小明").age(18).id(1).build();
Account updatedAccount = account.toBuilder().name("小红").build();
e.setterPrefixes
设置生成的 setter 方法的前缀
-------------------------------------------------------------------------------------------------
@Builder(setterPrefixes = "with")
public class Account {
private String name;
private int age;
private int id;
}
-------------------------------------------------------------------------------------------------
使用自定义的 setter 方法
Account account = Account.builder().withName("小明").withAge(18).withId(1).build();
07.var 和 val:类型推断
a. 基本用法
从 Java 10 开始,Java 引入了 var 关键字,用于局部变量的类型推断
var name = "小明"; // 类型推断为 String
var age = 18; // 类型推断为 int
-----------------------------------------------------------------------------------------------------
在 Java 10 之前,可以通过 Lombok 提供的 val 关键字实现类似的功能
val name = "小明"; // 类型推断为 String,且不可修改
val age = 18; // 类型推断为 int,且不可修改
b.使用场景
a.var
用于局部变量的类型推断,变量可以被重新赋值
var list = new ArrayList<String>();
list = new LinkedList<String>(); // 可以重新赋值
b.val
用于局部变量的类型推断,变量不可被重新赋值,类似于 final
val list = new ArrayList<String>();
// list = new LinkedList<String>(); // 错误:不能重新赋值
08.资源释放和异常处理:@Cleanup 和 @SneakyThrows
a.@Cleanup:自动资源释放
a.说明
在 Java 中,资源管理是一个重要的问题,尤其是涉及到文件、网络连接等需要手动关闭的资源
b.代码
import lombok.Cleanup;
public static void main(String[] args) throws IOException {
@Cleanup InputStream inputStream = new FileInputStream("test.txt");
byte[] bytes = inputStream.readAllBytes();
System.out.println(new String(bytes));
}
c.优点
简洁性:@Cleanup 注解使得代码更加简洁,避免了冗长的 try-finally 或 try-with-resources 块
灵活性:可以应用于任何实现了 AutoCloseable 接口的资源
d.注意事项
@Cleanup 注解的资源必须是局部变量,不能是类的成员变量
如果需要处理异常,可以结合 @SneakyThrows 注解使用
b.@SneakyThrows:简化异常处理
a.说明
Java 的异常处理机制要求显式声明或处理异常,这有时会导致代码冗长和复杂
b.代码
import lombok.SneakyThrows;
@SneakyThrows
public static void main(String[] args) {
@Cleanup InputStream inputStream = new FileInputStream("test.txt");
byte[] bytes = inputStream.readAllBytes();
System.out.println(new String(bytes));
}
c.优点
简洁性:避免了显式声明 throws 或使用 try-catch 块
灵活性:可以在不改变方法签名的情况下处理异常
d.注意事项
@SneakyThrows 注解只能用于方法或构造函数
使用时需要谨慎,避免隐藏潜在的异常问题
09.非空判断:@NotNull
a.基本用法
import lombok.NonNull;
public static void test(@NonNull String name) {
System.out.println(name);
}
b.生成的代码
public static void test(String name) {
if (name == null) {
throw new NullPointerException("name");
}
System.out.println(name);
}
c.优点
简洁性:减少了显式的非空检查代码
可读性:通过注解清晰地表明参数不能为空
d.注意事项
@NonNull 注解只能用于方法参数
如果需要对类的字段进行非空检查,可以结合 @Builder 或 @RequiredArgsConstructor 注解使用
10.锁处理:@Synchronized
a.基本用法
import lombok.Synchronized;
public class Counter {
private int count = 0;
@Synchronized
public void increment() {
count++;
}
@Synchronized("lock")
public void decrement() {
count--;
}
private final Object lock = new Object();
}
b.优点
简洁性:减少了显式的同步代码
灵活性:可以指定同步锁对象,而不仅仅是 this
c.注意事项
@Synchronized 注解只能用于方法
如果需要更复杂的同步逻辑,建议使用 java.util.concurrent 包中的工具类
11.日志相关:@Log 和 @Slf4j
a.基本用法
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoggerExample {
public static void main(String[] args) {
log.info("This is an info message");
log.warn("This is a warning message");
log.error("This is an error message");
}
}
b.生成的代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerExample {
private static final Logger log = LoggerFactory.getLogger(LoggerExample.class);
public static void main(String[] args) {
log.info("This is an info message");
log.warn("This is a warning message");
log.error("This is an error message");
}
}
c.优点
简洁性:减少了显式的日志记录器初始化代码
灵活性:支持多种日志框架,如 SLF4J、Log4j 等
d.注意事项
确保项目中已经引入了相应的日志框架依赖
如果需要更复杂的日志配置,建议在日志框架的配置文件中进行设置
12.@Accessors:定制访问器(Getter/Setter)行为
a.基本用法
@Accessors 注解用于定制生成的 getter 和 setter 方法的行为
import lombok.Accessors;
@Accessors(chain = true) // 启用链式调用
public class Account {
private String name;
private int age;
}
---
使用链式调用:
---
Account account = new Account()
.setName("小明")
.setAge(18);
b.参数说明
a.chain
是否启用链式调用
@Accessors(chain = true)
public class Account {
private String name;
private int age;
}
b.fluent
是否启用“流式”访问器
@Accessors(fluent = true)
public class Account {
private String name;
private int age;
}
使用流式访问器:
Account account = new Account();
account.name("小明").age(18);
c.makeFinal
是否将生成的访问器方法标记为 final
@Accessors(makeFinal = true)
public class Account {
private String name;
private int age;
}
d.prefix
指定需要生成访问器的字段的前缀
@Accessors(prefix = "my")
public class Account {
private String myName;
private int age; // 不会生成访问器
}
13.@FieldDefaults:快速生成字段的权限修饰符
a.基本用法
@FieldDefaults 注解用于为类中的所有字段指定默认的访问修饰符
import lombok.experimental.FieldDefaults;
import lombok.AccessLevel;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class Account {
String name; // 默认为 private final
int age; // 默认为 private final
}
b.参数说明
a.makeFinal
是否将字段标记为 final
@FieldDefaults(makeFinal = true)
public class Account {
private String name; // private final
private int age; // private final
}
b.level
指定字段的默认访问权限
@FieldDefaults(level = AccessLevel.PROTECTED)
public class Account {
String name; // 默认为 protected
int age; // 默认为 protected
}
1.8 Lombok3
00.汇总
在Lombok和ORM中正确处理实体继承下的equals()和hashCode()
01.问题
a.CoreEntity.java (假设)
@Data
public class CoreEntity implements Serializable {
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@TableField("CREATE_BY")
private String createBy;
@TableField("CREATE_TIME")
private Date createTime;
@TableField("UPDATE_BY")
private String updateBy;
@TableField("UPDATE_TIME")
private Date updateTime;
}
b.ExamPerformanceAppealDetail.java (原始代码)
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("EXAM_PERFORMANCE_APPEAL_DETAIL")
public class ExamPerformanceAppealDetail extends CoreEntity implements Serializable {
// ... 大量业务字段,如 indLibraryId, prfOrgId 等
}
c.核心疑问
当ExamPerformanceAppealDetail继承CoreEntity时,
@EqualsAndHashCode(callSuper = false)会引发什么现象?当父类CoreEntity含有id等关键字段时,
callSuper应该设置为true还是false?正确的实践方案是什么?
02.解决方案
a.@EqualsAndHashCode(callSuper = false) 在这里意味着什么?有什么风险?
a.回答
callSuper = false 告诉Lombok在为ExamPerformanceAppealDetail生成equals()和hashCode()方法时,
完全忽略其父类CoreEntity中的所有字段 (id, createTime 等)。
b.现象
比较两个ExamPerformanceAppealDetail对象时,只会使用子类中定义的字段(如indLibraryId, prfOrgId, causeRemark等)来进行判断。
c.巨大风险
这种做法极其危险!CoreEntity中的id字段是实体在数据库中的唯一标识。
如果忽略它,可能导致两个在数据库中完全不同的记录(例如,一个id是"1001",另一个是"1002"),只要它们的子类业务字段恰好相同,
就会被程序错误地认为是equals()的。这会严重破坏HashSet、HashMap等集合的正常工作,并引发难以追踪的业务逻辑错误。
b.那是不是应该简单地把callSuper设置为true就行了?
a.回答
不行,简单地设置为true会引入其他更隐蔽的问题。
如果设置为@EqualsAndHashCode(callSuper = true),Lombok会调用super.equals()和super.hashCode(),
这意味着父类CoreEntity的字段将被纳入比较。这会导致以下两个致命问题:
b.主键id的问题
一个新创建、还未存入数据库的实体,其id为null。
如果你将这样的对象放入HashSet,之后保存它并获得了数据库生成的id,它的hashCode()就会改变。
这违反了Java集合框架的基本约定,会导致该对象在集合中“丢失”。
c.可变审计字段的问题
updateTime、updateBy等字段在其生命周期中是会改变的。
同样地,如果一个对象的hashCode依赖于这些可变字段,那么每次更新对象都会改变其哈希值,同样会破坏哈希集合。
c.既然callSuper=false和callSuper=true都有问题,那正确的做法是什么?
a.回答
正确的做法是遵循“业务键(Business Key)”原则。
一个对象的相等性,应该由其业务上唯一、稳定不变的字段组合来定义,而不是由数据库主键或易变的审计字段来定义。
b.实施步骤
a.识别业务键
在ExamPerformanceAppealDetail类中,找出能够唯一标识一条“申诉明细”记录的业务字段组合。
通常是那些在创建后就不会再改变的字段。
在此例中,examPerformanceAppealId + indLibraryId + prfOrgId是一个合理的业务键候选。
b.精确指定比较字段
使用Lombok的@EqualsAndHashCode注解的of参数,明确告诉它只使用这些业务键字段来进行比较和哈希计算。
c.正确设置callSuper
因为我们的业务键字段全部位于子类ExamPerformanceAppealDetail中,所以我们不需要包含父类的任何字段。因此,callSuper应该设置为false。
d.最终推荐代码
a.CoreEntity.java (推荐实践)
这个基础类不应该参与业务相等性判断,所以最好不要在它上面加@EqualsAndHashCode。只保留@Data来生成getter/setter即可。
-------------------------------------------------------------------------------------------------
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 核心实体基类,包含通用字段
* 这个类不应该定义 equals/hashCode,因为其字段(id, audit fields)不适合用于业务相等性判断。
*/
@Data
public class CoreEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@TableField("create_by")
private String createBy;
@TableField("create_time")
private Date createTime;
@TableField("update_by")
private String updateBy;
@TableField("update_time")
private Date updateTime;
}
b.ExamPerformanceAppealDetail.java (推荐实践)
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
@Data
// 这是最关键的修改:
// 1. of = {"..."}: 明确指定用于相等性判断的业务键字段。
// 2. callSuper = false: 正确且安全,因为它确保了CoreEntity中的id和审计字段被完全忽略。
@EqualsAndHashCode(callSuper = false, of = {"examPerformanceAppealId", "indLibraryId", "prfOrgId"})
// 3. (可选但推荐) ToString 的 callSuper 设置为 true,方便日志调试时能看到 id 等父类信息。
@ToString(callSuper = true)
@TableName("EXAM_PERFORMANCE_APPEAL_DETAIL")
@ApiModel(value="ExamPerformanceAppealDetail对象", description="考核申诉明细表")
public class ExamPerformanceAppealDetail extends CoreEntity implements Serializable {
private static final long serialVersionUID = 1L;
// --- 业务键字段 (用于 equals 和 hashCode) ---
@ApiModelProperty("指标ID")
@TableField("IND_LIBRARY_ID")
private String indLibraryId;
@ApiModelProperty("组织ID")
@TableField("PRF_ORG_ID")
private String prfOrgId;
@ApiModelProperty(value = "绩效申诉主表ID")
@TableField("EXAM_PERFORMANCE_APPEAL_ID")
private String examPerformanceAppealId;
// --- 其他业务数据字段 (不参与相等性判断) ---
@ApiModelProperty(value = "申诉类型")
@TableField("APPEAL_TYPE_DICT")
private Integer appealTypeDict;
@ApiModelProperty(value = "原因说明")
@TableField("CAUSE_REMARK")
private String causeRemark;
@ApiModelProperty("考核活动内容ID")
@TableField("EXAM_ACTIVITY_CONTENT_ID")
private String examActivityContentId;
@TableField("CONTENT")
@ApiModelProperty("内容")
private String content;
}
c.结论
在处理继承了包含数据库主键和审计字段的父类的实体时,绝对不能简单地使用默认的@EqualsAndHashCode或盲目地设置callSuper = true。
最佳实践是:通过@EqualsAndHashCode(callSuper = false, of = {"业务键1", "业务键2", ...})的方式,
精确控制相等性判断的范围,使其仅限于稳定、唯一的业务键字段。这既保证了业务逻辑的正确性,也遵循了Java集合框架的契约。
2 区别
2.1 @Async:异步
01.异步处理有4种方式
1.使用@Async注解
2.使用CompletableFuture
3.使用@Scheduled注解
4.使用线程池
02.@Async异步方法
a.介绍
默认,使用Spring创建【ThreadPoolTaskExecutor】
默认,核心线程数为8
默认,最大队列是Integer.MAX_VALUE。
默认,默认最大线程数是Integer.MAX_VALUE。
b.执行原理
创建新线程的条件是队列填满时,而这样的配置队列永远不会填满,如果有@Async注解标注的方法长期占用线程(比如HTTP长连接等待获取结果),
在核心8个线程数占用满了之后,新的调用就会进入队列,外部表现为没有执行。
c.示例:基本的任务调度
@EnableScheduling //开启任务调度
@SpringBootApplication
@ComponentScan("com.sxoms.ws.task")
@Slf4j
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
-----------------------------------------------------------------------------------------------------
@Slf4j
@Component
public class MyTask {
@Scheduled(cron = "0/5 * * * * ?")
public void executeTask(){
log.info("定时任务开始执行:{}", new Date());
}
}
d.示例:任务调度与异步执行
@Slf4j
@Component
@EnableAsync //使用1
@Configuration
@EnableScheduling
public class MyTask {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(14);
executor.setMaxPoolSize(50);
executor.setThreadNamePrefix("tmxtxjx-task-18080-exec-");
return executor;
}
@Async("taskExecutor") //使用2
@Scheduled(cron = "0/5 * * * * ?")
public void executeTask(){
log.info("定时任务开始执行:{}", new Date());
}
@Bean(name = "asyncPoolTaskExecutor")
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(10);
taskExecutor.setMaxPoolSize(100);
taskExecutor.setQueueCapacity(50);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("async-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
@Async("asyncPoolTaskExecutor") //使用3
@Scheduled(cron = "0/5 * * * * ?")
public void executeTask(){
log.info("定时任务开始执行:{}", new Date());
}
}
2.2 @Async:失效
00.汇总
1.未使用@EnableAsync注解
2.内部方法调用
3.方法非public
4.方法返回值错误
5.方法用static修饰了
6.方法用final修饰
7.业务类没加@Service注解
8.自己new的对象
9.Spring无法扫描异步类
00.注意事项
a.事务管理注解 @Transactional
问题:@Async 和 @Transactional 一起使用时,事务管理可能会失效
原因:@Async 会导致方法在不同的线程中执行,而事务上下文是线程绑定的。因此,异步方法中的事务管理可能无法正常工作
解决方法:如果需要在异步方法中使用事务,可以在异步方法内部手动管理事务,或者将事务逻辑移到同步方法中
网上验证1:当方法中同时使用 @Transactional 与 @Async 时,事务是可以生效的
网上验证2:@Transactional 调用 @Async 的方式,异步方法的事务是无法生效的
网上验证3:@Async 调用 @Transaction 的方式,异步方法事务是可以生效的,需要注意的是调用方也是没有事务管理的
b.定时任务注解 @Scheduled
问题:@Async 和 @Scheduled 一起使用时,可能会导致定时任务的调度行为异常
原因:@Scheduled 本身已经在独立的线程池中执行,再加上 @Async 可能会导致线程管理混乱
解决方法:避免在同一个方法上同时使用 @Scheduled 和 @Async。如果需要异步执行定时任务,可以在 @Scheduled 方法中调用另一个标记为 @Async 的方法
c.控制器方法注解 @RequestMapping / @GetMapping / @PostMapping 等
问题:@Async 和 Spring MVC 控制器方法注解一起使用时,可能会导致异步处理行为异常
原因:Spring MVC 控制器方法通常期望同步执行,并返回一个 ModelAndView 或 ResponseEntity。异步方法可能会导致响应无法正确返回
解决方法:如果需要异步处理请求,可以使用 Spring 的异步请求处理机制,例如返回 Callable 或 DeferredResult
d.构造函数
问题:@Async 不能用于构造函数
原因:构造函数在对象创建时调用,无法在对象创建过程中启动异步任务
解决方法:将异步逻辑移到普通方法中,并在需要时调用该方法
e.私有方法
问题:@Async 不能用于私有方法
原因:Spring AOP 代理机制无法拦截私有方法,因此 @Async 注解不会生效
解决方法:将异步方法定义为 public 或 protected
f.静态方法
问题:@Async 不能用于静态方法
原因:Spring AOP 代理机制无法拦截静态方法,因此 @Async 注解不会生效
解决方法:将异步方法定义为实例方法
g.返回值类型
问题:@Async 方法的返回值类型需要是 void、Future、CompletableFuture 或其子类
原因:Spring 需要通过返回值类型来处理异步结果
解决方法:确保 @Async 方法的返回值类型符合要求
01.未使用@EnableAsync注解
a.说明
在Spring中要开启@Async注解异步的功能,需要在项目的启动类,或者配置类上,使用@EnableAsync注解
b.代码
@EnableAsync
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
c.说明
@EnableAsync注解相当于一个开关,控制是否开启@Async注解异步的功能,默认是关闭的
如果在项目的启动类上没使用@EnableAsync注解,则@Async注解异步的功能不生效
02.内部方法调用
a.说明
在日常开发中,经常需要在一个方法中调用另外一个方法
b.代码
@Slf4j
@Service
public class UserService {
public void test() {
async("test");
}
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
c.说明
这个示例中,在UserService类中的test()方法中调用了async()方法
如果在controller中@Autowired了UserService类的对象,调用了它的test()方法,则async()异步的功能会失效
我们知道Spring通过@Async注解实现异步的功能,底层其实是通过Spring的AOP实现的,也就是说它需要通过JDK动态代理或者cglib,生成代理对象
异步的功能,是在代理对象中增加的,我们必须调用代理对象的test()方法才行
而在类中直接进行方法的内部调用,在test()方法中调用async()方法,调用的是该类原对象的async方法,相当于调用了this.async()方法,而并非UserService代理类的async()方法
因此,像这种内部方法调用,@Async注解的异步功能会失效
03.方法非public
a.在Java中有4种权限修饰符
public:所有类都可以访问
private:只能同一个类访问
protected:同一个类,同一个包下的其他类,不同包下的子类可以访问
默认修饰符:同一个类,同一个包下的其他类可以访问
b.说明
在实际工作中,我们使用频率最高的可能是public和private了
如果我在定义Service类中的某个方法时,有时把权限修饰符定义错了
c.代码
@Slf4j
@Service
public class UserService {
@Async
private void async(String value) {
log.info("async:{}", value);
}
}
d.说明
这个例子中将UserService类的async()方法的权限修饰符定义成了private的,这样@Async注解也会失效
因为private修饰的方法,只能在UserService类的对象中使用
而@Async注解的异步功能,需要使用Spring的AOP生成UserService类的代理对象,该代理对象没法访问UserService类的private方法,因此会出现@Async注解失效的问题
04.方法返回值错误
a.说明
我们在写一个新的方法时,经常需要定义方法的返回值
返回值可以是void、int、String、User等等,但如果返回值定义错误,也可能会导致@Async注解的异步功能失效
b.代码
@Service
public class UserService {
@Async
public String async(String value) {
log.info("async:{}", value);
return value;
}
}
c.说明
UserService类的async方法的返回值是String,这种情况竟然会导致@Async注解的异步功能失效
在AsyncExecutionInterceptor类的invoke()方法,会调用它的父类AsyncExecutionAspectSupport中的doSubmit方法,该方法时异步功能的核心代码,如下
从图中看出,@Async注解的异步方法的返回值,要么是Future,要么是null
因此,在实际项目中,如果想要使用@Async注解的异步功能,相关方法的返回值必须是void或者Future
05.方法用static修饰了
a.说明
有时候,我们的方法会使用static修饰,这样在调用的地方,可以直接使用类名.方法名,访问该方法了
但如果在@Async方法上加了static修饰符
b.代码
@Slf4j
@Service
public class UserService {
@Async
public static void async(String value) {
log.info("async:{}", value);
}
}
c.说明
这时@Async的异步功能会失效,因为这种情况idea会直接报错:Methods annotated with '@Async' must be overridable
使用@Async注解声明的方法,必须是能被重写的,很显然static修饰的方法,是类的静态方法,是不允许被重写的
因此这种情况下,@Async注解的异步功能会失效
06.方法用final修饰
a.说明
在Java种final关键字,是一个非常特别的存在
用final修饰的类,没法被继承
用final修饰的方法,没法被重写
用final修饰的变量,没法被修改
b.说明
如果final使用不当,也会导致@Async注解的异步功能失效
c.代码
@Slf4j
@Service
public class UserService {
public void test() {
async("test");
}
@Async
public final void async(String value) {
log.info("async:{}", value);
}
}
d.说明
这种情况下idea也会直接报错:Methods annotated with '@Async' must be overridable
因为使用final关键字修饰的方法,是没法被子类重写的
因此这种情况下,@Async注解的异步功能会失效
07.业务类没加@Service注解
a.说明
有时候,我们在新加Service类时,会忘了加@Service注解
b.代码
@Slf4j
//@Service
public class UserService {
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
@Service
public class TestService {
@Autowired
private UserService userService;
public void test() {
userService.async("test");
}
}
c.说明
这种情况下,@Async注解异步的功能也不会生效
因为UserService类没有使用@Service、@Component或者@Controller等注解声明
该类不会被Spring管理,因此也就无法使用Spring的异步功能
08.自己new的对象
a.说明
在项目中,我们经常需要new一个对象,然后对他赋值,或者调用它的方法。
但如果new了一个Service类的对象,可能会出现一些意想不到的问题
b.代码
@Slf4j
@Service
public class UserService {
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
@Service
public class TestService {
public void test() {
UserService userService = new UserService();
userService.async("test");
}
}
c.说明
在TestService类的test()方法中,new了一个UserService类的对象,然后调用该对象的async()方法
很显然这种情况下,async()方法只能同步执行,没法异步执行
因为在项目中,我们自己new的对象,不会被Spring管理,因此也就无法使用Spring的异步功能
不过我们可以通过BeanPostProcessor类,将创建的对象手动注入到Spring容器中
09.Spring无法扫描异步类
a.说明
我们在Spring项目中可以使用@ComponentScan注解指定项目中扫描的包路径
b.代码
@ComponentScan({"com.susan.demo.service1"})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
c.说明
项目中com.susan.demo.service1这个路径是不存在的,会导致@Async注解异步的功能失效
同时如果@ComponentScan注解定义的路径,没有包含你新加的Servcie类的路径,@Async注解异步的功能也会失效
2.3 @Profile:环境
01.介绍
环境配置:在不同的环境中使用不同的配置
例如,开发环境可能使用嵌入式数据库,而生产环境使用外部数据库
条件加载:根据环境变量或系统属性来决定是否加载某些 Bean
02.使用方法
a.在配置类上使用
可以在整个配置类上使用 @Profile 注解,这样该配置类中的所有 Bean 都会根据指定的环境条件进行加载
-----------------------------------------------------------------------------------------------------
@Configuration
@Profile("dev")
public class DevDatabaseConfig {
@Bean
public DataSource dataSource() {
// 配置开发环境的数据源
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
b.在 Bean 方法上使用
可以在单个 Bean 方法上使用 @Profile 注解,以便在不同的环境中加载不同的 Bean
-----------------------------------------------------------------------------------------------------
@Configuration
public class DatabaseConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
// 配置开发环境的数据源
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 配置生产环境的数据源
return new DataSource(); // 生产环境的数据源配置
}
}
03.激活Profile
a.第1种:命令行参数
java -jar myapp.jar --spring.profiles.active=dev
b.第2种:环境变量
export SPRING_PROFILES_ACTIVE=dev
c.第3种:配置文件
spring.profiles.active=dev
2.4 @Scheduled:定时任务
01.添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
02.启用定时任务
在主应用类上添加 @EnableScheduling 注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
03.定义定时任务
创建一个组件类,并使用 @Scheduled 注解定义定时任务:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTasks {
@Scheduled(fixedRate = 5000) // 每5秒执行一次
public void reportCurrentTime() {
System.out.println("当前时间: " + System.currentTimeMillis());
}
@Scheduled(cron = "0 0 12 * * ?") // 每天中午12点执行
public void performDailyTask() {
System.out.println("执行每日任务: " + System.currentTimeMillis());
}
}
fixedRate:任务执行间隔时间(以毫秒为单位)
cron:使用 Cron 表达式定义任务的执行时间
2.5 @Conditional:条件加载
01.介绍
条件加载:根据特定条件(如类路径中是否存在某个类、某个 Bean 是否已经存在、某个属性是否被设置等)来决定是否加载某个 Bean
环境适配:在不同的环境中加载不同的配置或组件
02.使用
a.说明
@Conditional 注解通常与实现了 Condition 接口的类一起使用
Condition 接口定义了一个 matches 方法,该方法返回一个布尔值,指示是否满足条件
b.定义一个实现 Condition 接口的类
public class OnMissingBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 检查某个 Bean 是否不存在
return !context.getBeanFactory().containsBeanDefinition("myBean");
}
}
c.在配置类或 Bean 方法上使用 @Conditional 注解
@Configuration
public class MyConfig {
@Bean
@Conditional(OnMissingBeanCondition.class)
public MyService myService() {
return new MyServiceImpl();
}
}
-----------------------------------------------------------------------------------------------------
在这个示例中,myService Bean 只有在 myBean 不存在时才会被注册
03.常用的衍生注解
@ConditionalOnBean:当容器中存在指定的 Bean 时,条件成立
@ConditionalOnMissingBean:当容器中不存在指定的 Bean 时,条件成立
@ConditionalOnClass:当类路径中存在指定的类时,条件成立
@ConditionalOnMissingClass:当类路径中不存在指定的类时,条件成立
@ConditionalOnProperty:当指定的属性具有特定值时,条件成立
@ConditionalOnExpression:基于 SpEL 表达式的条件
@ConditionalOnResource:当类路径中存在指定的资源时,条件成立
@ConditionalOnWebApplication:当应用程序是 Web 应用时,条件成立
@ConditionalOnNotWebApplication:当应用程序不是 Web 应用时,条件成立
2.6 @EventListener:监听器
01.介绍
a.什么是 @EventListener 注解?
在 Spring 中,事件是通过 ApplicationEvent 类及其子类表示的
事件发布者通过 ApplicationEventPublisher 发布事件
而事件监听器则通过 @EventListener 注解的方法来处理这些事件
b.@EventListener 与实现 ApplicationListener 接口有何区别?
@EventListener 注解:允许在任意 Spring bean 的方法上定义事件监听器,更加灵活
实现 ApplicationListener 接口:需要创建一个实现 ApplicationListener 接口的类,适合在特定情况下使用
02.基础用法
a.定义事件:事件通常是继承自 ApplicationEvent 的类,但从 Spring 4.2 开始,任何普通的 POJO 类都可以作为事件
public class CustomEvent {
private String message;
public CustomEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
b.发布事件:使用 ApplicationEventPublisher 来发布事件
@Service
public class EventPublisherService {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publishEvent(String message) {
CustomEvent customEvent = new CustomEvent(message);
applicationEventPublisher.publishEvent(customEvent);
}
}
c.监听事件:使用 @EventListener 注解的方法来监听事件
@Component
public class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("Received custom event - " + event.getMessage());
}
}
02.高级用法
a.条件监听:可以使用 condition 属性来指定监听器方法的触发条件
@Component
public class ConditionalEventListener {
@EventListener(condition = "#event.message == 'special'")
public void handleSpecialEvent(CustomEvent event) {
System.out.println("Received special event - " + event.getMessage());
}
}
b.异步事件监听:可以使用 @Async 注解使事件监听器方法异步执行。需要在配置类中启用异步支持
@Configuration
@EnableAsync
public class AsyncConfig {
}
@Component
public class AsyncEventListener {
@EventListener
@Async
public void handleAsyncEvent(CustomEvent event) {
System.out.println("Received async event - " + event.getMessage());
}
}
c.事件层次结构:监听器方法可以监听事件的层次结构中的所有事件。例如,如果监听器方法监听 ApplicationEvent,它将处理所有 ApplicationEvent 的子类事件
@Component
public class GenericEventListener {
@EventListener
public void handleAllEvents(ApplicationEvent event) {
System.out.println("Received event - " + event.getClass().getSimpleName());
}
}
3 区别
3.1 @Resource:默认byName,支持byType
01.@Autowired
a.介绍
来源:@Autowired 是 Spring 框架提供的注解
注入方式:默认按类型(byType)进行注入
可选属性:required:默认为 true,表示必须找到一个匹配的 Bean。如果设置为 false,则在没有匹配的 Bean 时不会抛出异常
使用场景:适用于 Spring 环境下的依赖注入
b.示例
@Component
public class MyService {
@Autowired
private MyRepository myRepository;
// 或者通过构造函数注入
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
// 或者通过 setter 方法注入
@Autowired
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
02.@Resource
a.介绍
来源:@Resource 是 JSR-250 规范中的注解,由 Java 提供
注入方式:默认按名称(byName)进行注入。如果没有指定名称,则按字段名进行注入;如果找不到匹配的名称,则按类型注入
可选属性:name:指定要注入的 Bean 的名称
type:指定要注入的 Bean 的类型
使用场景:适用于需要与 Java EE 兼容的场景
b.示例
@Component
public class MyService {
@Resource(name = "myRepository")
private MyRepository myRepository;
// 或者不指定名称,按字段名注入
@Resource
private MyRepository anotherRepository;
// 也可以通过 setter 方法注入
@Resource
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
3.2 @Autowired:先byType、再byName
00.注意
@Autowired可以用于【属性注入】、【setter注入】、【构造方法注入】的依赖注入形式
01.@Autowired装配过程
a.加载后置处理器
在启动Spring IoC容器时,容器会自动加载 AutowiredAnnotationBeanPostProcessor 后置处理器
b.扫描注解
当容器扫描到 @Autowired、@Resource 或 @Inject 注解时,会自动查找并装配需要的Bean
c.查找Bean
按类型(byType)查找:首先在容器中按类型查找对应的Bean
如果查询结果刚好为一个,就将该Bean装配给 @Autowired 指定的属性
如果查询结果不止一个,则根据名称(byName)来查找
如果查找结果为空,则会抛出异常;可以通过设置 required=false 来避免异常
02.Spring官方推荐避免使用注解注入,而是通过【构造函数注入】或【Setter方法注入】的原因
a.控制反转原则
注解注入暴露对容器的依赖,与IOC理念相悖
b.依赖关系
构造器注入明确展示依赖关系和数量顺序,便于理解和维护
c.类灵活性
属性注入限制类在容器之外的实例化,限制类的可重用性和灵活性
d.错误捕获
构造器注入在实例化阶段捕获依赖注入错误,避免空指针异常
e.单元测试
属性注入导致对象与其依赖关系紧密耦合,不利于单元测试
3.3 @Autowired与@Resource
01.定义
@Autowired:对类成员变量、方法及构造函数进行标注,完成自动装配的工作
@Resource:在语义上被定义为通过其唯一的名称来标识特定的目标组件,其中声明的类型与匹配过程无关
---------------------------------------------------------------------------------------------------------
@Autowired:主要按类型(byType)注入,适用于 Spring 环境,支持构造函数、字段和 setter 方法注入
@Resource:主要按名称(byName)注入,适用于需要与 Java EE 兼容的场景,支持字段和 setter 方法注入
02.五大区别
a.包含的属性不同
@Autowired只包含一个参数:required,表示是否开启自动注入,默认是true
@Resource包含七个参数,其中最重要的两个参数是:name 和 type
b.默认自动装配类型不同
@Autowired默认根据【类型】自动装配
@Resource默认先根据【名字】查找,再根据【类型】自动装配
c.注解应用的地方不同
@Autowired能够用在:构造器、方法、参数、成员变量和注解上
@Resource能用在:类、成员变量和方法上
d.出处不同
@Autowired是Spring定义的注解
@Resource是JSR-250定义的注解,标准的Java注解
e.装配顺序不同
@Autowired:默认先按byType进行匹配,如果发现找到多个bean,则又按照byName方式进行匹配,如果还有多个,则报出异常
@Resource:①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
3.4 @Primary与@Bean
00.总结
在 Spring 框架中,@Bean 用于向容器注册一个被管理的对象(Bean),
而 @Primary 用于当遇到多个同类型 Bean 时,指定其中一个为首选注入目标。二者功能侧重点不同,但常常结合使用以实现灵活、可控的依赖注入。
01.@Bean:注册 Bean 到 Spring 容器
a.定义与作用
声明位置:标注在 @Configuration 类的方法上。
功能:将方法的返回值交由 Spring 容器管理,注册为一个 Bean。
b.核心特点
显式声明:可以在配置类中按需创建第三方类或自定义类型的 Bean。
可定制性:支持指定 name(或 value)属性,自定义 Bean 名称;也可通过方法名作为默认 ID。
作用域控制:可与 @Scope 一起使用,设置单例、原型等多种作用域。
生命周期回调:可在方法上结合 @Bean(initMethod=…, destroyMethod=…) 指定初始化与销毁回调。
c.示例
@Configuration
public class AppConfig {
// 注册一个名为 "dataSource" 的 Bean,类型为 DataSource
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
// ... 配置数据源 ...
return ds;
}
}
02.@Primary:指定首选 Bean
a.定义与作用
声明位置:标注在类、构造函数、方法或字段上(通常与 @Component、@Bean 结合)。
功能:当容器中存在多个同类型 Bean 且没有其他更具体的注入限定符时,优先注入被 @Primary 标注的那个 Bean。
b.核心特点
a.解决歧义:在多 Bean 情况下,Spring 无法自动决定注入哪个实例时,@Primary 提供了默认选择。
b.可覆盖性:若同时使用 @Qualifier 指定名称,则 @Qualifier 优先于 @Primary。
c.单一首选:同一类型只能有一个 @Primary,否则仍会报歧义异常。
c.示例
@Configuration
public class DataSourceConfig {
// 默认数据源
@Bean
@Primary
public DataSource primaryDataSource() {
// ... 主数据源配置 ...
}
// 备份或测试数据源
@Bean
public DataSource secondaryDataSource() {
// ... 其他数据源配置 ...
}
}
@Service
public class UserService {
// 注入时,无需使用 @Qualifier,自动注入 primaryDataSource
public UserService(DataSource dataSource) {
this.dataSource = dataSource;
}
}
03.其他信息
a.关键区别对比
a.定义目标
@Bean:注册 Bean 到 Spring 容器
@Primary:在多 Bean 场景下指定首选的注入目标
b.使用场景
@Bean:需要显式创建并管理非组件扫描的第三方或自定义对象
@Primary:当同类型 Bean 多于一个,且希望默认注入其中一个时
c.与组件扫描
@Bean:与 @Component 不同,不纳入扫描;显式声明注册
@Primary:可应用于 @Component、@Bean 或字段注入点
d.冲突解决
@Bean:无
@Primary:仅用于依赖注入歧义时;被 @Qualifier 覆盖
e.配置方式
@Bean:通过方法返回值定义;可自定义 Bean 名称、作用域等
@Primary:通过注解标记已注册的 Bean,声明其首选地位
b.实践建议
a.显式 Bean 注册
对于第三方库的对象或需要特殊构造逻辑的 Bean,应使用 @Bean 在配置类中注册。
b.优雅处理多实现场景
a.若一个实现为默认业务实现,标注 @Primary;
b.若需要按需注入其它实现,使用 @Qualifier("beanName")。
c.保持清晰可维护
a.避免多个 Bean 同时被 @Primary,防止注入歧义异常;
b.在文档和代码注释中明确说明主次 Bean 的用途与场景。
c.结论
@Bean 负责 创建与注册,是容器托管 Bean 的“入口”。
@Primary 负责 优先选择,是依赖注入过程中的“歧义解决器”。
二者结合使用,可实现 灵活管理 与 明确注入,提升 Spring 应用的可扩展性与可维护性。
3.5 @Primary与@Fallback
00.汇总
a.@Primary
用于在多个相同类型的 Bean 中指定一个首选的 Bean,解决 Bean 注入冲突
b.@Fallback
@Fallback 并不是 Spring 注解,通常与其他框架(如 Hystrix)使用,用于实现服务降级策略,提供备用处理逻辑
01.@Primary
a.定义
@Primary 是 Spring 提供的注解,用于在有多个候选 Bean 时,指定一个首选的 Bean
b.原理
当 Spring 容器中存在多个相同类型的 Bean 时,@Primary 标识的 Bean 会被优先注入
c.常用 API
@Primary:直接在 Bean 定义上使用,无需其他配置
d.使用步骤
1.在多个相同类型的 Bean 中,选择一个 Bean 使用 @Primary 注解
2.在需要注入的地方,直接注入该类型的 Bean
e.示例
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
public class ServiceA implements MyService {
// Implementation
}
@Component
@Primary
public class ServiceB implements MyService {
// Implementation
}
@Component
public class MyComponent {
private final MyService myService;
public MyComponent(MyService myService) {
this.myService = myService;
}
}
-----------------------------------------------------------------------------------------------------
在这个例子中,ServiceB 被标记为 @Primary
因此在 MyComponent 中注入 MyService 时,ServiceB 会被优先选择
02.@Fallback
a.定义
@Fallback 并不是 Spring 框架中的标准注解
而是与其他框架(如 Hystrix 或 Resilience4j)结合使用,用于实现服务降级策略
b.原理
当主服务调用失败时,@Fallback 指定的方法会被调用,以提供备用的处理逻辑
c.使用步骤
1.在需要降级处理的方法上使用 @Fallback 注解
2.定义降级处理逻辑
d.示例代码(使用 Resilience4j)
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.annotation.Fallback;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
public String performOperation() {
// Main operation logic
throw new RuntimeException("Service failure");
}
public String fallbackMethod(Throwable t) {
return "Fallback response";
}
}
-----------------------------------------------------------------------------------------------------
在这个例子中,performOperation 方法使用了 @CircuitBreaker 和 fallbackMethod
当 performOperation 失败时,fallbackMethod 会被调用
3.6 @Primary与@Qualifier:消除歧义
00.总结
@Primary:用于指定默认的Bean选择,适用于大多数情况下希望使用某个Bean作为默认选择
@Qualifier:用于精确指定要注入的Bean,适用于需要明确选择某个Bean的情况,即使myService1被标记为@Primary
01.@Primary
a.介绍
作用:指定一个Bean为首选Bean,当存在多个相同类型的Bean时,Spring会优先注入标记了@Primary的Bean
使用场景:适用于大多数情况下希望使用某个特定Bean作为默认选择
使用方法:在Bean定义上添加@Primary注解
b.示例
@Configuration
public class AppConfig {
@Bean
@Primary
public MyService myService1() {
return new MyServiceImpl1();
}
@Bean
public MyService myService2() {
return new MyServiceImpl2();
}
}
-----------------------------------------------------------------------------------------------------
如果注入MyService类型的Bean,Spring会优先选择myService1,因为它被标记为@Primary
02.@Qualifier
a.作用
作用:通过指定Bean的名称来明确选择要注入的Bean
使用场景:适用于需要精确指定某个Bean进行注入的情况,尤其是在多个Bean都可能是合适的选择时
使用方法:在依赖注入的地方使用@Qualifier注解,并指定要注入的Bean的名称
b.示例
@Component
public class MyComponent {
private final MyService myService;
@Autowired
public MyComponent(@Qualifier("myService2") MyService myService) {
this.myService = myService;
}
}
-----------------------------------------------------------------------------------------------------
@Qualifier("myService2")明确指定了要注入myService2,即使myService1被标记为@Primary
3.7 @Order与@DependsOn:Bean初始化顺序
00.总结
@Order:用于同类型Bean的排序,主要影响集合中的顺序或处理器链的执行顺序
@DependsOn:用于指定Bean的初始化顺序,确保依赖的Bean在当前Bean之前被初始化
01.@Order
a.定义
@Order注解用于定义多个同类型Bean的排序顺序。它通常用于实现Ordered接口或使用@Order注解的组件
b.作用范围
主要用于集合类型的Bean(如List、Set)或在处理器链中(如拦截器、过滤器)
c.使用方式
通过在类上添加@Order注解,指定一个整数值,值越小优先级越高
d.示例
@Component
@Order(1)
public class FirstBean {
// ...
}
@Component
@Order(2)
public class SecondBean {
// ...
}
在上面的示例中,FirstBean会在SecondBean之前被处理
e.MybatisPlusSaasConfig 在 CustomMyBatisPlusConfig 之前加载
@Configuration
@Order(0) // 确保 MybatisPlusSaasConfig 先加载
@MapperScan("cn.jggroup.**.mapper*")
public class MybatisPlusSaasConfig {
// 配置内容
}
@Configuration
@Order(1) // 确保 CustomMyBatisPlusConfig 后加载
@MapperScan("cn.jggroup.**.mapper*")
public class CustomMyBatisPlusConfig extends MybatisPlusSaasConfig {
// 配置内容
}
-----------------------------------------------------------------------------------------------------
@Order(0):表示最高优先级,会在其他没有指定或指定更大值的组件之前加载。
@Order(1):表示次高优先级,依此类推
-----------------------------------------------------------------------------------------------------
默认顺序:如果没有指定 @Order,Spring 会根据类名的字母顺序加载配置类。
负值:@Order 也可以使用负值,例如 @Order(-1),这将比 @Order(0) 更早加载。
没有 @Order 注解:如果没有使用 @Order 注解,Spring 不会为该组件或配置类分配特定的优先级。
-----------------------------------------------------------------------------------------------------
默认行为:在没有 @Order 的情况下,Spring 通常会按照类名的字母顺序加载配置类,但这不是绝对的,具体行为可能会因 Spring 版本和具体配置而异。
使用 @Order:通过使用 @Order 注解,您可以明确指定加载顺序。值越小,优先级越高。
02.@DependsOn
a.定义
@DependsOn注解用于指定当前Bean依赖于其他Bean,确保依赖的Bean在当前Bean之前初始化
b.作用范围
用于控制Bean的初始化顺序,确保依赖的Bean在当前Bean之前被创建
c.使用方式
在类或方法上添加@DependsOn注解,指定依赖的Bean名称
d.示例
@Component
public class FirstBean {
// ...
}
@Component
@DependsOn("firstBean")
public class SecondBean {
// ...
}
在上面的示例中,SecondBean依赖于FirstBean,因此FirstBean会在SecondBean之前被初始化
4 区别
4.1 @Import
01.介绍
@Import注解是Spring框架中一个非常强大的工具
它允许你将普通类、组件类、ImportSelector实现类和ImportBeanDefinitionRegistrar实现类引入到容器中
通过@Import,你可以实现配置的模块化,使得代码更加清晰和易于维护
02.使用@Import
a.@Import导入普通类
a.说明
对于普通类(没有被声明为Component),通过 @Import 也可以添加到Spring容器中
例如,使用@Import(OrderService.class) ,就能向容器中添加OrderService类型的bean
b.代码
import com.xiakexing.service.OrderService;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(OrderService.class)
public class AppConfig {
}
-------------------------------------------------------------------------------------------------
// 没有使用@Component的普通类
public class OrderService {
public void test() {
System.out.println("执行OrderService.test");
}
}
-------------------------------------------------------------------------------------------------
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean("orderService", OrderService.class);
orderService.test();
}
}
c.说明
当执行上面代码时,会出现报错:No bean named 'orderService' available
注意,通过@Import直接导入的bean,名称是该类的全类名
因此应这样获取context.getBean(OrderService.class.getName(), OrderService.class)
b.@Import与ImportSelector接口
a.ImportSelector接口
ImportSelector接口,允许你自定义条件动态选择要导入的配置类,有两个方法:
selectImports:返回一个全类名的数组,这些类将被添加到spring容器中。注意,该方法可以返回空数组,但是不能返回null
getExclusionFilter:返回一个Predicate,用于排除selectImports方法返回值中的某些类
b.示例:OrderService与上面相同,增加UserService类
public class UserService {
public void test() {
System.out.println("执行UserService.test");
}
}
c.示例:SimpleImportSelector只导入UserService类
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.function.Predicate;
public class SimpleImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{OrderService.class.getName(), UserService.class.getName()};
}
// 排除全类名中有Order的类
@Override
public Predicate<String> getExclusionFilter() {
return new Predicate<String>() {
@Override
public boolean test(String name) {
return name.contains("Order");
}
};
}
}
-------------------------------------------------------------------------------------------------
import com.xiakexing.service.SimpleImportSelector;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(SimpleImportSelector.class)
public class AppConfig {
}
-------------------------------------------------------------------------------------------------
import com.xiakexing.service.OrderService;
import com.xiakexing.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class.getName(), UserService.class);
userService.test();
OrderService orderService = context.getBean(OrderService.class.getName(), OrderService.class);
orderService.test();
}
}
-------------------------------------------------------------------------------------------------
结果就是userService.test()执行成功,而获取orderService时报错
c.@Import与ImportBeanDefinitionRegistrar接口
a.说明
ImportBeanDefinitionRegistrar 接口允许通过编程方式动态注册BeanDefinition
b.定义User类
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
c.自定义ImportBeanDefinitionRegistrar接口实现
编程式向容器中注册User类的beanDefinition,Spring容器将创建对应的bean
-------------------------------------------------------------------------------------------------
import com.xiakexing.entity.User;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class SimpleBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
definition.setBeanClass(User.class);
ConstructorArgumentValues values = new ConstructorArgumentValues();
values.addIndexedArgumentValue(0, "Tom");
values.addIndexedArgumentValue(1, 29);
definition.setConstructorArgumentValues(values);
definition.setScope("singleton");
registry.registerBeanDefinition("user", definition);
}
}
-------------------------------------------------------------------------------------------------
import com.xiakexing.service.SimpleBeanRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(SimpleBeanRegistrar.class)
public class AppConfig {
}
03.@Import的原理
a.内容
Spring中ConfigurationClassPostProcessor类,会扫描所有的@Configuration类,并处理其中的@Import注解
扫描配置类:Spring容器启动时,会扫描所有的@Configuration类
解析@Import注解:对于每个配置类,Spring会解析其中的@Import注解,获取需要导入的类
处理导入的类:根据导入的类的类型(普通类、@Configuration类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类),Spring会采取不同的处理方式
注册BeanDefinition:最终,Spring会将所有导入的类注册为BeanDefinition,让Spring容器管理
b.源码
源码中ConfigurationClassParser类负责解析@Configuration类,其中doProcessConfigurationClass方法逻辑如下:
先递归地收集配置类或某个注解上的@Import的value值
然后调用processImports方法来处理@Import注解,分3种情况处理导入的类
例如,对于ImportBeanDefinitionRegistrar接口实现,先收集到Map中缓存起来,然后遍历Map,逐个调用registerBeanDefinitions方法
04.源码中使用
a.内容
Spring源码中,模块化、插拔式的功能选项,就是通过@Import实现的,如aop
b.@EnableAspectJAutoProxy实现
当在配置类上添加@EnableAspectJAutoProxy时,就启用了Spring aop功能
源码中,EnableAspectJAutoProxy注解上通过Import引入了AspectJAutoProxyRegistrar
该类ImportBeanDefinitionRegistrar接口的实现
在registerBeanDefinitions方法中向容器注册了AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator类是BeanPostProcessor接口的一个实现
会解析容器中@Aspect注解的bean
封装@Before等方法为Advisor对象;在bean的初始化后阶段,为切面命中的bean创建代理对象
c.@EnableAsync实现
@EnableAsync注解提供了开箱即用的异步解决方案,我们只需在想要异步执行的方法上加@Async即可
源码中,EnableAsync注解上通过Import引入了AsyncConfigurationSelector类,
该类是ImportSelector接口实现,在selectImports方法中引入了ProxyAsyncConfiguration类(默认使用JDK动态代理)
在ProxyAsyncConfiguration中,又声明了AsyncAnnotationBeanPostProcessor的bean
后者是BeanPostProcessor接口实现,在bean的初始化后阶段,为使用了@Async的bean创建代理对象
4.2 @Bean与@Component
01.相同点
都使用注解定义Bean
02.不同点
a.装配方式
@Bean是使用Java代码装配Bean
@Component是自动装配Bean
b.位置不同
@Bean注解用在【方法】上,表示这个方法会返回一个Bean。@Bean需要在配置类中使用,即类上需要加上@Configuration注解
@Component注解用在【类】上,表明一个类会作为组件类,并告知Spring要为这个类创建bean,每个类对应一个 Bean
@Component注解用于类级别,将该类标记为Spring容器中的一个组件,自动检测并注册为Bean(需要扫对应的包),每个类对应一个Bean
4.3 @Mapper与@Repository
01.@Repository
a.介绍
@Repository:@Repository的作用与@Controller,@Service的作用都是把对象交给Spring管理
@Repository是标注在Dao层接口上,作用是将接口的一个实现类交给Spring管理
b.注意
使用这个注解的前提是必须在启动类上添加@MapperScan("Mapper接口层路径")注解
这个@Repository完全可以省略不写,也完全可以实现自动注入,但是在IDEA中会存在一个红色的波浪线
-----------------------------------------------------------------------------------------------------
原因如下:
Spring配置文件中配置了MapperScannerConfiguer这个Bean,它会扫描持久层接口创建实现类并交给Spring管理
SpringBoot的启动类上标注了@MapperScan,它的作用和上面的MapperScannerConfiguer作用一样
02.@Mapper
a.介绍
这个注解一般使用在Dao层接口上,相当于一个mapper.xml文件,它的作用就是将接口生成一个动态代理类
加入了@Mapper注解,目的就是为了不再写mapper映射文件。这个注解就是用来映射mapper.xml文件的
-----------------------------------------------------------------------------------------------------
使用@mapper后,不需要在spring配置中设置扫描地址
通过mapper.xml里面的namespace属性对应相关的mapper类,spring将动态的生成Bean后注入到ServiceImpl中
b.注意
在Dao层不要存在相同名字的接口,也就是在Dao不要写重载
因为mapper文件是通过id与接口进行对应的,如果写了两个同名的接口,就会导致mapper文件映射出错
03.@Mapper和@MapperScan区别
a.介绍
@Mapper注解写在每个Dao接口层的接口类上,@MapperScan注解写在SpringBoot的启动类上
当我们的一个项目中存在多个Dao层接口的时候,此时我们需要对每个接口类都写上@Mapper注解,非常的麻烦
此时可以使用@MapperScan注解来解决这个问题,让这个接口进行一次性的注入,不需要在写@Mapper注解
b.示例
@SpringBootApplication
@MapperScan("cn.gyyx.mapper")
// 这个注解可以扫描 cn.gyyx.mapper 这个包下面的所有接口类,可以把这个接口类全部的进行动态代理
public class WardenApplication {
public static void main(String[] args) {
SpringApplication.run(WardenApplication.class,args);
}
}
-----------------------------------------------------------------------------------------------------
@Mapper注解相当于是@Reponsitory注解和@MapperScan注解的和,会自动的进行配置加载
@MapperScan注解多个包,@Mapper只能把当前接口类进行动态代理
c.在实际开发中,如何使用@Mapper、@MapperSacn、@Repository注解
在SpringBoot的启动类上给定@MapperSacn注解
此时Dao层可以省略@Mapper注解,当然@Repository注解可写可不写,最好还是写上
当使用@Mapper注解的时候,可以省略@MapperSacn以及@Repository
d.建议
以后在使用的时候,在启动类上给定@MapperScan("Dao层接口所在的包路径")
在Dao层上不写@Mapper注解,写上@Repository即可
04.动态SQL注解
a.介绍
MyBatis的动态SQL注解开发是基于Java注解和接口方法的,通过在接口方法上使用注解,可以在运行时生成对应的SQL语句
MyBatis会根据接口方法的注解来生成SQL,并将它们与数据库进行交互。它可以在实体类或映射文件中使用注解来配置映射关系
常用的注解包括 @Select、@Update、@Insert、@Delete等,它们分别用于配置查询、更新、插入和删除操作
通过这些注解,我们可以直接在实体类或映射文件中定义 SQL 语句
而不需要像传统的 MyBatis 配置方式那样在映射文件中使用 <select>、<update>、<insert> 和 <delete> 标签来定义SQL语句
b.@Select
@Select:该注解的目的是为了取代mapper.xml中的select标签,只作用于方法上面。此时就不要在写mapper.xml文件了
-----------------------------------------------------------------------------------------------------
@Select注解的源码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select
{
String[] value();
}
-----------------------------------------------------------------------------------------------------
从上述可以看到两点信息:
@Select注解只能修饰方法。
@Select注解的值是字符数组。
-----------------------------------------------------------------------------------------------------
所以,@Select注解的用法是这样的:
@Select({ "select * from xxx", "select * from yyy" })
Person selectPersonById(Integer id);
-----------------------------------------------------------------------------------------------------
虽然@Select注解的值是字符数组,但是真正生效的应该是最后那条SQL语句
c.@Select注解动态SQL拼写
普通的字符串值,只能实现变量的替换功能,实现简单的SQL语句,如下所示
@Select("select * from t_person where id = #{id}")
Person selectPersonById(Integer id);
-----------------------------------------------------------------------------------------------------
如果要想实现复杂的逻辑判断,则需要使用标签 ,如下所示:
@Select("<script> select * from t_person where id = #{id}
<when test='address !=null'> and address = #{address}
</when> </script>")
Person selectPersonById(Integer id);
-----------------------------------------------------------------------------------------------------
其实,标签 注解专用的,其他的注解,例如@Insert、@Update、@Delete等等,都可以使用的
d.与@Select相关注解
import java.util.List;
import me.gacl.domain.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/**
* @author gacl
* 定义sql映射的接口,使用注解指明方法要执行的SQL
*/
public interface UserMapperI {
使用@Insert注解指明add方法要执行的SQL
@Insert("insert into users(name, age) values(#{name}, #{age})")
public int add(User user);
使用@Delete注解指明deleteById方法要执行的SQL
@Delete("delete from users where id=#{id}")
public int deleteById(int id);
使用@Update注解指明update方法要执行的SQL
@Update("update users set name=#{name},age=#{age} where id=#{id}")
public int update(User user);
使用@Select注解指明getById方法要执行的SQL
@Select("select * from users where id=#{id}")
public User getById(int id);
使用@Select注解指明getAll方法要执行的SQL
@Select("select * from users")
public List<User> getAll();
}
e.动态SQL查询
动态SQL查询通常用于根据不同条件构建SQL语句
在这个示例中,我们将演示如何构建一个动态查询,根据传递的参数来动态生成查询条件
-----------------------------------------------------------------------------------------------------
首先,创建一个 UserProvider 类,用于提供动态SQL查询的逻辑:
import org.apache.ibatis.jdbc.SQL;
import java.util.Map;
public class UserProvider {
public String dynamicSQL(Map<String, Object> params) {
return new SQL() {
{
SELECT("*");
FROM("users");
if (params.get("username") != null) {
WHERE("username = #{username}");
}
if (params.get("email") != null) {
WHERE("email = #{email}");
}
}
}.toString();
}
}
-----------------------------------------------------------------------------------------------------
在Mapper接口中,使用 @SelectProvider 注解来指定动态SQL的提供者类和方法
@SelectProvider(type = UserProvider.class, method = "dynamicSQL")
List<User> searchUsers(UserCriteria criteria);
-----------------------------------------------------------------------------------------------------
在这里,UserProvider 类的 dynamicSQL 方法会根据传递的 criteria 参数动态生成SQL查询语句
实现了根据用户名和电子邮件地址查询用户的功能
-----------------------------------------------------------------------------------------------------
MyBatis动态注解开发是一种强大的方式,能够根据不同条件动态生成SQL语句,无需编写繁琐的XML映射文件
可以在接口方法上直接使用注解,减少了冗余的代码。它提供了灵活性和简洁性,适用于各种不同的数据库操作需求
f.@Param
a.介绍
接口绑定解决多参数的传递,一般使用@Param
@Param : 动态代理接口向映射文件中传递参数的时候,会使用@Param注解
并且如果动态代理接口使用了@Param这个注解,那么映射文件中的标签中可以不用写parameterType属性
可以直接接收@Param中传递来的参数值
b.@Param注解基本类型的参数
mapper中的方法:
public User selectUser(@Param("userName") String name,@Param("password") String pwd);
-------------------------------------------------------------------------------------------------
映射到xml中的标签
<select id="selectUser" resultMap="User">
select * from user where user_name = #{userName} and user_password=#{password}
</select>
其中where user_name = #{userName} and user_password = #{password}中的userName和password都是从注解@Param()里面取出来的,取出来的值就是方法中形式参数 String name 和 String pwd的值。
-------------------------------------------------------------------------------------------------
重点:当只存在一个参数的时候,此时可以省略这个@Param注解,但是两个参数必须使用这个注解
c.@Param注解JavaBean对象
SQL语句通过@Param注解中的别名把对象中的属性取出来然后复制 mapper中的方法:
public List<User> getAllUser(@Param("user") User u);
-------------------------------------------------------------------------------------------------
映射到xml中的标签
<select id="getAllUser" parameterType="com.vo.User" resultMap="userMapper">
select
from user t where 1=1
and t.user_name = #{user.userName}
and t.user_age = #{user.userAge}
</select>
-------------------------------------------------------------------------------------------------
注意:
当使用了@Param注解来声明参数的时候,SQL语句取值使用#{},${}取值都可以
当不使用@Param注解声明参数的时候,必须使用的是#{}来取参数。使用${}方式取值会报错
不使用@Param注解时,参数只能有一个,可以是一个基本的数据也可以是一个JavaBean
d.不使用@Param
接口绑定解决多参数的传递,如果不使用@Param,则使用数字的方式取出,从0开始
-------------------------------------------------------------------------------------------------
接口中定义方法
User selByUP(String username, String password
-------------------------------------------------------------------------------------------------
映射文件中提供对应的标签
此时, SQL语句中获取方式有两种, 通过#{index}或#{param+数字}的方式
<select id="selByUP" resultType="user">
select * from t_user where username=#{0} and password=#{1}
</select>
或者
<select id="selByUP" resultType="user">
select * from t_user where username=#{param1} and password=#{param2}
</select>
4.4 @value与@ConfigurationProperties
01.SpringBoot配置加载顺序:
properties文件 -> YAML文件 -> 系统环境变量 -> 命令行参数 - XML配置(推荐JAVA配置,可以使用 @ImportResource 引入XML配置)
02.@ConfigurationProperties(prefix = "student")与@value:二者可以互补使用
@ConfigurationProperties:同时绑定properties和yml方式的注值,支持批量注值
@value:支持单个注值
4.5 @ConfigurationProperties与@EnableConfigurationProperties
00.汇总
a.@ConfigurationProperties
用于定义属性绑定的 Java Bean,指定属性前缀
b.@EnableConfigurationProperties
用于启用属性绑定,通常在 Spring Boot 应用的启动类上使用
01.@ConfigurationProperties
a.定义
@ConfigurationProperties 是一个注解,用于将外部配置文件中的属性绑定到 Java Bean 上
b.原理
通过注解的 prefix 属性指定配置文件中的属性前缀
Spring Boot 会自动将匹配的属性值注入到 Java Bean 的对应字段中
c.常用 API
prefix:指定配置属性的前缀
d.使用步骤
1.创建一个 Java Bean,使用 @ConfigurationProperties 注解标记
2.在配置文件中定义属性
3.使用 @EnableConfigurationProperties 或通过自动扫描启用配置属性绑定
e.示例代码
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
// getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
f.在 application.properties 中定义属性
app.name=MyApplication
app.version=1.0.0
02.@EnableConfigurationProperties
a.定义
@EnableConfigurationProperties 是一个注解,用于启用 @ConfigurationProperties 注解的配置属性绑定
b.原理
通过在配置类上使用 @EnableConfigurationProperties
Spring Boot 会扫描并注册所有使用 @ConfigurationProperties 的 Bean
c.使用步骤
1.在 Spring Boot 应用的配置类上使用 @EnableConfigurationProperties 注解
2.指定需要启用的配置属性类
d.示例代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
5 区别
5.1 @RequestParam
01.问题复现
a.代码
@RequestMapping(path = "/hi4", method = RequestMethod.GET)
public String hi4(@RequestParam("name") String name, @RequestParam("address") String address){
return name + ":" + address;
};
b.说明
当需要特别多的请求参数时,我们往往会忽略其中一些参数是否可选
在访问 http://localhost:8080/hi4?name=xiaoming&address=beijing 时并不会出问题
但是一旦用户仅仅使用 name 做请求(即 http://localhost:8080/hi4?name=xiaoming )时,则会直接报错如下:
此时,返回错误码 400,提示请求格式错误:此处缺少 address 参数
c.疑问
既然不存在 address,address 应该设置为 null,而不应该是直接报错不是么
02.剖析问题
a.原因分析
要了解这个错误出现的根本原因,你就需要了解请求参数的发生位置
实际上,这里我们也能按注解名(@RequestParam)来确定解析发生的位置是在 RequestParamMethodArgumentResolver 中
为什么是它?
追根溯源,针对当前案例,当根据 URL 匹配上要执行的方法是 hi4 后,要反射调用它
必须解析出方法参数 name 和 address 才可以。而它们被 @RequestParam 注解修饰
所以解析器借助 RequestParamMethodArgumentResolver 就成了很自然的事情
b.RequestParamMethodArgumentResolver参数解析
a.内容
接下来我们看下 RequestParamMethodArgumentResolver 对参数解析的一些关键操作
参考其父类方法 AbstractNamedValueMethodArgumentResolver#resolveArgument
-------------------------------------------------------------------------------------------------
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
//省略其他非关键代码
//获取请求参数
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
//省略后续代码:类型转化等工作
return arg;
}
b.处理步骤
a.查看 namedValueInfo 的默认值,如果存在则使用它
这个变量实际是通过下面的方法来获取的,参考 RequestParamMethodArgumentResolver#createNamedValueInfo:
---------------------------------------------------------------------------------------------
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
---------------------------------------------------------------------------------------------
实际上就是 @RequestParam 的相关信息,我们调试下,就可以验证这个结论
b.在 @RequestParam 没有指明默认值时,会查看这个参数是否必须,如果必须,则按错误处理
判断参数是否必须的代码即为下述关键代码行:
---------------------------------------------------------------------------------------------
namedValueInfo.required && !nestedParameter.isOptional()
---------------------------------------------------------------------------------------------
很明显,若要判定一个参数是否是必须的,需要同时满足两个条件:
条件 1 是 @RequestParam 指明了必须(即属性 required 为 true,实际上它也是默认值)
条件 2 是要求 @RequestParam 标记的参数本身不是可选的
我们可以通过 MethodParameter#isOptional 方法看下可选的具体含义:
---------------------------------------------------------------------------------------------
public boolean isOptional() {
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(getContainingClass()) &&
KotlinDelegate.isOptional(this)));
}
---------------------------------------------------------------------------------------------
在不使用 Kotlin 的情况下,所谓可选,就是参数的类型为 Optional
或者任何标记了注解名为 Nullable 且 RetentionPolicy 为 RUNTIM 的注解
c.如果不是必须,则按 null 去做具体处理
如果接受类型是 boolean,返回 false,如果是基本类型则直接报错,这里不做展开
结合我们的案例,我们的参数符合步骤 2 中判定为必选的条件,所以最终会执行方法 AbstractNamedValueMethodArgumentResolver#handleMissingValue:
---------------------------------------------------------------------------------------------
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
throw new ServletRequestBindingException("Missing argument '" + name +
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
03.解决问题
a.设置 @RequestParam 的默认值
@RequestParam(value = "address", defaultValue = "no address") String address
b.设置 @RequestParam 的 required 值
@RequestParam(value = "address", required = false) String address)
c.标记任何名为 Nullable 且 RetentionPolicy 为 RUNTIME 的注解
//org.springframework.lang.Nullable 可以
//edu.umd.cs.findbugs.annotations.Nullable 可以
@RequestParam(value = "address") @Nullable String address
d.修改参数类型为 Optional
@RequestParam(value = "address") Optionaladdress
5.2 @RequestMapping与@GetMapping
01.位置
@RequestMapping:类、方法
@GetMapping:方法
02.请求
@RequestMapping:GET、POST、PUT、DELETE等请求方法
@GetMapping:@RequestMapping的GET请求方法的特例
5.3 @RequestBody与@ResponseBody
01.@RequestBody与@ResponseBody区别
a.@RequestBody
作用:将HTTP请求的正文内容转换为Java对象
用途:用于接收客户端发送的JSON、XML等格式的数据,并将这些数据自动映射到Java对象中
使用场景:通常用于处理POST、PUT等请求方法,其中请求体包含数据
-----------------------------------------------------------------------------------------------------
@PostMapping("/user")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 处理逻辑
return ResponseEntity.ok(user);
}
b.@ResponseBody
作用:将控制器方法的返回值直接写入HTTP响应正文中
用途:用于构建RESTful Web服务,返回的数据格式通常是JSON或XML
使用场景:通常用于返回数据给客户端,而不是返回一个视图(View)
-----------------------------------------------------------------------------------------------------
@GetMapping("/user/{id}")
public @ResponseBody User getUser(@PathVariable Long id) {
// 处理逻辑
return userService.findById(id);
}
c.总结
@RequestBody 用于将【HTTP请求的正文内容】转换为【Java对象】,通常用于【接收】客户端发送的数据
@ResponseBody 用于将【控制器方法的返回值】直接写入【HTTP响应正文】中,通常用于【返回数据】给客户端