Skip to content

一、Spring Web MVC注解

@RequestMapping

  • 注解说明:主要用途是将Web请求与请求处理类中的方法进行映射,可以作用到类或者方法上

  • 属性说明

    • name:相当于方法的注释,使方法更易理解

      java
      @RequestMapping(value = "/index",name = "第一个接口")
      @ResponseBody
      public String login() {
      	return "hello world";
      }
    • value:默认属性(如果只填一个属性时,属性名可以省略),属性支持通配符匹配

      java
      // 效果一样
      @RequestMapping(value="/login")
      @RequestMapping("/login")
      java
      // http://localhost:8080/login/test和http://localhost:8080/login/1都能访问该端口
      @RequestMapping("/login/*")
    • path:与value同义,也支持通配符匹配

    • method:指定请求类型,值为枚举类RequestMethod一个或者多个,如果不指定,说明该方法支持全部HTTP请求

      java
      // 接口只支持 GET 请求
      @RequestMapping(value = "/login",method = RequestMethod.GET)
      @ResponseBody
      public String login() {
      	return "success";
      }
      java
      // 接口同时支持 GET和POST 请求
      @RequestMapping(value = "/login",method = {RequestMethod.GET, RequestMethod.POST})
      @ResponseBody
      public String login() {
      	return "success";
      }
    • params:该属性指定请求中必须包含params指定的一个或者多个参数时,才能执行该请求

      java
      // http://localhost:8080/login?flag=0能访问
      // http://localhost:8080/login无法访问
      @RequestMapping(value = "/login",method = RequestMethod.GET, params = "flag")
      @ResponseBody
      public String login() {
      	return "success";
      }
      java
      // http://localhost:8080/login?flag=0能访问
      // http://localhost:8080/login?flag=1无法访问
      // http://localhost:8080/login无法访问
      @RequestMapping(value = "/login",method = RequestMethod.GET, params = "flag=0")
      @ResponseBody
      public String login() {
      	return "success";
      }
    • headers:该属性指定请求中必须包含headers指定的一个或者多个请求头时,才能执行该请求

      java
      // 请求的header中包含Referer请求头并且值为http://localhost:80才能访问
      @RequestMapping(path = "/login", headers="Referer=http://localhost:80")
      public String login() {
      	return "success";
      }
    • consumes:该属性指定请求的提交内容类型(Content-Type)必须包含consumes指定的一个或者多个请求头时,才能执行该请求

      java
      @RequestMapping(value = "/login", consumes = "application/json")
      @ResponseBody
      public String login() {
      	return "success";
      }
    • produces:该属性指定返回值的内容类型,注意:返回的内容类型必须是request请求头Accept中所包含的类型

      java
      @RequestMapping(value = "login", produces = "application/json")
      @ResponseBody
      public String login() {
      	return "success";
      }
      java
      // 还可以指定返回值的编码
      @RequestMapping(value = "login", produces = "application/json;charset=utf-8")
      @ResponseBody
      public String login() {
      	return "success";
      }
  • 注意:在使用 @RequestMapping 之前,请求处理类还需要使用 @Controller@RestController 进行标记

@RequestBody

  • 注解说明:将请求主题中的参数绑定到一个对象中,通常用于处理 POSTPUT 请求,该注解的实现依赖于Spring MVC的 HttpMessageConverter 接口,该接口定义了将请求体中的 JSON、XML等格式的数据转换成Java对象的逻辑

@RequestParam

  • 注解说明:用于将指定的请求参数赋值给方法中的形参

  • 属性说明

    • value:默认参数,请求参数名,如果和形参一致,不需要设置

      java
      // http://localhost:8080/test?id=123 名称一致,都为id,可省略属性值的指定
      @GetMapping("/test")
      public String test(@RequestParam String id) {
          return "success";
      }
      java
      // http://localhost:8080/test?id=123 将请求中的id参数赋值给userId
      @GetMapping("/test")
      public String test(@RequestParam("id") String userId) {
          return "success";
      }
    • name:与value同义

    • required:表示请求中是否包含该参数,默认为true,如果设置成true并且请求没有此参数,则抛出异常

      java
      // http://localhost:8080/test
      // @RequestParam默认required为true,请求中没有参数,报错 Required String parameter 'userId' is not present
      @GetMapping("/test")
      public String test(@RequestParam("id") String userId) {
          return "success";
      }
    • defaultValue:为属性设置默认值,当设置默认值时,required 属性会被设置成 false

      java
      // http://localhost:8080/test 后台拿到的是默认值456
      // http://localhost:8080/test?id=123 后台拿到的是123
      @GetMapping("/test")
      public String test(@RequestParam(defaultValue = "456") String id) {
          return "success";
      }
  • 代码示例:除了上面的属性用法以外,还有下面的扩展用法

    • 映射所有参数:使用 Map 封装多个参数(无需定义参数的名称和数量)

      java
      // http://localhost:8080/test?id=123&name=zhangsan
      @GetMapping("/test")
      public String test(@RequestParam Map<String, String> params) {
          System.out.println("Parameters are: " + allParams.entrySet());
          return "success";
      }
    • 多值参数

      java
      // http://localhost:8080/test?id=1,2,3 可以映射
      // http://localhost:8080/test?id=1&id=2&id=3 可以映射
      @GetMapping("/test")
      public String test(@RequestParam List<String> idList) {
          System.out.println("idList: " + idList);
          return "success";
      }

@PathVariable

  • 注解说明:将方法的参数与请求URL中的模板变量相绑定

  • 属性说明

    • value:指定URL中的模板变量,如果方法参数和模板变量一样,那么value可以省略

      java
      // http://localhost:8080/test/user/info/1
      @GetMapping("/test/{system}/info/{id}")
      public String test(@PathVariable(name = "system") String system, @PathVariable(value = "id") String id) {
          System.out.println("system: " + system);
          System.out.println("id: " + id);
          return "success";
      }
    • name:与value同义

    • required:是否必需,默认为true

@GetMapping

  • 注解说明:用于处理HTTP的 GET 请求,并将请求映射到具体的处理方法中,该注解相当于 @RequestMapping(method=RequestMethod.GET) 的快捷方式

@PostMapping

  • 注解说明:用于处理HTTP的 POST 请求,并将请求映射到具体的处理方法中,该注解相当于 @RequestMapping(method=RequestMethod.POST) 的快捷方式

@PutMapping

  • 注解说明:用于处理HTTP的 PUT 请求,并将请求映射到具体的处理方法中,该注解相当于 @RequestMapping(method=RequestMethod.PUT) 的快捷方式

@DeleteMapping

  • 注解说明:用于处理HTTP的 DELETE 请求,并将请求映射到具体的处理方法中,该注解相当于 @RequestMapping(method=RequestMethod.DELETE) 的快捷方式

@PatchMapping

  • 注解说明:用于处理HTTP的 PATCH 请求,并将请求映射到具体的处理方法中,该注解相当于 @RequestMapping(method=RequestMethod.PATCH) 的快捷方式

@Controller

  • 注解说明:@Controller是@Component注解的一个延伸注解,此注解用于标注Spring MVC的控制器
  • 属性说明
    • value:通过此属性显式定义Bean的名称,该属性也仅用于定义Bean的名称,与请求路径无关

@ResponseBody

  • 注解说明:该注解会自动将控制器中的方法返回值写入到HTTP响应中
  • 注意:@ResponseBody注解只能用在被@Controller注解标记的类中

@RestController

  • 注解说明:该注解相当于 @ResponseBody + @Controller 的快捷方式

@ControllerAdvice

  • 注解说明:@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用 @ExceptionHandler 注解进行标记,此外,在有必要的时候,可以使用 @InitBinder 在类中进行全局的配置,还可以使用 @ModelAttribute 配置与视图相关的参数

  • 属性说明

    • basePackages:增强指定包下的所有Controller,如果不指定,默认增强所有包下的Controller

      java
      @ControllerAdvice(basePackages = {"com.example.user", "com.example.order"})
    • value:与basePackages同义

    • basePackageClasses:增强指定类所在包以及其子包下的所有Controller

      java
      // 增强的是UserController类所在的包下的所有组件
      @ControllerAdvice(basePackageClasses = {com.example.user.UserController.class})
    • assignableTypes:增强指定的Controller,其他的Controller不会被增强

      java
      // 增强的只有UserController和OrderController类
      @ControllerAdvice(assignableTypes = {com.example.user.UserController.class, com.example.order.OrderController.class})
    • annotations:增强标记了指定注解的所有Controller

      java
      // 自定义注解
      @Documented
      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface ControllerMarkAnnotation {
      }
      java
      // 指定增强的标注了自定义注解的Controller
      @ControllerAdvice(annotations = ControllerMarkAnnotation.class)
      java
      // 被自定义注解标注了,被增强了
      @ControllerMarkAnnotation
      @RestController
      public class UserController {
      }
      
      // 未被自定义注解标注了,未被增强
      @RestController
      public class OrderController {
      }
  • 代码示例

    • @ControllerAdvice 配合 @ExceptionHandler 实现全局异常处理

      java
      @RestControllerAdvice
      public class GlobalExceptionHandler {
          private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
          
          // 处理@Validated @Valid校验表单时抛出的异常
          @ExceptionHandler(BindException.class)
          public AjaxResult handleBindException(BindException e) {
              List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
              List<String> msgList = fieldErrors.stream()
                      .map(o -> o.getDefaultMessage())
                      .collect(Collectors.toList());
              String messages = StringUtils.join(msgList.toArray(), ";");
      		log.error("Validation格式校验异常:-------------->{}", messages);
              
              return AjaxResult.error(messages);
          }
          
          // 处理@Validated @Valid校验json格式时抛出的异常
          @ExceptionHandler(MethodArgumentNotValidException.class)
          public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
              BindingResult bindingResult = e.getBindingResult();
              ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
              String messages = objectError.getDefaultMessage();
              log.error("MethodArgumentNotValidException异常:-------------->{}", messages);
      
              return AjaxResult.error(messages);
          }
          
          // 处理@NotBlank @NotNull @NotEmpty校验失败抛出的异常
          @ExceptionHandler(ConstraintViolationException.class)
          public AjaxResult handleConstraintViolationException(ConstraintViolationException e) {
              List<String> msgList = new ArrayList<>();
          	for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
              	msgList.add(constraintViolation.getMessage());
          	}
          	String messages = StringUtils.join(msgList.toArray(), ";");
          	log.error("entity格式校验异常:-------------->{}", messages);
              
              return AjaxResult.error(messages);
          }
          
          // 处理自定义异常...
          
          // 处理其他异常...
      }
    • @ControllerAdvice 配合 @ModelAttribute 预设全局数据

      1. 方式一

        java
        @ControllerAdvice
        public void GlobalControllerAdvice {
            @ModelAttritbute
            public void presetParam(Model model) {
                model.addAttribute("flag", "Y");
            }
        }
      2. 方式二

        java
        @ControllerAdvice
        public void GlobalControllerAdvice {
            @ModelAttritbute()
            public Map<String, String> presetParam() {
                Map<String, String> result = new HashMap<String, String>();
                result.put("key1", "value1");
                result.put("key2", "value2");
                result.put("key3", "value3");
                
                return result;
            }
        }
        
        // @ModelAttritbute()会将返回值的变量名作为key,例如上面return result,所以key则是"result",但是!不支持字符串作为key
        // @ModelAttritbute("myMap") 如果显式定义了key名,则key是"myMap"
      3. 全局参数的使用

        java
        @RestController
        public class UserController {
        
            @GetMapping("/test1")
            public String test01(Model model) { 
                Map<String, Object> modelMap = model.asMap();
                return (String) modelMap.get("flag");
            }
        
          
            @GetMapping("/test2")
            public String test02(@ModelAttribute("flag") String flag) {
                return flag;
            }
        
        
            @GetMapping("test3")
            public String test03(ModelMap modelMap) {
                return (String) modelMap.get("flag");
            }
            
        }
    • @ControllerAdvice 配合 @InitBinder 实现对请求参数的预处理

      java
      @ControllerAdvice
      public class GlobalControllerAdvice {
          @InitBinder
          public void processParams(WebDataBinder webDataBinder) {
              // 创建一个字符串微调编辑器,去掉字符串前后的空格
              StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
              
              /*
               * 注册自定义编辑器
               * 第一个参数requiredType: 需要处理的类型
               * 第二个参数propertyEditor: 属性编辑器,PropertyEditor的实现类
               */
              webDataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
          }
      }
      java
      // http://localhost:8080/test?name=%20%20zhangsan%20
      @RestController
      public class UserController {
          @GetMapping("/test")
          public String test(@RequestParam String name) {
              System.out.println("name: " + name); // 去掉前后空格的值zhangsan
              return "success";
          }
      }

@RestControllerAdvice

  • 注解说明:该注解相当于 @ResponseBody + @ControllerAdvice 的快捷方式

@ExceptionHandler

  • 注解说明:@ExceptionHandler用于标注处理特定类型异常类所抛出异常的方法,当控制器中的方法抛出异常时,Spring会自动捕获异常,并将捕获的异常信息传递给被@ExceptionHandler标注的方法
  • 属性说明
    • value:方法处理的异常类型,Throwable的子类

@ResponseStatus

  • 注解说明:@ExceptionHandler用于改变HTTP响应的状态码

  • 属性说明

    • code:http状态码,值为枚举类HttpStatus的属性,如HttpStatus.OK、HttpStatus.CREATED
    • value:与code同义
    • reason:错误信息
  • 代码示例

    • 用法一:@ResponseStatus加在自定义异常类上

      java
      @RestControllerAdvice
      public class GlobalExceptionHandler {
          
          @ResponseStatus(HttpStatus.BAD_REQUEST)
          @ExceptionHandler(Exception.class)
          public AjaxResult handleGlobalExceptionHandler(Exception e) {
              return AjaxResult.error();
          }
      }
    • 用法二:@ResponseStatus加在控制层方法上

      java
      @RestController
      public class UserController {
          
          @ResponseStatus(HttpStatus.BAD_REQUEST)
          @GetMapping("/test")
          public String test() {
              return "success";
          }
      }

@CrossOrigin

  • 注解说明:为请求处理类或者请求处理方法提供跨域调用支持

  • 属性说明

    • *origins:允许跨域的域名,如果想向所有域名开放,直接填 **
    • value:与origins同义
    • maxAge:预检请求的缓存时间,减少重复预检
    • originPatterns:允许跨域的域名通配符
    • allowedHeaders:服务端通过此项告知浏览器客户端可以携带哪些请求头
    • exposedHeaders:允许客户端访问的响应头
    • methods:允许客户端使用的HTTP方法
    • allowCredentials:是否允许客户端携带凭据
  • 代码示例

    • 作用到处理类上(生效的是类中的所有方法)

      java
      @CrossOrigin
      @RestController
      public class UserController {
          @GetMapping("/test")
          public String test() {
              return "success";
          }
      }
    • 作用到处理方法上

      java
      @RestController
      public class UserController {
          @CrossOrigin
          @GetMapping("/test")
          public String test() {
              return "success";
          }
      }

@ModelAttribute

  • 注解说明:@ModelAttribute将方法参数或者方法返回值绑定到命名的模型属性,并将其暴露给web视图

  • 代码示例*(数据预处理的使用在@ControllerAdvice已经演示过了,这里只对此注解在SpringMVC+模板引擎这种数据驱动视图的场景进行演示)*

    • @ModelAttribute作用到void返回值的方法上

      java
      // http://localhost:8080/info?username=zhangsan, 当用户在请求这个接口时,populateModel方法会在goToInfo方法前被调用,它将请求参数加入到了model属性username中,在此之后,goToInfo被调用
      
      @Controller
      public class UserController {
          @ModelAttribute
          public void populateModel(@RequestParam String username, Model model) {
              model.addAttribute("username", username);
          }
          
          @GetMapping("/info")
          public String goToInfo() {
              return "info";
          }
      }
    • @ModelAttribute作用到返回具体类的方法上

      java
      // http://localhost:8080/info?username=zhangsan, 当用户在请求这个接口时,populateModel方法会在goToInfo方法前被调用,model属性的名称没有指定,它由返回类型隐含表示,如此方法返回的是Account类 ,那么model属性的名称就是account
      
      @Controller
      public class UserController {
          @ModelAttribute
          public Account populateModel(@RequestParam String username) {
              return new Account(username);
          }
          
          @GetMapping("/info")
          public String goToInfo() {
              return "info";
          }
      }
    • @ModelAttribute(value="xxx")作用到返回具体类的方法上

      java
      // http://localhost:8080/info?username=zhangsan, 当用户在请求这个接口时,populateModel方法会在goToInfo方法前被调用,使用@ModelAttribute注释的value属性作为model属性的名称
      
      @Controller
      public class UserController {
          @ModelAttribute("username")
          public String populateModel(@RequestParam String username) {
              return username;
          }
          
          @GetMapping("/info")
          public String goToInfo() {
              return "info";
          }
      }
    • @ModelAttribute和@RequestMapping作用到同一个方法

      java
      // 此时方法的返回值就不是一个视图的名称了,而是model属性的值,属性的名称是由@ModelAttribute(value="")指定,视图名称由RequestToViewNameTranslator根据请求"/info.do"转换为逻辑视图info
      
      @Controller
      public class UserController {
          @ModelAttribute("username")
          @GetMapping("/info.do")
          public String goToInfo() {
              return "zhangsan";
          }
      }
    • @ModelAttribute作用到一个方法的参数上

      java
      @PostMapping("/saveProduct")
      public String saveProduct(@ModelAttribute Product product) {
          // product 对象已通过请求参数自动填充(如 name, price 等字段)
          productService.save(product);
          return "redirect:/products";
      }

@InitBinder

  • 注解说明:该注解用于标注初始化WebDataBinider 的方法,该方法用于对Http请求传递的表单数据进行处理,如时间格式化、字符串处理等

    java
    @ControllerAdvice
    public class GlobalControllerAdvice {
        @InitBinder
        public void initBinder(WebDataBinder webDataBinder) {
            // 将请求中的所有的字符串去掉前后空格
            StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
            webDataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
        }
    }
  • java.beans.PropertyEditor接口实现类说明:PropertyEditor使得开发者能够轻易地调用扩展类进行对象与String类型的转换

    类名描述
    BooleanEditor处理布尔类型(Boolean)的属性,将字符串"true""false" 转换为 Boolean 对象
    ByteEditor处理字节类型(Byte)的属性,将字符串转换为 Byte 对象
    ColorEditor处理颜色类型(Color)的属性,将十六进制颜色代码(如 #RRGGBB)转换为 Color 对象
    DoubleEditor处理双精度浮点数(Double)的属性,将字符串解析为 Double 类型
    FloatEditor处理单精度浮点数(Float)的属性,将字符串解析为 Float 类型
    IntegerEditor处理整数类型(Integer)的属性,将字符串解析为 Integer 类型
    LongEditor处理长整型类型(Long)的属性,将字符串解析为 Long 类型
    ShortEditor处理短整型类型(Short)的属性,将字符串解析为 Long 类型
    StringEditor处理字符串类型(String)的属性,直接返回或设置字符串,无需额外解析
    EnumEditor处理枚举类型(Enum)的属性,将字符串转换成枚举常量或将枚举常量转换成字符串
    FontEditor处理字体类型(Font类)的属性,将字符串(例如 "Arial,PLAIN,12")转换为 Font 对象
  • 自定义PropertyEditor扩展类:java提供了一个默认的实现类java.beans.PropertyEditorSupport,开发者只需要重写setAsTextgetAsText方法就可以实现String类型到特定类型的转换了

    java
    // 例如: String转成Integer的扩展类
    public class String2IntegerPropertyEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            this.setValue(Integer.valueOf(text));
        }
    }
    
    public class App {
    
        public static void main(String[] args) {
            String text = "520";
    
            String2IntegerPropertyEditor editor = new String2IntegerPropertyEditor();
            editor.setAsText(text);
            
            Object resultVal = editor.getValue();
            System.out.println("值是:"); // 520
            System.out.println(resultVal);
            System.out.println("类型是:"); // java.lang.Integer
            System.out.println(resultVal.getClass());
        }
    }

二、Spring Bean注解

@ComponentScan

  • 注解说明:@ComponentScan指定Spring容器自动扫描组件并注册组件(被 @Component@Service@Repository@Controller 标注的组件)

  • 属性说明

    java
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class) // 可重复注解
    public @interface ComponentScan {
    
       @AliasFor("basePackages")
       String[] value() default {}; // 基础包名,等同于basePackages
    
       @AliasFor("value")
       String[] basePackages() default {}; // 基础包名,value
    
       Class<?>[] basePackageClasses() default {}; // 扫描的类,会扫描该类所在包及其子包的组件。
    
       Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; // 注册为BeanName生成策略 默认BeanNameGenerator,用于给扫描到的Bean生成BeanName
    
       Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; // 用于解析bean的scope的属性的解析器,默认是AnnotationScopeMetadataResolver
    
       ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; // scoped-proxy 用来配置代理方式 // no(默认值):如果有接口就使用JDK代理,如果没有接口就使用CGLib代理 interfaces: 接口代理(JDK代理) targetClass:类代理(CGLib代理)
    
       String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; // 配置要扫描的资源的正则表达式的,默认是"**/*.class",即配置类包下的所有class文件。
      
       boolean useDefaultFilters() default true; // useDefaultFilters默认是true,扫描带有@Component ro @Repository ro @Service ro @Controller 的组件
    
       Filter[] includeFilters() default {}; // 包含过滤器
    
       Filter[] excludeFilters() default {}; // 排除过滤器
    
       boolean lazyInit() default false; // 是否是懒加载
    
       @Retention(RetentionPolicy.RUNTIME)
       @Target({})
       @interface Filter { // 过滤器注解
    
          FilterType type() default FilterType.ANNOTATION; // 过滤判断类型
    
          @AliasFor("classes")
          Class<?>[] value() default {}; // 要过滤的类,等同于classes
    
          @AliasFor("value")
          Class<?>[] classes() default {}; // 要过滤的类,等同于value
    
          String[] pattern() default {}; // 正则化匹配过滤
    
       }
    }
  • 代码示例

    • @ComponentScan的基本用法

      java
      // 使用@ComponentScan注解指定扫描com.example包中的组件
      @Configuration
      @ComponentScan(basePackages = "com.example")
      public class AppConfig {
      }
      java
      // 在Spring应用中通过AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext来加载配置类
      public class Application {
          public static void main(String[] args) {
              ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
      
              MyService myService = context.getBean(MyService.class);
              myService.doSomething();
          }
      }
    • @ComponentScan指定扫描多个包路径

      java
      @Configuration
      @ComponentScan(basePackages = {"com.example.service", "com.example.repository"})
      public class AppConfig {
      }
    • @ComponentScan指定扫描的类以及类所在包及其子包的所有组件

      java
      @Configuration
      @ComponentScan(basePackageClasses = {MyService.class, MyRepository.class})
      public class AppConfig {
      }
    • @ComponentScan排除特定的组件

      java
      // 排除了com.example下的service包的所有组件
      @Configuration
      @ComponentScan(basePackages = "com.example",
                     excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com\\.example\\.service\\..*"))
      public class AppConfig {
      }
    • @ComponentScan指定扫描的类带有自定义注解的所有组件

      java
      // @ComponentScan扫描包含了所有带有MyCustomAnnotation注解的组件
      @Configuration
      @ComponentScan(basePackages = "com.example",
                     includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyCustomAnnotation.class))
      public class AppConfig {
      }

@Component

  • 注解说明:@Component注解用于标注一个普通的组件类,它没有明确的业务范围,只是通知Spring被此注解的类需要被纳入到Spring Bean容器中并进行管理

@Service

  • 注解说明:@Service注解是@Component的一个延伸,用于标注业务逻辑类

@Repository

  • 注解说明:@Repository注解是@Component的一个延伸,用于标注dao层数据持久化的类

三、Spring Dependency Inject注解

@DependsOn

  • 注解说明:@DependsOn注解可以定义在类和方法上,意思是我这个组件要依赖于另一个组件,也就是说被依赖的组件会比该组件先注册到IOC容器中

  • 代码演示

    • 没使用@DependsOn时,因为spring默认扫描包时会根据文件在文件夹的位置先后顺序扫描加载,而EventSource 文件位置在EventTListener前面,所以会先加载EventSource 事件源组件

      java
      /**
       * 事件源
       */
      @Component
      public class EventSource {
          public EventSource(){
              System.out.println("事件源创建");
          }
      }
      
      /**
       * 监听类
       */
      @Component
      public class EventListener {
          public EventListener(){
              System.out.println("监听器创建");
          }
      }
      
      @Configuration
      @ComponentScan(basePackages = "com.example")
      public class AppConfig {
      }
      
      // 事件源创建
      // 监听器创建
    • 使用@DependsOn,value属性必填!

      java
      /**
       * 事件源
       */
      @Component
      @DependsOn(value = {"eventListener"})
      public class EventSource {
          public EventSource(){
              System.out.println("事件源创建");
          }
      }
      
      /**
       * 监听类
       */
      @Component
      public class EventListener {
          public EventListener(){
              System.out.println("监听器创建");
          }
      }
      
      @Configuration
      @ComponentScan(basePackages = "com.example")
      public class AppConfig {
      }
      
      // 监听器创建
      // 事件源创建
  • 注意事项

    • 依赖一个bean

      java
      @DependsOn("b")
      public class A {
      	public A() {
              System.out.println("a创建");
          }
      }
      
      public class B {
      	public B() {
              System.out.println("b创建");
          }
      }
      
      // b创建
      // a创建
    • 依赖多个bean

      java
      @DependsOn({"b", "c"})
      public class A {
      	public A() {
              System.out.println("a创建");
          }
      }
      
      public class B {
      	public B() {
              System.out.println("b创建");
          }
      }
      
      public class C {
      	public C() {
              System.out.println("C创建");
          }
      }
      
      // b创建
      // c创建
      // a创建

@Bean

  • 注解说明

    • @Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。 产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中
    • @Bean是一个方法级别的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里
  • 为什么要使用@Bean?

    • 类似@Compnent、@Repository、@Controller、@Service这些注册Bean的注解局限性,只能局限作用于自己编写类,如果是一个jar包的第三方库的类对象要加入IOC容器的话,这些注解就心有余力不足了,这时候就轮到@Bean注解登场了
  • 属性说明

    • value:bean的名称,如果没有指定,默认是注解所标注的方法的名

    • name:与value同义

    • autowire:自动装配,默认不开启,建议尽量不要开启,因为自动装配不能装配基本数据类型、字符串、数组等,这是自动装配设计的局限性,并且自动装配不如依赖注入精确

    • initMethod:bean初始化之前的执行方法,一般也不怎么用,初始化前的逻辑也完全可以在方法中编写

    • destroyMethod:bean销毁时执行的方法,如果存在该方法但是不想执行该方法,可以使用@Bean(destroyMethod="")阻止销毁方法的执行

      java
      public class Person {
      
          public Person() {
              System.out.println("执行Person构造器创建Person对象");
          }
      
          public void init() {
              System.out.println("initMethod");
          }
      
          public void destroy() {
              System.out.println("destroyMethod");
          }
      }
      java
      @Configuration
      public class BeanConfig {
      
          // 可以自己指定方法名,也可以不指定,使用Spring默认提供的方法
          @Bean(initMethod = "init", destroyMethod = "destroy")   
          // singleton单例,在IOC容器销毁时,就会调用 destroyMethod() 方法来将 Bean对象销毁;prototype多例,它的Bean实例对象则不受IOC容器的管理,最终由GC来销毁
          @Scope(value = "singleton")
          public Person person(){
              return new Person();
          }
      }
  • @Bean注解与其他注解一起使用

@Import

  • 注解说明:

    • 该注解允许将一个或多个配置类、组件类导入到当前Spring容器中
    • 该注解允许将动态注册逻辑导入到当前Spring容器中
  • 代码示例

    • 导入一个或多个配置类(如将数据库配置、安全配置等模块拆分到不同类中,通过@Import统一加载)

      java
      @Configuration
      @Import({DatabaseConfig.class, SecurityConfig.class}) // 导入多个配置类
      public class AppConfig {
          // 主配置类
      }
    • 导入普通类作为Bean(如导入第三方库的类(无法修改源码添加 @Component 注解)作为Bean)

      java
      @Configuration
      @Import(ExternalService.class) // 将第三方库的类 ExternalService 注册为 Bean
      public class AppConfig {
      }
    • 使用 ImportSelector 动态选择配置(如根据条件(如环境变量、系统属性)动态决定导入哪些配置)

      java
      // step1: 实现ImportSelector接口
      // step2: 返回需要导入的配置类的全限定名数组
      
      public class EnvBasedImportSelector implements ImportSelector {
          @Override
          public String[] selectImports(AnnotationMetadata metadata) {
              String env = System.getProperty("spring.profiles.active", "dev");
              if ("prod".equals(env)) {
                  return new String[] {ProdConfig.class.getName()};
              } else {
                  return new String[] {DevConfig.class.getName()};
              }
          }
      }
      
      @Configuration
      @Import(EnvBasedImportSelector.class) // 根据环境动态导入配置
      public class AppConfig {
      }
    • 使用 ImportBeanDefinitionRegistrar 编程式注册Bean(需要完全控制 Bean 的注册逻辑时)

      java
      public class CustomBeanRegistrar implements ImportBeanDefinitionRegistrar {
          @Override
          public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
              // 动态注册一个 ExternalService类的Bean
              GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
              beanDefinition.setBeanClassName(ExternalService.class.getName());
              registry.registerBeanDefinition("externalService", beanDefinition);
          }
      }
      
      @Configuration
      @Import(CustomBeanRegistrar.class) // 编程式注册 Bean
      public class AppConfig {
      }
    • 组合注解简化 @Import

      java
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      @Import({DatabaseConfig.class, CacheConfig.class}) // 隐藏导入细节
      public @interface EnableInfrastructure {
      }
      
      @Configuration
      @EnableInfrastructure // 等效于 @Import({DatabaseConfig.class, CacheConfig.class})
      public class AppConfig {
      }

四、Bean Scops注解

@Scope

  • 注解说明:@Scope注解的作用是用来配置IOC容器中Bean实例的作用域

  • 属性说明

    • value:指定作用域名称

      作用域名称描述、
      singleton默认作用域,整个IOC容器中只存在一个实例,所有依赖共享该实例
      采用的是饿汉加载(容器启动,Bean实例就创建好了),如果想让bean变成懒汉加载,使用@Lazy注解
      prototype每次获取(如getBean() 或者 注入)都会创建一个新实例
      采用的是懒汉加载(IOC容器启动的时候,并不会创建Bean实例,而是在第一次使用的时候才会创建)
      request每个 HTTP 请求生成一个新实例(仅 Web 应用有效)
      session每个用户会话生成一个实例(仅 Web 应用有效)
      application整个 Web 应用生命周期内共享一个实例(类似 ServletContext
      websocket每个 WebSocket 会话生成一个实例(Spring 5+)
    • scopeName:与value同义

    • proxyMode:指定作用域代理模式,用于解决作用域生命周期不一致的问题

  • 代码示例

    • @Component 类上指定作用域

      java
      @Component
      @Scope("prototype") 
      public class ShoppingCart {
      }
    • @Bean 方法上指定作用域

      java
      @Configuration
      public class AppConfig {
          @Bean
          @Scope("prototype")
          public UserService userService() {
              return new UserService();
          }
      }
  • 快捷注解

    • @RequestScope <=> @Scope("request")
    • @SessionScope <=> @Scope("session")
    • @ApplicationScope <=> @Scope("application")

五、容器配置注解

@Autowired和@Resource

  • @Autowired和@Resource的对比

    @Autowired@Resource
    来源Spring框架Java EE 规范(JSR-250),Spring 兼容支持
    默认匹配策略类型 匹配名称 匹配(若未指定名称,则回退到类型匹配)
    名称指定方式需配合 @Qualifier 注解指定名称直接通过name属性指定名称
    依赖查找循序1. 按类型 → 2. 按名称(需 @Qualifier1. 按名称 → 2. 按类型
    是否支持required=false支持不支持(默认必须存在)
    支持位置字段、构造函数、Setter 方法字段、Setter 方法
  • @Autowired的代码示例

    • 字段、构造函数、Setter 方法注入

      java
      @Service
      public class OrderService {
          // 字段注入(按类型匹配)
          @Autowired
          private PaymentService paymentService;
          
          // 构造函数注入(推荐)
          @Autowired
          public OrderService(PaymentService paymentService) {
              this.paymentService = paymentService;
          }
      
          // Setter 方法注入
          @Autowired
          public void setPaymentService(PaymentService paymentService) {
              this.paymentService = paymentService;
          }
      }
    • 允许依赖不存在

      java
      @Autowired(required = false) // 若找不到 Bean,注入 null
      private Optional<SomeService> someService;
  • @Resource的代码示例

    • 字段、Setter 方法注入

      java
      @Service
      public class OrderService {
          // 字段注入(默认按字段名 "paymentService" 匹配)
          @Resource
          private PaymentService paymentService;
      
          // 显式指定名称注入
          @Resource(name = "alipayService")
          private PaymentService paymentService;
      
          // Setter 方法注入
          @Resource
          public void setPaymentService(PaymentService paymentService) {
              this.paymentService = paymentService;
          }
      }

@Lazy

  • 注解说明:标明一个bean是否懒汉加载(第一次用的时候才会创建实例),当作用在被 @Component 标注的类上或 @Component 的延伸注解(如 @Controller、@Service、@Repository等)时,表明该类中的所有bean都延迟加载

  • 代码示例

    • @Lazy作用在组件类上

      java
      @Lazy
      @Configuration
      @ComponentScan(basePackages = "com.example")
      public class AppConfigWithLazy {
          @Bean
          public MyBean myBean(){
              System.out.println("myBean Initialized");
              return new MyBean();
          }
      
          @Bean
          public MyBean myBean2(){
              System.out.println("myBean2 Initialized");
              return new MyBean();
          }
      }
      java
      // 上面的方法也相当于
      @Configuration
      @ComponentScan(basePackages = "com.example")
      public class AppConfigWithLazy {
          @Bean
          @Lazy
          public MyBean myBean(){
              System.out.println("myBean Initialized");
              return new MyBean();
          }
      
          @Bean
          @Lazy
          public MyBean myBean2(){
              System.out.println("myBean2 Initialized");
              return new MyBean();
          }
      }
    • @Lazy与@Autowired一起使用:当@Lazy注解与@Autowired一起使用时,Spring会为依赖项生成一个代理对象,而非立即初始化真实Bean,真实Bean的初始化会延迟到第一次实际使用该依赖时(如调用其的方法)

      java
      @Service
      public class ServiceA {
          private final ServiceB serviceB;
      
          @Autowired
          public ServiceA(@Lazy ServiceB serviceB) { // 延迟注入 ServiceB
              this.serviceB = serviceB;
          }
      }
      java
      // bean的注入也可以写成下面的方式
      @Service
      public class ServiceA {
          @Lazy
          @Autowired
          private ServiceB serviceB;
      }
  • @Lazy的使用场景

    • 解决循环依赖:若两个单例Bean相互依赖,可以通过@Lazy打破初始化顺序导致的循环依赖问题

      java
      @Service
      public class ServiceA {
          private final ServiceB serviceB;
      
          @Autowired
          public ServiceA(@Lazy ServiceB serviceB) { // 延迟注入 ServiceB
              this.serviceB = serviceB;
          }
      }
      
      @Service
      public class ServiceB {
          @Autowired
          private ServiceA serviceA; // 正常注入 ServiceA
      }
    • 优化启动性能:避免加载启动时不需要的“重型”Bean(如数据库连接池、第三方服务客户端)

      java
      @Service
      public class ReportService {
          @Autowired
          @Lazy  // 延迟初始化大型报表生成器
          private ReportGenerator heavyReportGenerator;
      
          public void generateReport() {
              heavyReportGenerator.generate(); // 第一次调用时初始化
          }
      }

@Primary

  • 注解说明:该注解是Spring框架中用于解决多个同类型Bean的依赖注入冲突的注解,通过标记某个Bean为“首选”,使得Bean在自动装配时,Spring会优先选择该Bean,避免因存在多个候选Bean而抛出异常

  • 代码示例

    • @Primary作用在组件类上

      java
      @Component
      @Primary
      public class PrimaryDataSource implements DataSource {
          // 主数据源实现
      }
      
      @Component
      public class BackupDataSource implements DataSource {
          // 备份数据源实现
      }
      
      // 使用时,优先装配PrimaryDataSource
      private DataSource dataSource;
    • @Primary作用在@Bean方法上

      java
      @Configuration
      public class AppConfig {
          @Bean
          @Primary
          public DataSource primaryDataSource() {
              return new PrimaryDataSource();
          }
      
          @Bean
          public DataSource backupDataSource() {
              return new BackupDataSource();
          }
      }
      
      // 使用时,优先装配PrimaryDataSource
      private DataSource dataSource;
  • @Primary 和 @Qualifier 的区别

    @Primary@Qualifier
    优先级高,和@Primary同时使用时,会覆盖@Primary
    使用场景定义全局默认的Bean按名称精确指定Bean
    是否需要指定名称属性
    作用范围影响所有注入点仅影响当前注入点

@Qualifier

  • 注解说明:当容器中存在多个相同类型的 Bean 时,通过 @Qualifier 可以显式指定要注入的具体 Bean,避免因类型冲突导致 NoUniqueBeanDefinitionException 异常

  • 代码示例

    • 按照Bean名称注入

      java
      public interface PaymentService {
          void pay();
      }
      
      @Component("alipayService") // 指定 Bean 名称
      public class AlipayService implements IPaymentService {
          @Override
          public void pay() { /* 支付宝逻辑 */ }
      }
      
      @Component("wechatPayService") // 指定 Bean 名称
      public class WechatPayService implements IPaymentService {
          @Override
          public void pay() { /* 微信支付逻辑 */ }
      }
      
      @Service
      public class OrderService {
          @Autowired
          @Qualifier("alipayService") // 按名称注入
          private IPaymentService paymentService;
      }
    • 结合@Bean方法

      java
      @Configuration
      public class PaymentConfig {
          @Bean("alipay")
          public PaymentService alipayService() {
              return new AlipayService();
          }
      
          @Bean("wechat")
          public PaymentService wechatPayService() {
              return new WechatPayService();
          }
      }
      
      @Service
      public class OrderService {
          @Autowired
          @Qualifier("wechat") // 注入指定 Bean
          private PaymentService paymentService;
      }
    • 用于构造函数或Setter方法

      java
      // 用于构造函数
      @Service
      public class OrderService {
          private final PaymentService paymentService;
      
          @Autowired
          public OrderService(@Qualifier("wechatPayService") PaymentService paymentService) {
              this.paymentService = paymentService;
          }
      }
      java
      // 用于Setter方法
      @Service
      public class OrderService {
          private PaymentService paymentService;
          
          @Autowired
          public void setPaymentService(@Qualifier("wechatPayService") PaymentService paymentService) {
              this.paymentService = paymentService;
          }
      }
    • 使用组合注解标注Bean和注入Bean

      java
      @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Qualifier("alipay") // 组合注解
      public @interface Alipay {
      }
      
      @Component
      @Alipay // 使用自定义注解标记 Bean
      public class AlipayService implements PaymentService { /* ... */ }
      
      @Service
      public class OrderService {
          @Autowired
          @Alipay // 通过注解注入
          private PaymentService paymentService;
      }

@PostConstruct

  • 注解说明:用于标记一个Bean的方法依赖注入(如构造器注入、Setter注入、字段注入等)完成后,Bean正式投入使用前被调用,经典使用场景如初始化资源(如数据库连接)、加载配置、预缓存数据

  • 添加@PostConstruct注解的方法需要满足以下几点

    • 访问权限:非 private(通常为 public
    • 参数列表:无参数
    • 返回值:void
    • 静态性:非 static 方法
  • 代码示例

    java
    import javax.annotation.PostConstruct;
    
    @Service
    public class UserServiceImpl implements IUserService {
        @Autowired
        private IOrderService orderService;
    
        @PostConstruct
        public void init() {
            // @PostConstruct方法中,该类的所有依赖都已注入完成,可安全使用
            orderService.getAllOrder();
            System.out.println("数据初始化完成");
        }
    }
  • 注意

    • 通常一个Bean定义一个 @PostConstruct 方法,若存在多个 @PostConstruct 方法,执行顺序不确定(依赖方法声明顺序,但不同 JVM 可能表现不同),所以不建议使用多个
    • 单例Bean@PostConstruct仅执行一次,原型Bean每次创建新实例都会执行一次

@PreDestroy

  • 注解说明:用于标记一个方法在 Bean 被容器销毁之前自动执行,在 Spring 框架中,它常用于执行资源清理、关闭连接等收尾工作

  • 添加@PostConstruct注解的方法需要满足以下几点

    • 访问权限:非 private(通常为 public
    • 参数列表:无参数
    • 返回值:void
    • 静态性:非 static 方法
  • 代码示例

    java
    @Component
    public class DatabaseConnection {
        private Connection connection;
    
        @PostConstruct
        public void init() {
            // 初始化数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
        }
    
        @PreDestroy
        public void cleanup() {
            // 容器销毁前关闭连接
            if (connection != null) {
                try {
                    connection.close();
                    System.out.println("数据库连接已关闭");
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 注意

    • 单例Bean(singleton),容器关闭时,@PreDestroy方法会被调用

    • 原型Bean(prototype),默认不会触发@PreDestroy,因为Spring不管理原型Bean的生命周期,若需清理,需要手动调用销毁方法

      java
      public class PrototypeBean {
          public void destroy() {
              // 手动清理逻辑
          }
      }
      
      // 使用方手动调用
      context.getBean(PrototypeBean.class).destroy();

六、Spring/SpringBoot注解

@Order

  • 注解说明:@Order的作用是定义Spring IOC容器中Bean的 执行顺序 优先级,而不是Bean的加载顺序
  • 底层原理
    • @Order 底层是在 Bean 注入 IOC 容器之后执行的,所以无法控制 Bean 的加载顺序
    • @Order 底层是通过 List.sort(Comparator) 实现的,AnnotationAwareOrderComparator 类集成 OrderComparator 类,通过获取注解的 value 值实现了比对逻辑
  • 代码示例
  • 注意
    • @Order注解只能用于Spring IOC容器中的Bean,普通的类不适用

@SpringBootApplication

@EnableAutoConfiguration

@ConfigurationProperties

  • 注解说明:

  • 使用场景之一:多层嵌套参数的加载方式

    yml
    spring:
      kafka:
        bootstrap-servers: xxx.xxx.xxx.xxx:9092
        producer:
          retries: 0
          batch-size: 16384
          buffer-memory: 33554432
          key-serializer: org.apache.kafka.common.serialization.StringSerializer
          value-serializer: org.apache.kafka.common.serialization.StringSerializer
        consumer:
          group-id: views.invoke
          auto-offset-reset: earliest
          enable-auto-commit: true
          max-poll-records: 100
          max-poll-interval-ms: 60000
          auto-commit-interval: 100
          key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
          value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    java
    @Data
    @Component
    @ConfigurationProperties(prefix = "spring.kafka")
    public class KafkaConfig {
    
        private String bootstrapServers;
        // prefix只截止到spring.kafka,所以自此往下,每新增一层,就用一个对象代替
        // 比如spring.kafka.producer,就需要用一个属性名producer接收,属性的类型可以自定义,为了统一,我也写成Producer
        // 同理,如果producer下还有多层,就需要在Producer类中再定义属性接收
        private Producer producer;
        private Consumer consumer;
    
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public static class Producer {
            private String retries;
            private String batchSize;
            private String bufferMemory;
            private String keySerializer;
            private String valueSerializer;
        }
    
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public static class Consumer {
            private String groupId;
            private String autoOffsetReset;
            private String enableAutoCommit;
            private String maxPollRecords;
            private String maxPollIntervalMs;
            private String autoCommitInterval;
            private String keyDeserializer;
            private String valueDeserializer;
        }
    
    }

@ConditionalOnClass

@ConditionalOnMissingClass

@ConditionalOnBean

@ConditionalOnMissingBean

@ConditionalOnProperty

@ConditionalOnResource

@ConditionalOnWebApplication

@ConditionalOnNotWebApplication

@ConditionalExpression

@Conditional

@Profile

  • 注解说明:@Profile注解的作用是指定类或者方法在特定的profile环境生效,任何 @Component 或者 @Configuration 注解的类都可以使用@Profile注解,环境是由 spring.profiles.active 控制

  • 代码示例

    • @Profile修饰类

      java
      @Configuration
      @Profile("prod")
      public class JndiDataConfig {
      
          @Bean(destroyMethod="")
          public DataSource dataSource() throws Exception {
              Context ctx = new InitialContext();
              return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
          }
      }
    • @Profile修饰方法

      java
      @Configuration
      public class AppConfig {
      
          @Bean("dataSource")
          @Profile("dev")
          public DataSource standaloneDataSource() {
              return new EmbeddedDatabaseBuilder()
                  .setType(EmbeddedDatabaseType.HSQL)
                  .addScript("classpath:com/bank/config/sql/schema.sql")
                  .addScript("classpath:com/bank/config/sql/test-data.sql")
                  .build();
          }
      
          @Bean("dataSource")
          @Profile("prod")
          public DataSource jndiDataSource() throws Exception {
              Context ctx = new InitialContext();
              return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
          }
      }
  • @Profile修饰注解:@Profile支持定义到其他注解之上,例如下面自定义的注解就可以使注入的bean使用dev的场景

    java
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Profile("prod")
    public @interface Production {
    }
    java
    // 使用场景一: 标注配置类,只有profile激活为prod时,ProdDatabaseConfig才生效
    @Production
    @Configuration
    public class ProdDatabaseConfig {
        // 生产环境的数据库配置(如连接池、集群地址)
        @Bean
        public DataSource dataSource() {
            return new HikariDataSource(...);
        }
    }
    java
    // 使用场景二: 标注组件类,只有profile激活为prod时,才会创建ProdEmailService的Bean
    @Production
    @Component
    public class ProdEmailService implements EmailService {
        // 生产环境的邮件发送实现(如使用企业 SMTP)
        public void sendEmail() { ... }
    }
    java
    // 使用场景三: 标注Bean方法,根据 Profile 动态选择缓存实现
    @Configuration
    public class AppConfig {
        @Production
        @Bean
        public CacheManager prodCacheManager() {
            return new RedisCacheManager(...); // 生产环境用 Redis 缓存
        }
    
        @Profile("dev") // 这里也可以定义一个@Development注解,仅做对比演示所以就不定义了
        @Bean
        public CacheManager devCacheManager() {
            return new SimpleCacheManager(); // 开发环境用简单缓存
        }
    }
    • @Profile的匹配规则(支持逻辑运算符)

      java
      // 非开发环境生效
      @Profile("!dev")
      java
      // 开发环境和生产环境同时激活时生效
      @Profile("dev & prod")
      java
      // 开发环境或生产环境任一激活时生效
      @Profile("dev & prod")

@ConfigurationProperties

  • 注解说明:该注解用于将外部配置属性绑定到Java对象上。通过使用这个注解,开发者可以将配置文件(如application.propertiesapplication.yml)中的属性值自动映射到Java类的字段上,从而实现配置的集中管理和类型安全

  • 代码示例

    • 基本使用

      properties
      # properties文件
      esign.v3.appId=3213213
      esign.v3.appSecret=4352hjvhgvn3w132123
      esign.v3.hostAddress=https://openapi.esign.cn
      yml
      # yml文件
      esign:
        v3:
          appId: 3213213
          appSecret: 4352hjvhgvn3w132123
          hostAddress: https://openapi.esign.cn
      java
      @Data
      @Configuration
      @ConfigurationProperties(prefix = "esign.v3")
      public class EsignConfig {
          private String appId;
          private String appSecret;
          private String hostAddress;
      }
    • 嵌套属性(类)

      properties
      # properties文件
      esign.v3.appId=3213213
      esign.v3.appSecret=4352hjvhgvn3w132123
      esign.v3.hostAddress=https://openapi.esign.cn
      esign.v3.callback.host=http://111.222.333.444:8000
      esign.v3.callback.signNotifyUrl=/api/esign/v3/callback/signNotify
      yml
      # yml文件
      esign:
        v3:
          appId: 3213213
          appSecret: 4352hjvhgvn3w132123
          hostAddress: https://openapi.esign.cn
          callback:
            host: http://111.222.333.444:8000
            signNotifyUrl: /api/esign/v3/callback/signNotify
      java
      @Data
      @Configuration
      @ConfigurationProperties(prefix = "esign.v3")
      public class EsignConfig {
          private String appId;
          private String appSecret;
          private String hostAddress;
          private Callback callback;
      
          @Data
          public static class Callback {
              private String host;
              private String signNotifyUrl;
          }
      }
    • 嵌套属性(List)

      properties
      # properties文件
      org.userList[0]=zhangsan
      org.userList[1]=lisi
      yml
      # yml文件
      org:
      	userList:
      		- zhangsan
      		-lisi
      java
      @Data
      @Configuration
      @ConfigurationProperties(prefix = "org")
      public class OrgConfig {
      	private List<String> userList;
      }
    • 嵌套属性(Map)

      properties
      # properties文件
      org.credentials.appId=3213213
      org.credentials.appSecret=4352hjvhgvn3w132123
      yml
      # yml文件
      org:
      	credentials:
      		appId: 3213213
      		appSecret: 4352hjvhgvn3w132123
      java
      @Data
      @Configuration
      @ConfigurationProperties(prefix = "org")
      public class OrgConfig {
      	private Map<String, String> credentials;
      }

@Value

  • 注解说明:该注解用于将外部配置属性绑定到Java对象的属性上

  • 代码示例

    • 字符串注入

      properties
      app.name=zhangsan
      java
      @Data
      @Component
      public class AppConfig {
          @Value("${app.name}")
          private String name;
      }
    • 数字注入

      properties
      app.count=200
      java
      @Data
      @Component
      public class AppConfig {
          // 设置默认值100
          @Value("${app.count:100}")
          private Integer count;
      }
    • 布尔值注入

      properties
      app.enabled=false
      java
      @Data
      @Component
      public class AppConfig {
          // 设置默认值true
          @Value("${app.enabled:true}")
          private Boolean enabled;
      }
    • 数组注入

      properties
      app.roles=ROLE_GUEST,ROLE_USER
      java
      @Data
      @Component
      public class AppConfig {
          // 设置默认值
          @Value("${app.roles:ROLE_USER,ROLE_ADMIN}")
          private String[] roles;
      }
    • 列表注入(使用SpEL表达式)

      properties
      app.roles=ROLE_GUEST,ROLE_USER
      java
      @Data
      @Component
      public class AppConfig {
          @Value("#{'${app.roles}'.split(',')}")
          private List<String> roles;
      }
    • Map注入

      properties
      app.credentials={key1:value1,key2:value2}
      yml
      app:
      	credentials:
      		key1: value1
      		key2: value2
      java
      @Data
      @Component
      public class AppConfig {
          @Value("#{${app.credentials}}")
          private Map<String, String> credentials;
      }

@PropertySource

  • 注解说明:用于指定资源文件读取的位置,可以读取properties文件,也能xml文件,通过YAML解析器并且配合自定义PropertySourceFactory能够实现读取yml文件,只能作用到类上

  • 属性说明

    java
    //只能作用在类上
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Repeatable(PropertySources.class)
    public @interface PropertySource {
    
    	/**
    	 * 指定资源名称,如果为空,就根据基础资源的描述生成。
    	 */
    	String name() default "";
    
    	/**
    	 * 指定资源路径。
    	 * 可以是 classpath:/xxx/xxxx
    	 * 也可以是 file:/xxx/xxx/xx
    	 */
    	String[] value();
    
    	/**
    	 * 是否忽略资源不存在的情况,如果不忽略,当资源不存在时就报错。默认不忽略。
    	 * 此属性是spring4.0以后出现的。
    	 */
    	boolean ignoreResourceNotFound() default false;
    
    	/**
    	 * 指定资源文件的编码格式。如果不指定就使用文件默认的。
    	 * 此属性是spring4.3以后出现的。
    	 */
    	String encoding() default "";
    
    	/**
    	 * 指定资源工厂,如果不指定,就使用默认的资源工厂。
    	 */
    	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
    
    }
  • 注意:该注解在spring4.3之前与spring4.3及之后使用的方式不一样

  • 注解使用

    • spring4.3之前读取properties文件

      java
      @Configuration
      @ComponentScan(basePackages = "propertysourcedemo")
      @PropertySource(value = "classpath:daoconfig/datasource-config.properties")
      public class SpringConfig {
      
          // 通过SPEL表达式注入文件中的属性
          @Value("${druid.driverClassName}")
          private String driverClassName;
      
          @Value("${druid.url}")
          private String url;
      
          @Value("${druid.username}")
          private String username;
      
          @Value("${druid.password}")
          private String password;
      
          // 创建资源文件解析器,spring4.3之前必须要的,不要就无法解析。
          @Bean
          public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
              return new PropertySourcesPlaceholderConfigurer();
          }
      
          //注册Druid数据源连接池
          @Bean
          public DruidDataSource druidDataSource(){
              System.out.println("driverClassName====> " + driverClassName);
              System.out.println("url====> " + url);
              System.out.println("username====> " + username);
              System.out.println("username====> " + username);
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName(driverClassName);
              druidDataSource.setUrl(url);
              druidDataSource.setUsername(username);
              druidDataSource.setPassword(password);
              return druidDataSource;
          }
      }
    • spring4.3之后读取properties文件

      java
      @Configuration
      @ComponentScan(basePackages = "propertysourcedemo")
      // 这次使用file协议的url路径来解析
      @PropertySource(value = "file:///D:/spring-high-level-study/src/main/resources/daoconfig/datasource-config.properties")
      public class SpringConfig {
      
          // 通过SPEL表达式注入文件中的属性
          @Value("${druid.driverClassName}")
          private String driverClassName;
      
          @Value("${druid.url}")
          private String url;
      
          @Value("${druid.username}")
          private String username;
      
          @Value("${druid.password}")
          private String password;
      
          // 注册Druid数据源连接池
          @Bean
          public DruidDataSource druidDataSource(){
              System.out.println("driverClassName====> " + driverClassName);
              System.out.println("url====> " + url);
              System.out.println("username====> " + username);
              System.out.println("username====> " + username);
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName(driverClassName);
              druidDataSource.setUrl(url);
              druidDataSource.setUsername(username);
              druidDataSource.setPassword(password);
              return druidDataSource;
          }
      }
    • 解析yml文件的时候,@PropertySource默认实现的PropertySourceFactory是解析不了的,如果要解析,就要自定义实现

      • 引入第三方jar包

        xml
         <dependency>
             <groupId>org.yaml</groupId>
             <artifactId>snakeyaml</artifactId>
             <version>1.23</version>
        </dependency>
      • 自定义yml解析工厂

        java
        public class YAMLPropertySourceFactory implements PropertySourceFactory {
            @Override
            public org.springframework.core.env.PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
                //创建一个YAML解析工厂。
                YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
                //设置资源。
                factory.setResources(encodedResource.getResource());
        
                //获取解析后的Properties对象
                Properties properties = factory.getObject();
                //返回。此时不能像默认工厂那样返回ResourcePropertySource对象 ,要返回他的父类PropertiesPropertySource对象。
                return name != null ? new PropertiesPropertySource(name, properties) :
                        new PropertiesPropertySource(encodedResource.getResource().getFilename(),properties);
            }
        }
    • spring4.3之前读取yml文件

      java
      @Configuration
      @ComponentScan(basePackages = "propertysourcedemo")
      // 指定PropertySourceFactory读取yml文件
      @PropertySource(value = "classpath:daoconfig/datasource-config.yml", factory = YAMLPropertySourceFactory.class)
      public class SpringConfig {
      
          // 通过SPEL表达式注入文件中的属性
          @Value("${druid.driverClassName}")
          private String driverClassName;
      
          @Value("${druid.url}")
          private String url;
      
          @Value("${druid.username}")
          private String username;
      
          @Value("${druid.password}")
          private String password;
      
          // 创建资源文件解析器,spring4.3之前必须要的,不要就无法解析。
          @Bean
          public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
              return new PropertySourcesPlaceholderConfigurer();
          }
      
          //注册Druid数据源连接池
          @Bean
          public DruidDataSource druidDataSource(){
              System.out.println("driverClassName====> " + driverClassName);
              System.out.println("url====> " + url);
              System.out.println("username====> " + username);
              System.out.println("username====> " + username);
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName(driverClassName);
              druidDataSource.setUrl(url);
              druidDataSource.setUsername(username);
              druidDataSource.setPassword(password);
              return druidDataSource;
          }
      }
    • spring4.3之后读取yml文件和spring4.3之前读取yml文件的区别就是去掉了资源文件解析器,这里就不赘述代码了

参考文档

MIT版权,未经许可禁止任何形式的转载