[spring注解]Spring相关注解(四)

前言

接上文,接着学习Spring的注解。本篇博客所学习注解基本都是spring-context对应jar包下的注解,Spring版本:4.3.14,包含注解:ComponentControllerServiceRepositorybeanComponentScanComponentScan.FilterComponentScansConfigurationDependsOnDescriptionImportLazyPrimaryScopePropertySourceConditionalProfileAsyncScheduled 。该jar包下其他注解平时使用不多,如果哪天用到了,再添加进来。

一、Spring-Context相关注解

1. Component注解

  该注解是Spring的元注解,意思是它可以用于标注其他注解,被它标注的注解和它具有相同或者相似的功能。Spring用它来标注为Spring组件的bean,用它标注的类,默认情况下在Spring进行扫描的时候,会自动注册为Spring的Bean。而Spring利用它定义了一些我们常用的注解如:@Controller,@Service,@Repository等。
  我们可以简单看下Controller注解的定义:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
}

  当然,我们也可以仿照Controller注解,自定义我们的注解,然后标注到某个类上,这样Spring在进行扫描的时候就会自动将该类注册为Bean。该注解一般泛指各种通用的组件,比如说我们的类既不属于@Controller,@Service,@Repository的时候,就可以使用该注解。

参数只有一个value,表示该bean的名称;

2. Controller注解

  这个注解应该是我们使用最多的注解了,在Spring MVC中作为控制器Controller,负责处理DispatcherServlet 分发的请求,接受到请求后解析处理数据,然后再返回给浏览器。而在Spring MVC中,定义Controller也是特别简单,只需要一个@Controller注解就可以标记一个类是Controller,然后使用RequestMapping等注解来定义URL和Controller之间的映射。
  该注解标记于类上,但也只是定义了一个控制器类,而使用@RequestMapping注解的方法才是真正处理请求的处理器,然后我们配置扫描路径后,就可以访问这些具体的控制器方法了。

该注解只有一个参数:

value,Spring扫描Controller后,会生成对应的bean,该参数用于定义bean的名称,如果不指定value,则Controller默认的bean名称是该类的类名,其中首字母小写。

3. Service注解

基于@Component注解,用于标注某个类为服务组件。也就是说用它标注的类表示Service层的实现,同样参数只有value,用于表示该bean的名称,也就是在Spring环境中的id。不过需要简单注意下:

在文章 spring-applicationContext.xml-context标签 的标签的 name-generator 这里,我们已经了解到,由于Spring默认的bean的命名策咯的实现类是AnnotationBeanNameGenerator,根据源码也就是说,默认情况下如果我们不设置value属性,那么默认的bean名称就是类名,第一个字母是小写;当然还有一个特殊的处理,当类的名字是以两个或两个以上的大写字母开头的话,那么bean的名称就和类名称是一样的,可以参考下源码:

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    if (definition instanceof AnnotatedBeanDefinition) {
        String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
        if (StringUtils.hasText(beanName)) {
            // Explicit bean name found.
            return beanName;
        }
    }
    // 处理默认的bean名称
    return buildDefaultBeanName(definition, registry);
}

然后,单步调试 buildDefaultBeanName 方法,一直到Introspector.decapitalize (shortClassName):

/**
 * Utility method to take a string and convert it to normal Java variable
 * name capitalization.  This normally means converting the first
 * character from upper case to lower case, but in the (unusual) special
 * case when there is more than one character and both the first and
 * second characters are upper case, we leave it alone.
 * 

* Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays * as "URL". * */ public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } // 如果类的前两个字母都是大写,直接返回类名 if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } // 将类的第一个字母转成小写 char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); }

然后看一下注释即对应的实现,就可以看到相应的实现了。至于如何修改bean的命名策咯,前文已经了解过,这里就不多讲了。

4. Repository 注解

Repository注解和@Controller,@Service这两个功能是类似的,@Controller作为Web层组件,@Service作为服务层组件,而@Repository是作为数据访问层的组件,也就是,一般情况下,通过该bean对数据库进行各种操作。其余和Service功能并无很大区别,不多说。

5. bean注解

  这里我们来学习bean相关的注解,由于前文已经学习了XML中bean的配置,所以有可能有重复的,如果有重复的,这里就一笔带过。首先来看一下bean注解。
  @Bean注解是一个方法级别的注解,和XML中配置标签功能是一样的。通常情况下是与@Configuration注解一起使用,并且通过和@Scope,@Lazy等注解一起来控制Bean的一些配置。

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

等价于在Spring XML中的配置:


    

我们来简单看下@Bean的各个参数:

  1. valuename,bean的名称;
  2. autowire,注入方式,可以是ByName,byType,也可也选择NO,默认是Autowire.NO;
  3. initMethoddestroyMethod,实例化bean的时候的初始化方法,及销毁前执行的方法;

官网文档:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/beans.html#beans-java-bean-annotation

6. ComponentScan注解

该注解对应于XML配置中的 ,用来扫描相应的注解并自动注入bean。该注解于XML配置有些许不同:

  1. 该注解没有 annotation-config对应的属性,因为我们程序几乎所有的情况,都会扫描@Autowired等这一类所对应的注解,所以也就相当于有这个参数,但这个参数一直是true;
  2. 此外,该注解多了basePackageClasses这个数组参数,表示指定特定的要扫描的类,注入的时候也只注入这些类;
  3. 还有一个参数:lazyInit,表示扫描的时候根据bean是否配置了lazy来决定是否延迟初始化该bean。默认是false,也就是说无论bean是否配置了lazy属性,该bean被扫描到后都会立刻初始化,设置为true之后,如果bean配置了lazy属性,那么会根据bean的lazy属性来选择是否延迟初始化;
7. ComponentScan.Filter注解

  ComponentScan注解的内部的一个注解,用在ComponentScanincludeFiltersexcludeFilters,而这里的用法和配置文件中的context:exclude-filtercontext:include-filter是一致的,目的就是为了扫描的时候过滤用的。至于Filter中的参数和我们前面学习的XML中的参数差不多,不多说了。

@Configuration 
@EnableSpringConfigured
@ComponentScan(basePackages = {"com.example"}, excludeFilters={
  @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=Foo.class)})
public class MySpringConfiguration {}

具体使用可参考:Spring - @ComponentScan custom filtering

8. ComponentScans注解

  这个注解是Spring4.3之后引入的,目的就是配置多个扫描器(当然也可以在一个类上配置多个ComponentScan注解)。学习过XML的配置我们知道,在XML中我们是可以配置多个标签的,同样,使用该注解可以让我们在程序里配置多个ComponentScan注解。该注解参数只有一个,就是数组类型的ComponentScan

@Configuration
@ComponentScans({@ComponentScan(value = {"controller", "global","controller2"}),
        @ComponentScan(value = {"service"})})
@EnableWebMvc
@EnableSwagger2
public class SpringConfig extends WebMvcConfigurerAdapter {
}
9. Configuration注解

  Spring的@Configuration注解修饰的类其实就相当于XML中的一个配置文件,可以通过该注解声明一个配置类来减少繁琐的XML配置,然后使用@Bean注解标记相应的方法。在这里@Configuration就相当于XML中的beans标签,而@Bean就相当于bean标签。
举个简单的例子:

@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        // instantiate, configure and return bean ...
    }
}

  由于Component注解是Configuration的元注解,所以也会被@ComponentScan所扫描到。而我们在使用注解调用的时候,是通过AnnotationConfigApplicationContext或者web相关的AnnotationConfigWebApplicationContext

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
MyBean myBean = ctx.getBean(MyBean.class);
// use myBean ...

一般,该注解有以下几种用法:

  1. 会配合ComponentScan注解进行扫描,并且配合EnableWebMvc注解来开启MVC:
@Configuration
@ComponentScan("com.acme.app.services")
@EnableWebMvc
public class AppConfig {
    // various @Bean definitions ...
}
  1. 配合PropertySource注解读取properties文件中的值:
@Configuration
@PropertySource("classpath:/com/acme/app.properties")
public class AppConfig {
    @Inject Environment env;
    @Bean
    public MyBean myBean() {
        return new MyBean(env.getProperty("bean.name"));
    }
}
  1. 配合@Value注解获取properties文件中的值:
@Configuration
@PropertySource("classpath:/com/acme/app.properties")
public class AppConfig {

    @Value("${bean.name}") 
    String beanName;

    @Bean
    public MyBean myBean() {
        return new MyBean(beanName);
    }
}
  1. 配合@Import注解获取导入其他的配置类:
@Configuration
public class DatabaseConfig {

    @Bean
    public DataSource dataSource() {
        // instantiate, configure and return DataSource
    }
}

@Configuration
@Import(DatabaseConfig.class)
public class AppConfig {

    private final DatabaseConfig dataConfig;

    public AppConfig(DatabaseConfig dataConfig) {
        this.dataConfig = dataConfig;
    }

    @Bean
    public MyBean myBean() {
        // reference the dataSource() bean method
        return new MyBean(dataConfig.dataSource());
    }
}
  1. 配合@Profile注解获取当前环境信息(开发,测试,生产):
 @Profile("development")
 @Configuration
 public class EmbeddedDatabaseConfig {

     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return embedded DataSource
     }
 }

 @Profile("production")
 @Configuration
 public class ProductionDatabaseConfig {

     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return production DataSource
     }
 }

同样,@Profile注解也可也位于Bean上:

@Configuration
public class ProfileDatabaseConfig {

    @Bean("dataSource")
    @Profile("development")
    public DataSource embeddedDatabase() { ... }

    @Bean("dataSource")
    @Profile("production")
    public DataSource productionDatabase() { ... }
}
  1. 配合@ImportResource注解导入对应的配置文件,如XML等:
@Configuration
@ImportResource("classpath:/com/acme/database-config.xml")
public class AppConfig {

    @Inject DataSource dataSource; // from XML

    @Bean
    public MyBean myBean() {
        // inject the XML-defined dataSource bean
        return new MyBean(this.dataSource);
    }
}
  1. 使用@Configuration嵌套注解:
@Configuration
public class AppConfig {

    @Inject DataSource dataSource;

    @Bean
    public MyBean myBean() {
        return new MyBean(dataSource);
    }

    @Configuration
    static class DatabaseConfig {
        @Bean
        DataSource dataSource() {
            return new EmbeddedDatabaseBuilder().build();
        }
    }
}
  1. 单元测试,引入@Configuration修饰的类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={AppConfig.class, DatabaseConfig.class})
public class MyTests {

    @Autowired MyBean myBean;
    @Autowired DataSource dataSource;

    @Test
    public void test() {
        // assertions against myBean ...
    }
}

关于Configuration注解,需要注意下:

    1. 该注解修饰的必须是类,不能是接口;
    1. 配置类必须不能是final类型的;
    1. 配置类不能是匿名类;
    1. 嵌套的配置类必须是static的;

更多有关Configuration的内容,可以参考官方API:https://docs.spring.io/spring/docs/current/javadoc-api/
而有关Spring @Configuration 和 @Component 区别可以参考:
Spring @Configuration 和 @Component 区别

10. DependsOn注解

  DependsOn注解和bean的 depends-on方法类似,用于一个beanA初始化之前必须先初始化另一个beanB,由于前面已经仔细分析过,这里就不多说,具体可参考:applicationContext.xml详解一beans标签

11. Description注解

  同样,和bean标签中的description属性类似,用于对bean添加描述信息,同样是直接或间接使用了@Component或者@Bean注解的类或方法上。

12. Import注解

  对应于beans标签的import子标签,用于模块化过程中引入其他的配置文件,而在对应的注解中,则是为了引入@Configuration注解所修饰的配置类。当然,不但但可以导入配置类,也可以导入普通的java类,并将其声明成一个bean。而如果需要导入XML或者其他非@Configuration所定义的资源,可以通过注解@ImportResource 来实现。

参数value 数组,声明用于导入的具体配置类的类型;

可以看个简单的例子:

public class BeanService {
    public void test() {
        System.out.println("test:BeanService");
    }
}

@Configuration
@Import(BeanService.class)
public class BeanConfig {
}

public static void main(String[] args) {
    ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("/resource/applicationContext.xml");
    BeanService beanService = ac.getBean(BeanService.class);
    beanService.test();
    ac.close();
}
13. Lazy注解

  该注解表示该bean是否延迟加载,和bean标签的lazy-init属性功能是相同的。同样,可以在任何直接或间接的使用了@Component或者@Bean注解的类或方法上使用该注解。如果该注解没有用于@Component或@Bean定义中,那么就会立即初始化,而不会延迟。该注解只有一个参数value,默认是true,也就是延迟初始化。

如果该注解用于@Configuration修饰的配置类上,那么@Configuration中的所有的@Bean方法都会被延迟初始化。而如果@Configuration中的bean上已经配置了Lazy注解,那么以Bean中的优先。

14. Primary注解

  在前面学习Spring bean标签的时候,我们当时已经学习了bean的primary属性,其实它和@Primary注解功能是一样的。是说在使用类型注入的时候,如果存在多个相同类型的bean,那么可以通过使用primary属性或者@Primary注解让该bean成为优先候选者,如果候选者之中只有一个primary修饰的bean,那么这个bean将会是自动注入的bean。

不过需要注意下,就是如果在XML中配置了bean的primary属性,那么该bean对应的@Primary注解将会被忽略掉,并且记得配置扫描路径。

可以在任何直接或间接的使用了@Component或者@Bean注解的类或方法上使用该注解。

15. Scope注解

Scope注解表示bean的作用域,默认情况下我们创建的bean都是单例的。前文已经说过,这里就不多说了,看一下参数:

  • scopeNamevalue,作用域类型的名称;
  • proxyMode,作用域代理相关的配置,对应的值是 ScopedProxyMode对象的值。
16. PropertySource 和 PropertySources注解

该注解是Spring提供的用于读取properties文件的,通过配合Environment 类来实现该功能,该注解用于Configuration 所修饰的类。先来看一个例子,其实上文已经简单介绍过:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

再简单来看一下该注解的参数:

  1. value,要加载的资源位置,数组格式,和XML中支持的功能一样强大,包括classpathfile;但不支持类似的资源通配符:**/*.properties,也就是.properties必须要精确到具体位置;
  2. name,对应属性资源的名称;
  3. encoding,字符编码,比如 UTF-8 格式;
  4. ignoreResourceNotFound,如果找不到对应的属性资源,是否忽略。默认是false,这种情况下,如果找不到的话将会提示异常;
  5. factory,指定一个资源工厂对象PropertySourceFactory;、

一般情况下,该注解还会配合@Value来一起使用,这时候记得配置PropertySourcesPlaceholderConfigurer对象来解析占位符,在XML配置中我们可以通过,在注解中我们可以将该对象注册为bean,配置如下:

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}

而对于@PropertySources注解,则是可以加载多个PropertySource,不多说了。

17. Conditional 注解

  Spring在4.0之后引入的条件注解,就是根据满足某个特定的条件创建一个特定的bean,而我们实现的方式就是实现Condition接口,然后重写matches方法来构造判断条件,比如在Windows下和在Linux下执行不同的操作,具体的例子可以参考:Spring 条件注解(@Conditional)

Configuration  
@ComponentScan(basePackages="com.chenfeng.xiaolyuh.conditional")  
public class ConditionConfig {  
      
    @Bean  
    @Conditional(LinuxCondition.class)// 使用@Conditional注解,符合Linux条件就实例化LinuxListService  
    public ListService linuxListService() {  
        return new LinuxListService();  
    }  
  
    @Bean  
    @Conditional(WindowsCondition.class)// 使用@Conditional注解,符合Windows条件就实例化WindowsListService  
    public ListService windowsListService() {  
        return new WindowsListService();  
    }  
}  

  可以用于类或方法上,当用于@Configuration修饰的类上时,那么该类下所有bean都将受此条件限制。

有关更多可参考:Spring @Conditional Annotation

18. Profile 注解

@Profile是Spring用于获取当前运行环境的注解,比如我们的开发,测试,UAT,生产等,或者还有其他的几套环境,而我们就可以通过该注解来获取当前环境信息。
看一个简单的例子:

@Profile("Development")
@Configuration
public class DevDatabaseConfig implements DatabaseConfig {
 
    @Override
    @Bean
    public DataSource createDataSource() {
        System.out.println("Creating DEV database");
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        /*
         * Set MySQL specific properties for Development Environment
         */
        return dataSource;
    }
 
}

如果要查看更多,可以参考:【译】Spring 4 @Profile注解示例
官网地址:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/beans.html#beans-java

19. Async 注解

  @Async是Spring用于异步调用的注解,该注解用于方法或类上。@Async标注的方法称为异步方法,这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。而@Async如果标注在类上的话,则表示该类的所有方法都将是异步的。

  1. 异步方法的返回值必须是void或者Future,方法必须是public类型的;
  2. 如果要异步的话,首先必须要在配置类上添加@EnableAsync注解来开启异步;
  3. @Async不能与生命周期函数一起使用,必须使用一个单独的初始化Spring bean来调用目标上的@Async注释方法;
  4. 在XML中没有与@Async等价的标签;

如果EnableAsync满足不了我们的需求的话,我们还可以配置EnableAsync的一些属性:

  • annotation - 默认情况下, @EnableAsync 会扫描使用了Spring @Async与EJB 3.1 javax.ejb.Asynchronous的方法;此选项也可以用来扫描其他的,如用户自定义的注解类型;
  • mode - 指定应该使用哪种AOP进行切面处理 - JAVA代理或AspectJ;
  • proxyTargetClass - 指定应该使用哪种代理类 - CGLIB或JDK;此属性只有当mode设置成AdviceMode.PROXY才会产生效果。
  • order - 设置AsyncAnnotationBeanPostProcessor执行顺序(生命周期有关);默认情况下会最后一个执行,所以这样就能顾及到所有已存在的代理。
@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Async
    public void doSomethind() {
        System.out.println("begin async");
    }

    @Async
    Future returnSomething(int i) {
        // this will be executed asynchronously
    }  
}

XML中开启异步的话,是通过如下:




默认情况下,当@Async使用于方法上时,将使用annotation-driven提供的executor执行器,如果要指定相应的executor,可以使用@Async唯一的一个参数value来指定。

@Async("otherExecutor")
void doSomething(String s) {
    // this will be executed asynchronously by "otherExecutor"
}

有关Async和Scheduled相关的官网文档地址:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/scheduling.html#scheduling-annotation-support

20. Scheduled Schedules 注解

  @Scheduled相信我们都用过,用来执行定时任务相关的操作,同样是方法级别的注解。使用该注解修饰的方法必须没有参数,并且返回类型通常是void类型的。该注解使用ScheduledAnnotationBeanPostProcessor来执行,同样,使用该注解前要首先要开启定时,XML中使用,而注解的话则是通过@EnableScheduling来完成。
  另外,该注解可以作为元注解,从而自定义我们的定时任务相关的注解。接下来我们来简单看下它的参数:

  1. cron,这个应该是我们使用最多的参数了,定时任务时间设置的表达式,比如每隔1天执行一次,每隔半小时执行,这个设置网上的介绍实在太多了,用的时候再去查询就可以了;
  2. zone,cron表达式解析的时区,默认情况下,该属性是空字符串(也就是服务器的本地时区),接收的是TimeZone.getTimeZone(String)的id;
  3. fixedDelay,每次执行任务之后间隔多久再次执行任务,以上次任务结束时间和下此任务开始时间之间的毫秒为单位来执行;比如说间隔时间是5秒,任务执行的时间是8秒,那么8秒执行后,下此就是8+5秒的时候在执行,再下此就是8+5+8+5的时候再执行,就是以结束时间为准;
  4. fixedDelayString,和上面fixedDelay一样,只不过参数类型是字符串形式,可以通过外部来定义,如 fixedDelayString= "${job.fixed.string}";;
  5. fixedRate,固定频率的间隔时间,也就是每隔多久就执行,不管任务是否完成;
  6. fixedRateString,和fixedRate一样,只不过参数类型是字符串形式,可以通过外部来定义,如 fixedRateString = "${job.fixed.rate}";
  7. initialDelay,该参数表示第一次执行定时任务之前需要等待多长时间,也就是第一次延时多长时间后执行,单位同样是毫秒;

有关fixedDelayfixedRatecron,再稍微简单说下:

fixedDelay比较简单,就是根据上次任务结束时间计算的,而cron,和fixedDelay则有点不太一样,比如间隔时间是5S,如果执行时间是8S,则下此执行的时间将从10S开始,会跳过一个间隔时间,如果执行时间是10S,则下此执行时间从15S开始;而如果执行时间是3S,那下此执行时间就将从5S开始,每隔5S执行;

至于fixedRate,它的使用和上面两种都不太相同,如果上次任务执行时间超过了间隔时间,那么超出的这部分时间会被计入下一次任务的执行时间中,至于问题的细节,我们可以参考这篇文章:
https://yanbin.blog/understand-spring-schedule-fixedrate-fixeddelay/

在网上看到两幅图描述fixedDelayfixedRate,描述的很形容:

[spring注解]Spring相关注解(四)_第1张图片
fixedDelay和fixedRate.png

图片转自: https://blog.csdn.net/applebomb/article/details/52400154

如果我们没有配置TaskScheduler 和 ScheduledExecutorService 的话,那么Scheduled默认是采用单线程来进行循环执行定时任务的,我们可以通过Thread.currentThread()来查看当前运行的线程,默认定时任务的线程是Executors.defaultThreadFactory() 产生的,线程名称是 "pool-NUMBER-thread-..."。

至于Schedules 注解,则是多个Scheduled组成的数组,就不说了。

参考:Spring 定时任务(Schedule) 和线程

你可能感兴趣的:([spring注解]Spring相关注解(四))