前言
接上文,接着学习Spring的注解。本篇博客所学习注解基本都是spring-context对应jar包下的注解,Spring版本:4.3.14,包含注解:
Component
,Controller
,Service
,Repository
,bean
,ComponentScan
,ComponentScan.Filter
,ComponentScans
,Configuration
,DependsOn
,Description
,Import
,Lazy
,Primary
,Scope
,PropertySource
,Conditional
,Profile
,Async
,Scheduled
。该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标签 的<context:component-scan>标签的 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.
* <p>
* 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中配置<bean/>标签功能是一样的。通常情况下是与@Configuration注解一起使用,并且通过和@Scope,@Lazy等注解一起来控制Bean的一些配置。
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
等价于在Spring XML中的配置:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
我们来简单看下@Bean的各个参数:
value
和name
,bean的名称;autowire
,注入方式,可以是ByName,byType,也可也选择NO,默认是Autowire.NO;initMethod
和destroyMethod
,实例化bean的时候的初始化方法,及销毁前执行的方法;
6. ComponentScan注解
该注解对应于XML配置中的 <context:component-scan>
,用来扫描相应的注解并自动注入bean。该注解于XML配置有些许不同:
- 该注解没有
annotation-config
对应的属性,因为我们程序几乎所有的情况,都会扫描@Autowired
等这一类<context:annotation-config>
所对应的注解,所以也就相当于有这个参数,但这个参数一直是true;- 此外,该注解多了
basePackageClasses
这个数组参数,表示指定特定的要扫描的类,注入的时候也只注入这些类;- 还有一个参数:
lazyInit
,表示扫描的时候根据bean是否配置了lazy来决定是否延迟初始化该bean。默认是false,也就是说无论bean是否配置了lazy属性,该bean被扫描到后都会立刻初始化,设置为true之后,如果bean配置了lazy属性,那么会根据bean的lazy属性来选择是否延迟初始化;
7. ComponentScan.Filter注解
ComponentScan注解的内部的一个注解,用在ComponentScan
的includeFilters
和excludeFilters
,而这里的用法和配置文件中的context:exclude-filter
和context: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中我们是可以配置多个<context:component-scan>
标签的,同样,使用该注解可以让我们在程序里配置多个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 ...
一般,该注解有以下几种用法:
- 会配合ComponentScan注解进行扫描,并且配合EnableWebMvc注解来开启MVC:
@Configuration
@ComponentScan("com.acme.app.services")
@EnableWebMvc
public class AppConfig {
// various @Bean definitions ...
}
- 配合
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"));
}
}
- 配合@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);
}
}
- 配合@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());
}
}
- 配合@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() { ... }
}
- 配合@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);
}
}
- 使用@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();
}
}
}
- 单元测试,引入@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注解,需要注意下:
- 该注解修饰的必须是类,不能是接口;
- 配置类必须不能是final类型的;
- 配置类不能是匿名类;
- 嵌套的配置类必须是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都是单例的。前文已经说过,这里就不多说了,看一下参数:
-
scopeName
和value
,作用域类型的名称; -
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;
}
}
再简单来看一下该注解的参数:
value
,要加载的资源位置,数组格式,和XML中支持的功能一样强大,包括classpath
,file
;但不支持类似的资源通配符:**/*.properties
,也就是.properties
必须要精确到具体位置;name
,对应属性资源的名称;encoding
,字符编码,比如UTF-8
格式;ignoreResourceNotFound
,如果找不到对应的属性资源,是否忽略。默认是false,这种情况下,如果找不到的话将会提示异常;factory
,指定一个资源工厂对象PropertySourceFactory
;、
一般情况下,该注解还会配合@Value来一起使用,这时候记得配置PropertySourcesPlaceholderConfigurer对象来解析占位符,在XML配置中我们可以通过<context:property-placeholder>
,在注解中我们可以将该对象注册为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如果标注在类上的话,则表示该类的所有方法都将是异步的。
- 异步方法的返回值必须是void或者Future,方法必须是public类型的;
- 如果要异步的话,首先必须要在配置类上添加
@EnableAsync
注解来开启异步;- @Async不能与生命周期函数一起使用,必须使用一个单独的初始化Spring bean来调用目标上的@Async注释方法;
- 在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<String> returnSomething(int i) {
// this will be executed asynchronously
}
}
XML中开启异步的话,是通过如下:
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
默认情况下,当@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中使用<task:annotation-driven/>
,而注解的话则是通过@EnableScheduling
来完成。
另外,该注解可以作为元注解,从而自定义我们的定时任务相关的注解。接下来我们来简单看下它的参数:
cron
,这个应该是我们使用最多的参数了,定时任务时间设置的表达式,比如每隔1天执行一次,每隔半小时执行,这个设置网上的介绍实在太多了,用的时候再去查询就可以了;zone
,cron表达式解析的时区,默认情况下,该属性是空字符串(也就是服务器的本地时区),接收的是TimeZone.getTimeZone(String)的id;fixedDelay
,每次执行任务之后间隔多久再次执行任务,以上次任务结束时间和下此任务开始时间之间的毫秒为单位来执行;比如说间隔时间是5秒,任务执行的时间是8秒,那么8秒执行后,下此就是8+5秒的时候在执行,再下此就是8+5+8+5的时候再执行,就是以结束时间为准;fixedDelayString
,和上面fixedDelay
一样,只不过参数类型是字符串形式,可以通过外部来定义,如 fixedDelayString= "${job.fixed.string}";;fixedRate
,固定频率的间隔时间,也就是每隔多久就执行,不管任务是否完成;fixedRateString
,和fixedRate
一样,只不过参数类型是字符串形式,可以通过外部来定义,如 fixedRateString = "${job.fixed.rate}";initialDelay
,该参数表示第一次执行定时任务之前需要等待多长时间,也就是第一次延时多长时间后执行,单位同样是毫秒;
有关fixedDelay
,fixedRate
,cron
,再稍微简单说下:
fixedDelay
比较简单,就是根据上次任务结束时间计算的,而cron
,和fixedDelay
则有点不太一样,比如间隔时间是5S,如果执行时间是8S,则下此执行的时间将从10S开始,会跳过一个间隔时间,如果执行时间是10S,则下此执行时间从15S开始;而如果执行时间是3S,那下此执行时间就将从5S开始,每隔5S执行;
至于fixedRate
,它的使用和上面两种都不太相同,如果上次任务执行时间超过了间隔时间,那么超出的这部分时间会被计入下一次任务的执行时间中,至于问题的细节,我们可以参考这篇文章:
https://yanbin.blog/understand-spring-schedule-fixedrate-fixeddelay/
在网上看到两幅图描述fixedDelay
,fixedRate
,描述的很形容:
图片转自:https://blog.csdn.net/applebomb/article/details/52400154
如果我们没有配置TaskScheduler 和 ScheduledExecutorService 的话,那么Scheduled默认是采用单线程来进行循环执行定时任务的,我们可以通过Thread.currentThread()来查看当前运行的线程,默认定时任务的线程是Executors.defaultThreadFactory() 产生的,线程名称是 "pool-NUMBER-thread-..."。
至于Schedules 注解,则是多个Scheduled组成的数组,就不说了。
网友评论