目录
说明
一、引用值的注解
1. @Value
2. @ConfigurationProperties
3. @AliasFor
二、使用功能的注解
1. @Slf4j
2. @EnableScheduling
3. @Scheduled
4. @EnableAutoConfiguration
5. @RestController
6. @PostMapping、@GetMapping、@DeleteMapping、@PutMapping
7. @EnableConfigurationProperties
三、引入类的注解
1. @Import
2. @ServletComponentScan
3. @MapperScan
四、处理逻辑的注解
1. @Conditional
五、修饰的注解
1. @Retention
六、便捷封装的注解
1. @Repeatable
七、其他
1. @SpringBootConfiguration
本文按自己理解,将注解进行归类。仅代表个人观点,非大众观点。自行食用。
引用值:引用别处的值,用在其他地方。使用功能:开启某项功能,比如日志、任务调度等。引入类:引入某些类。处理逻辑:需要系统去判断的。修饰:作修饰说明的。便捷封装(1)应用场景
注解在字段。
读取配置文件中单个字段的值。
(2)代码举例
spring: application: name: SpringBootDemo上边yml配置文件,下边控制器方法获取打印
@Value("${spring.application.name}") private String applicationName; /** * 登录 * <p> * * @param employeeDomain 员工实体类 * @return org.springframework.web.servlet.ModelAndView * @author ZRH * @date 2020-08-18 * @version 1.0.0 */ @PostMapping(value = "/login") public RestResultDTO<Object> login(@RequestBody EmployeeDomain employeeDomain, HttpSession session) { log.info(applicationName); return handleMessage(employeeService.checkAccount(employeeDomain, session) , session.getAttribute(CommonConstant.LOGIN_EMPLOYEE)); }(1)应用场景
注解在指定类,引用类用@Autowired。
读取配置文件中多个字段的值,映射到指定类(用prefix指定),记得setter注入,得写个setter方法。或者像我用@Data。
(2)代码举例
dog: name: 旺财 host: "胖\t虎" friend: '二\t狗' /** * 将配置文件中配置的每一个属性的值,映射到这个组件中 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定; * prefix = "dog":配置文件中哪个下面的所有属性进行一一映射 * * 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能; * @author ZRH * @version 1.0.0 * @date 2020/9/1 */ @Data @Component @ConfigurationProperties(prefix = "dog") public class DogDomain { private String name; private String host; private String friend; } /** * 测试 * * @author ZRH * @version 1.0.0 * @date 2020/8/31 */ @RestController @RequestMapping public class MyController { @Autowired private DogDomain dogDomain; @GetMapping("/index") public ModelAndView goIndex(ModelAndView modelAndView) { System.out.println(dogDomain); modelAndView.setViewName("index"); return modelAndView; } }(1)应用场景
注解在注解属性上
作用:
定义一个注解的属性,可以被另一个注解接收到相同注解内两属性通用,简略用法:
定义一个注解中的两个属性互为别名也可以不同注解的属性声明别名(2)代码举例
像@ComponentScan
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; // ... }互为别名,value和basePackages作用就一样了。这样的话,如果我们只需要指定basePackages,就可以使用value属性代替(还可以省略value):@ComponentScan("com.winrh")。而多个属性时,可以定义@ComponentScan(basePackages = "com.winrh", lazyInit = true),这样条理更加清晰。
像@SpringBootApplication
public @interface SpringBootApplication { @AliasFor( annotation = EnableAutoConfiguration.class ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "nameGenerator" ) Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }就用了@ComponentScan、@EnableAutoConfiguration、@Configuration的属性。再通过AnnotatedElementUtils.getMergedAnnotation方法,就可以将别名的值赋给另一个的注解的属性。这样,即使我定义的是SpringBootApplication的属性,但是@ComponentScan、@EnableAutoConfiguration、@Configuration都收到了。
(1)应用场景
注解在类上。
使用日志,这里日志抽象层用slf4j,具体的日志看你配置。配合lombok,可以省去自己创建Logger对象,直接调用log对象即可。
(2)代码举例
/** * 登录控制器类 * * @author ZRH * @version 1.0.0 * @date 2020/8/18 */ @Slf4j @RestController public class LoginController extends BaseController { @Autowired EmployeeService employeeService; @Value("${spring.application.name}") private String applicationName; /** * 登录 * <p> * * @param employeeDomain 员工实体类 * @return org.springframework.web.servlet.ModelAndView * @author ZRH * @date 2020-08-18 * @version 1.0.0 */ @PostMapping(value = "/login") public RestResultDTO<Object> login(@RequestBody EmployeeDomain employeeDomain, HttpSession session) { log.info(applicationName); return handleMessage(employeeService.checkAccount(employeeDomain, session) , session.getAttribute(CommonConstant.LOGIN_EMPLOYEE)); } }
(1)应用场景
注解在入口类上。
意为启用任务调度、定时任务。启用后,再在自己定义的定时任务类上注解@Component,并在其方法上注解@Scheduled,到时候生成上下文之前会扫描所有@Scheduled注解(详情见自动配置章节)。
(2)代码举例
@SpringBootApplication @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }上边为入口类,下边为自定义的定时任务类
/** * 定时备份 * @author ZRH * @version 1.0.0 * @date 2020/9/4 */ @Slf4j @Component public class DumpTask { @Scheduled(cron = "*/10 * * * * ?") public void cron(){ log.info("每隔10秒执行一次"); } @Scheduled(fixedDelay = 4000) public void fixedDelay(){ log.info("循环调用fixedDelay,延迟为4s"); } }见@EnableScheduling
(1)应用场景
这在Spring Boot入口类的@SpringBootApplication注解中见到。目的是开启Spring Boot自动扫描配置类的功能,让其尝试根据你添加的jar依赖自动配置你的Spring应用。
例如,如果你的classpath下存在HSQLDB,并且你没有手动配置任何数据库连接beans,那么其将自动配置一个内存型(in-memory)数据库。
(2)代码举例
看看注解:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }AutoConfigurationImportSelector是DeferredImportSelector实现类。
进入AutoConfigurationImportSelector#selectImports方法(可结合下文的@Import理解,导入的流程可参考我的第二章):
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }根据annotationMetadata进行模式的选择,是不导入,还是导入。导入则跳到AutoConfigurationImportSelector#getAutoConfigurationEntry方法:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry (AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this.getAttributes(annotationMetadata); List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.getConfigurationClassFilter().filter(configurations); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }里面有个AutoConfigurationImportSelector#getCandidateConfigurations:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata , AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader .loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass() , this.getBeanClassLoader()); // ... return configurations; }很明显,调用了SpringFactoriesLoader#loadFactoryNames去你项目的META-INF/spring.properties找配置文件,再去Spring Boot的jar包里找 @EnableAutoConfiguration对应的ConfigurationClass配置文件(详见我写的第二章)。
找到之后,每一个自动配置类结合对应的xxxProperties.java读取配置文件进行自动配置功能。如下面这些:
咱找一个,AOP的:
@Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) public class AopAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Advice.class) static class AspectJAutoProxyingConfiguration { @Configuration(proxyBeanMethods = false) @EnableAspectJAutoProxy(proxyTargetClass = false) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false) static class JdkDynamicAutoProxyConfiguration { } @Configuration(proxyBeanMethods = false) @EnableAspectJAutoProxy(proxyTargetClass = true) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) static class CglibAutoProxyConfiguration { } } @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.aspectj.weaver.Advice") @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) static class ClassProxyingConfiguration { ClassProxyingConfiguration(BeanFactory beanFactory) { if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } } } }看到这个@ConditionalOnProperty了吗,prefix+name就是你需要启动aop,就在全局配置文件写上的属性,比如这里的spring.aop.auto=true,你设置了true,那它就解析这个配置类。
继续找个WebMvcConfiguration
建议看我的第十五章自定义starter,就容易理解了。
自定义spring.properties里就可以写:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xxx.MyConfiguration然后进行配置类去重:
configurations = this.removeDuplicates(configurations);再排除spring.autoconfigure.exclude配置的ConfigurationClass:
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions);最后过滤掉一些配置类:
configurations = this.getConfigurationClassFilter().filter(configurations);使用spring.factories中配置的AutoConfigurationImportFilter的实现类(OnBeanCondition,OnClassCondition,OnWebApplicationCondition)过滤部分ConfigurationClass,这里处理@ConditionalOnBean,@ConditionalOnClass,@ConditionalOnMissingClass等注解。
OnClassCondition可以判断当前Java环境中存在或者不存在某一个class,SpringBoot 自动配置功能可以实现当我们引入某个框架jar后,自动配置完成该框架的配置,正是通过该条件判断类实现。
(1)应用场景
注解在类上
Spring4之后新加入的注解,用于标注控制层组件,返回JSON视图再也不用老是在方法注解@ResponseBody,方便REST开发。
/** * 文件上传控制器 * @author ZRH * @version 1.0.0 * @date 2020/9/3 */ @Slf4j @RestController public class UploadController extends BaseController{ @Autowired private UploadService uploadService; @PostMapping("/upload") public RestResultDTO<Long> upload(HttpSession session, MultipartFile multipartFile){ return handleMessage(uploadService.uploadFile(session.getServletContext().getRealPath("/") + "upload/", multipartFile)); } }(2)代码举例
瞄一下@RestController,自带@ResponseBody,妙啊
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { @AliasFor( annotation = Controller.class ) String value() default ""; }(1)应用场景
REST风格开发,代替繁琐的设置method属性
(2)代码举例
/** * 文件上传控制器 * @author ZRH * @version 1.0.0 * @date 2020/9/3 */ @Slf4j @RestController public class UploadController extends BaseController{ @Autowired private UploadService uploadService; @PostMapping("/upload") public RestResultDTO<Long> upload(HttpSession session, MultipartFile multipartFile){ return handleMessage(uploadService.uploadFile(session.getServletContext().getRealPath("/") + "upload/", multipartFile)); } }瞄一下@PostMapping
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @RequestMapping( method = {RequestMethod.POST} ) public @interface PostMapping { @AliasFor( annotation = RequestMapping.class ) String name() default ""; @AliasFor( annotation = RequestMapping.class ) String[] value() default {}; @AliasFor( annotation = RequestMapping.class ) String[] path() default {}; @AliasFor( annotation = RequestMapping.class ) String[] params() default {}; @AliasFor( annotation = RequestMapping.class ) String[] headers() default {}; @AliasFor( annotation = RequestMapping.class ) String[] consumes() default {}; @AliasFor( annotation = RequestMapping.class ) String[] produces() default {}; }已经写好method属性了
(1)应用场景
在自定义starter时,会编写自动配置类,这时就会用@EnableConfigurationProperties来启动参数里的配置类(例子可看第十五章)。
(2)代码举例
比如我的自动配置类:
@Configuration @ConditionalOnWebApplication @ConditionalOnClass(HelloService.class) @EnableConfigurationProperties(HelloProperties.class) public class HelloWorldAutoConfiguration { @Autowired private HelloProperties helloProperties; @Bean @ConditionalOnMissingBean(HelloService.class) @ConditionalOnProperty(prefix = "helloworld", value = "enabled", havingValue = "true") HelloService helloService() { return new HelloService(helloProperties); } }看下注解内容:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EnableConfigurationPropertiesRegistrar.class) public @interface EnableConfigurationProperties { /** * The bean name of the configuration properties validator. * @since 2.2.0 */ String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator"; /** * Convenient way to quickly register * {@link ConfigurationProperties @ConfigurationProperties} annotated beans with * Spring. Standard Spring Beans will also be scanned regardless of this value. * @return {@code @ConfigurationProperties} annotated beans to register */ Class<?>[] value() default {}; }@Import(EnableConfigurationPropertiesRegistrar.class),这就是在初始化上下文时,ConfigurationClassParser解析配置类,会解析@Import。最后loadBeanDefinitions注册beanDefinition。
对象的创建必须通过构造方法创建,分无参和有参构造器。基于spring体系的创建Bean注解有:@Configuration里的@Bean、@Component、@Controller、@Service、@Repository等。
如果想自定义一些初始设置比较复杂的bean时,可以在类上用@Configuration注解,然后类内部在返回具体bean的方法上使用@Bean注解。那么,要让容器找到这个配置类,并让容器进行管理,方法有这么几种:
① 在@ComponentScan指定的配置类所在包
② 用@Import来导入一个或多个类(会被spring容器管理),或者配置类(配置类里的@Bean标记的类也会被spring容器管理)
直接指定要引入的配置类,如: @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Import({SchedulingConfiguration.class}) @Documented public @interface EnableScheduling { } 或者引入实现了ImportSelector接口的类如果并不确定引入哪个配置类,需要根据@Import注解所标识的类或者另一个注解(通常是注解)里的定义信息选择配置类的话,用这种方式。例如@EnableAutoConfiguration
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }是通过AutoConfigurationImportSelector类,根据注解@EnableTransactionManagement所指定的AnnotationMetadata来选择使用哪个配置类的。
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } } 或者是指定实现了ImportBeanDefinitionRegistrar接口的类如果是第三方包,而且又不是确定的类,并且这些类并不是spring专用,因而不想用spring的注解进行侵入式标识时,那么,如何找到这些类放到spring的容器呢?这就用到了注解@Import引入ImportBeanDefinitionRegistrar子类的方式,最典型的应用就是mybatis,使用工具自动生成了一批mapper和entity。而如何把这些普通的类放入容器,就是通过注解。典型代表@MapperScan:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({MapperScannerRegistrar.class}) @Repeatable(MapperScans.class) public @interface MapperScan { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends Annotation> annotationClass() default Annotation.class; Class<?> markerInterface() default Class.class; String sqlSessionTemplateRef() default ""; String sqlSessionFactoryRef() default ""; Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class; String lazyInitialization() default ""; }这个注解用@Import引入了MapperScannerRegistrar类,这个类里会取得注解@MapperScan作设置的package,然后扫描这个package下所有的类,并放入容器中。
用来导入一个或多个类(会被spring容器管理),或者配置类(配置类里的@Bean标记的类也会被spring容器管理)。详见上方介绍。
(1)应用场景
注解在入口类上,并指定servlet所在包即可扫描。
(2)代码举例
例如,我写了个过滤器:
然后指定包:
@SpringBootApplication @MapperScan("com.winrh.mapper") @ServletComponentScan("com.winrh.filter") @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }看看@ServletComonentScan
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ServletComponentScanRegistrar.class}) public @interface ServletComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }原来是用@Import引入了ServletComponetScanRegistrar,可参考上文提及的@Import的三种方式。
在上文@Import的三种方式中提及了。扫描MyBatis的接口及映射文件。
使用场景:
@SpringBootApplication @MapperScan("com.winrh.mapper") @ServletComponentScan("com.winrh.filter") @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
(1)应用场景
可注解在配置类的方法上,也可注解在配置类上。
当判断正确,则选用该方法(注解类上则是选用该类的所有方法)
(2)代码举例
先定义两个实现了Condition接口的条件类:
/** * 测试@Conditional * @author ZRH * @version 1.0.0 * @date 2020/9/8 */ public class NeverFalse implements Condition { @Override public boolean matches(ConditionContext conditionContext , AnnotatedTypeMetadata annotatedTypeMetadata) { return true; } } /** * 测试@Conditional * @author ZRH * @version 1.0.0 * @date 2020/9/8 */ public class NeverTrue implements Condition { @Override public boolean matches(ConditionContext conditionContext , AnnotatedTypeMetadata annotatedTypeMetadata) { return false; } }接着定义一个配置类:
/** * 测试@Conditional * @author ZRH * @version 1.0.0 * @date 2020/9/8 */ @Configuration public class TestConfig { @Conditional(NeverTrue.class) @Bean public CategoryDomain getDrinkCategory() { return new CategoryDomain(1, "酒水"); } @Conditional(NeverFalse.class) @Bean public CategoryDomain getVegetableCategory() { return new CategoryDomain(2, "鲜蔬"); } }这样,鲜蔬通过,酒水不通过。测试下:
@SpringBootTest class DemoApplicationTests { @Test void contextLoads() { ApplicationContext ctx = new AnnotationConfigApplicationContext(TestConfig.class); CategoryDomain categoryDomain = ctx.getBean(CategoryDomain.class); System.out.println(categoryDomain); } }结果一致。
那么原理是什么呢?
我们可以从refreshContext方法进入, 一直到ConfigurationClassPostProcessor#processConfigBeanDefinitions再到ConfigurationClassParser#processConfigurationClass,对配置类进行解析,此时会先检查该配置类是否有@Conditional注解,有就跳过:
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata() , ConfigurationPhase.PARSE_CONFIGURATION)) { ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses .get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } return; } this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass, filter); do { sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter); } while(sourceClass != null); this.configurationClasses.put(configClass, configClass); } }就在ConditionEvaluator#shouldSkip
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { if (metadata != null && metadata.isAnnotated(Conditional.class.getName())) { if (phase == null) { return metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata)metadata) ? this.shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION) : this.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } else { List<Condition> conditions = new ArrayList(); Iterator var4 = this.getConditionClasses(metadata).iterator(); while(var4.hasNext()) { String[] conditionClasses = (String[])var4.next(); String[] var6 = conditionClasses; int var7 = conditionClasses.length; for(int var8 = 0; var8 < var7; ++var8) { String conditionClass = var6[var8]; Condition condition = this.getCondition(conditionClass , this.context.getClassLoader()); conditions.add(condition); } } AnnotationAwareOrderComparator.sort(conditions); var4 = conditions.iterator(); Condition condition; ConfigurationPhase requiredPhase; do { do { if (!var4.hasNext()) { return false; } condition = (Condition)var4.next(); requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition)condition).getConfigurationPhase(); } } while(requiredPhase != null && requiredPhase != phase); } while(condition.matches(this.context, metadata)); return true; } } else { return false; } }
(1)应用场景
常看到注解里有@Retention,故来说说。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }有个属性,为RetentionPolicy类型,看下源码:
public enum RetentionPolicy { // 在编译的时候,注解被编译器抛弃 SOURCE, // 默认。注解被编译器保存在class文件,但JVM加载class文件时注解不保留 CLASS, // 注解保存在class文件,且JVM加载class文件时仍保留 RUNTIME }(2)代码举例
像lombok的@Data是SOURCE级别 package lombok; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface Data { String staticConstructor() default ""; } 像Spring的@Autowired是RUNTIME级别 package org.springframework.beans.factory.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER , ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { boolean required() default true; }(1)应用场景
注解在注解上
JDK8新增注解。将多个注解替换为一个数组注解
(2)代码举例
可以看看@ComponentScan注解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { // ... }再看ComponentScans.class
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface ComponentScans { ComponentScan[] value(); }ComponentScans中value属性是一个ComponentScan数组,这里@Repeatable表示当配置了多个@ComponentScan时,@ComponentScan可以被@ComponentScans代替(JDK8中支持重复的注解)
(1)应用场景
应用在@SpringBootApplication里,本身又包含@Configuration注解,表明可作配置类,可以写@Bean来玩。
(2)代码举例
@SpringBootApplication @MapperScan("com.winrh.mapper") @ServletComponentScan("com.winrh.filter") @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }入口类注解了@SpringBootApplication,而后者又包含@SpringBootConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { // ... }再看
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; }有个@Configuration注解,以及与Configuration的proxyBeanMethods互为别名的proxyBeanMethods属性。
这样一来,入口类就可以作配置类,可以这么玩:
@SpringBootApplication @MapperScan("com.winrh.mapper") @ServletComponentScan("com.winrh.filter") @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean public CategoryDomain getCategory(){ return new CategoryDomain(3, "酒水"); } }控制器类:
@Autowired CategoryDomain categoryDomain; /** * 登录 * <p> * * @param employeeDomain 员工实体类 * @return org.springframework.web.servlet.ModelAndView * @author ZRH * @date 2020-08-18 * @version 1.0.0 */ @PostMapping(value = "/login") public RestResultDTO<Object> login(@RequestBody EmployeeDomain employeeDomain, HttpSession session) { log.info(categoryDomain.toString()); return handleMessage(employeeService.checkAccount(employeeDomain, session) , session.getAttribute(CommonConstant.LOGIN_EMPLOYEE)); }效果就有了
待更新...
