|
|
51CTO旗下网站
|
|
移动端
创建专栏

一文了解和 Spring Bean 有关的那些注解

随着Spring的流行,我们经历过基于XML-Based 的配置,随着SpringBoot的流行,我们逐渐使用基于注解的配置替换掉了基于XML-Based的配置,那么你知道基于注解的配置的基础组件都是什么吗?

作者:cxuan|2019-07-02 11:01

【线上直播】11月21日晚8点贝壳技术总监侯圣文《数据安全之数据库安全黄金法则》

随着Spring的流行,我们经历过基于XML-Based 的配置,随着SpringBoot的流行,我们逐渐使用基于注解的配置替换掉了基于XML-Based的配置,那么你知道基于注解的配置的基础组件都是什么吗?都包括哪些要素?那么本节就来探讨一下。注:本篇文章更多的是讨论Spring基于注解的配置一览,具体的技术可能没有那么深,请各位大佬见谅。

探讨主题:

  • 基础概念:@Bean 和 @Configuration
  • 使用AnnotationConfigApplicationContext 实例化Spring容器
  • 使用@Bean 注解
  • 使用@Configuration 注解
  • 编写基于Java的配置
  • Bean定义配置文件
  • PropertySource 抽象类
  • 使用@PropertySource
  • 占位符的声明

基础概念:@Bean 和 @Configuration

Spring中新的概念是支持@Bean注解 和 @Configuration 注解的类。@Bean 注解用来表明一个方法实例化,配置并且通过IOC容器初始化并管理一个新的对象。@Bean注解就等同于XML-Based中的标签,并且扮演了相同的作用。你可以使用基于注解的配置@Bean 和 @Component,然而他们都用在@Configuration配置类中。

使用@Configuration 注解的主要作用是作为bean定义的类,进一步来说,@Configuration注解的类允许通过调用同类中的其他@Bean标注的方法来定义bean之间依赖关系。如下所示:

新建一个maven项目(我一般都直接创建SpringBoot项目,比较省事),创建AppConfig,MyService,MyServiceImpl类,代码如下:

  1. @Configuration 
  2. public class AppConfig { 
  3.  
  4.     @Bean 
  5.     public MyService myService(){ 
  6.         return new MyServiceImpl(); 
  7.     } 
  8.  
  9. public interface MyService {} 
  10. public class MyServiceImpl implements MyService {} 

上述的依赖关系等同于XML-Based:

  1. <beans> 
  2.     <bean id="myService",class="com.spring.annotation.service.impl.MyServiceImpl"/> 
  3. </beans> 

使用AnnotationConfigApplicationContext 实例化Spring容器

AnnotationConfigApplicationContext 基于注解的上下文是Spring3.0 新添加的注解,它是ApplicationContext的一个具体实现,它可以接收@Configuration注解的类作为输入参数,还能接收使用JSR-330元注解的普通@Component类。

当提供了@Configuration 类作为输入参数时,@Configuration类就会注册作为bean的定义信息并且所有声明@Bean的方法也都会作为bean的定义信息。

当提供@Component和JSR-330 声明的类时,他们都会注册作为bean的定义信息,并且假设在必要时在这些类中使用诸如@Autowired或@Inject之类的注解

简单的构造

在某些基于XML-Based的配置,我们想获取上下文容器使用ClassPathXmlApplicationContext,现在你能够使用@Configuration 类来实例化AnnotationConfigApplicationContext。

在MyService中添加一个printMessage()方法,实现类实现对应的方法。新建测试类进行测试

  1. public class ApplicationTests { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); 
  5.         MyService service = context.getBean(MyService.class); 
  6.           // printMessage() 输出something... 
  7.         service.printMessage(); 
  8.     } 

如前所述,AnnotationConfigApplicationContext不仅限于使用@Configuration类。任何@Component或JSR-330带注释的类都可以作为输入提供给构造函数,如下例所示

  1. public class ApplicationTests { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ApplicationContext context = new AnnotationConfigApplicationContext(MyServiceImpl.class,Dependency1.class,Dependency2.class); 
  5.         MyService myService = context.getBean(MyService.class); 
  6.         myService.printMessage(); 
  7.     } 

使用register注册IOC容器

你可以实例化AnnotationConfigApplicationContext通过使用无参数的构造器并且使用register方法进行注册,它和AnnotationConfigApplicationContext带参数的构造器起到的效果相同。

  1. public class ApplicationTests { 
  2.  
  3.     public static void main(String[] args) { 
  4.         AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 
  5.         ctx.register(AppConfig.class, OtherConfig.class); 
  6.         ctx.register(AdditionalConfig.class); 
  7.         ctx.refresh(); 
  8.         MyService myService = ctx.getBean(MyService.class); 
  9.         System.out.println(ctx.getBean(OtherConfig.class)); 
  10.         System.out.println(ctx.getBean(AdditionalConfig.class)); 
  11.         myService.printMessage(); 
  12.     } 

OtherConfig.class 和 AdditionalConfig.class 是使用@Component 标注的类。

允许scan()方法进行组件扫描

为了允许组件进行扫描,需要在@Configuration配置类添加@ComponentScan()注解,改造之前的AdditionalConfig类,如下:

  1. @Configuration 
  2. @ComponentScan(basePackages = "com.spring.annotation.config"
  3. public class AdditionalConfig {} 

@ComponentScan指定了基础扫描包位于com.spring.annotation.config下,所有位于该包范围内的bean都会被注册进来,交由Spring管理。它就等同于基于XML-Based的注解:

  1. <beans> 
  2.     <context:component-scan base-package="com.spring.annotation.config/> 
  3. </beans> 

AnnotationConfigApplicationContext中的scan()方法以允许相同的组件扫描功能,如以下示例所示:

  1. public static void main(String[] args) { 
  2.   AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 
  3.   ctx.scan("com.spring.annotation"); 
  4.   ctx.refresh(); 
  5.   MyService myService = ctx.getBean(MyService.class); 

为什么说@Configuration用法和@Component都能够标注配置类?因为@Configuration的元注解就是@Component。

  1. @Target({ElementType.TYPE}) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. @Documented 
  4. @Component 
  5. public @interface Configuration { 
  6. String value() default ""

使用AnnotationConfigWebApplicationContext支持web容器

AnnotationConfigApplicationContext的一个WebApplicationContext的变化是使用AnnotationConfigWebApplicationContext。配置Spring ContextLoaderListener的servlet监听器,Spring MVC的DispatcherServlet等时,可以使用此实现。以下web.xml代码段配置典型的Spring MVC Web应用程序(请注意context-param和init-param的使用)

  1. <web-app> 
  2.       <!-- 配置web上下文监听器使用 AnnotationConfigWebApplicationContext 而不是默认的 
  3.         XmlWebApplicationContext --> 
  4.     <context-param> 
  5.         <param-name>contextClass</param-name
  6.         <param-value> 
  7.             org.springframework.web.context.support.AnnotationConfigWebApplicationContext 
  8.         </param-value> 
  9.     </context-param> 
  10.  
  11.       <!-- 配置位置必须包含一个或多个以逗号或空格分隔的完全限定的@Configuration类。 也可以为组件扫描指定完全           限定的包--> 
  12.     <context-param> 
  13.         <param-name>contextConfigLocation</param-name
  14.         <param-value>com.spring.annotation.config.AdditionalConfig</param-value> 
  15.     </context-param> 
  16.  
  17.       <!--使用ContextLoaderListener像往常一样引导根应用程序上下文--> 
  18.     <listener> 
  19.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
  20.     </listener> 
  21.  
  22.       <!-- 定义一个SpringMVC 核心控制器 DispatcherServlet--> 
  23.     <servlet> 
  24.         <servlet-name>dispatcher</servlet-name
  25.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
  26.         <!-- 配置web上下文监听器使用 AnnotationConfigWebApplicationContext 而不是默认的 
  27.                 XmlWebApplicationContext --> 
  28.         <init-param> 
  29.             <param-name>contextClass</param-name
  30.             <param-value> 
  31.                 org.springframework.web.context.support.AnnotationConfigWebApplicationContext 
  32.             </param-value> 
  33.         </init-param> 
  34.         <!-- 配置位置必须包含一个或多个以逗号或空格分隔的完全限定的@Configuration类。 也可以为组件扫描指定                    完全限定的包--> 
  35.         <init-param> 
  36.             <param-name>contextConfigLocation</param-name
  37.             <param-value>com.spring.annotation.config.MvcConfig</param-value> 
  38.         </init-param> 
  39.     </servlet> 
  40.  
  41.     <!-- 将/app/* 的所有请求映射到调度程序servlet --> 
  42.     <servlet-mapping> 
  43.         <servlet-name>dispatcher</servlet-name
  44.         <url-pattern>/app/*</url-pattern> 
  45.     </servlet-mapping> 
  46. </web-app> 

使用@Bean注解

@Bean 注解是一个方法级别的注解,能够替换XML-Based中的标签,@Bean注解同样支持标签支持的属性,像是 init-method, destroy-method, autowiring。

定义一个Bean

与基础概念中Bean的定义相同,读者可以参考基础概念部分进行了解,我们不在此再进行探讨。

Bean的依赖

@Bean 注解可以有任意数量的参数来构建其依赖项,例如

  1. public class MyService { 
  2.     private final MyRepository myRepository; 
  3.     public MyService(MyRepository myRepository) { 
  4.         this.myRepository = myRepository; 
  5.     } 
  6.     public String generateSomeString() { 
  7.         return myRepository.findString() + "-from-MyService"
  8.     } 
  9.  
  10. @Configuration 
  11. class MyConfiguration { 
  12.     @Bean 
  13.     public MyService myService() { 
  14.         return new MyService(myRepository()); 
  15.     } 
  16.     @Bean 
  17.     public MyRepository myRepository() { 
  18.         return new MyRepository(); 
  19.     } 
  20.  
  21. public class MyRepository { 
  22.     public String findString() { 
  23.         return "some-string"
  24.     } 

接受生命周期回调

任何使用@Bean的注解都支持生命周期的回调,使用JSR-220提供的@PostConstruct和@PreDestory注解来实现。如果bean实现了InitializingBean,DisposableBean或者Lifecycle接口,他们的方法会由IOC容器回调。一些以Aware的实现接口(像是BeanFactoryAware,BeanNameAware, MessageSourceAware, ApplicationContextAware等)也支持回调。

@Bean注解支持特定的初始化和销毁方法,就像XML-Based中的init-method和 destory-method中的bean属性,下面这个例子证实了这一点

  1. @Configuration 
  2. public class AppConfig { 
  3.  
  4.     @Bean(initMethod = "init"
  5.     public BeanOne beanOne(){ 
  6.         return new BeanOne(); 
  7.     } 
  8.  
  9.     @Bean(destroyMethod = "cleanup"
  10.     public BeanTwo beanTwo(){ 
  11.         return new BeanTwo(); 
  12.     } 
  13.  
  14. class BeanOne { 
  15.     public void init(){} 
  16.  
  17. class BeanTwo { 
  18.     public void cleanup(){} 

对于上面的例子,也可以手动调用init()方法,与上面的initMethod 方法等效

  1. @Bean 
  2. public BeanOne beanOne(){ 
  3.   BeanOne beanOne = new BeanOne(); 
  4.   beanOne.init(); 
  5.   return beanOne; 

当你直接使用Java开发时,你可以使用对象执行任何操作,并且不必总是依赖于容器生命周期。

Bean的作用范围

Spring包括@Scope注解能够让你指定Bean的作用范围,Bean的Scope默认是单例的,也就是说@Bean标注的对象在IOC的容器中只有一个。你可以重写@Scope的作用范围,下面的例子说明了这一点,修改OtherConfig如下

  1. @Configuration 
  2. public class OtherConfig { 
  3.  
  4.     @Bean 
  5.     @Scope("prototype"
  6.     public Dependency1 dependency1(){ 
  7.         return new Dependency1(); 
  8.     } 

每次尝试获取dependency1这个对象的时候都会重新生成一个新的对象实例。下面是Scope的作用范围和解释:

@Scope和Scoped-proxy

Spring提供了一种通过scoped proxies与scoped依赖一起作用的方式。最简单的在XML环境中创建代理的方式是通过标签。使用@Scope注解为在Java中配置bean提供了与proxyMode属性相同的功能。默认是不需要代理的(ScopedProxyMode.NO),但是你需要指定ScopedProxyMode.TARGET_CLASS或者ScopedProxyMode.INTERFACES。

自定义Bean名称

默认的情况下,配置类通过@Bean配置的默认名称(方法名***个字母小写)进行注册和使用,但是你可以更换@Bean的name为你想指定的名称。修改AdditionalConfig 类

  1. @Configuration 
  2. //@ComponentScan(basePackages = "com.spring.annotation.config"
  3. public class AdditionalConfig { 
  4.  
  5.     @Bean(name = "default"
  6.     public Dependency2 dependency2(){ 
  7.         return new Dependency2(); 
  8.     } 

Bean的别名

有时候需要为单例的bean提供多个名称,也叫做Bean的别名。Bean注解的name属性接收一个Array数组。下面这个例子证实了这一点:

  1. @Configuration 
  2. public class OtherConfig { 
  3.  
  4. //    @Bean 
  5. //    @Scope("prototype"
  6. //    public Dependency1 dependency1(){ 
  7. //        return new Dependency1(); 
  8. //    } 
  9.  
  10.         @Bean({"dataSource""dataSourceA""dataSourceB"}) 
  11.     public DataSource dataSource(){ 
  12.             return null
  13.     } 

Bean的描述

有时,提供更详细的bean描述信息会很有帮助(但是开发很少使用到)。为了增加一个对@Bean的描述,你需要使用到@Description注解

  1. @Configuration 
  2. public class OtherConfig { 
  3.  
  4. //    @Bean 
  5. //    @Scope("prototype"
  6. //    public Dependency1 dependency1(){ 
  7. //        return new Dependency1(); 
  8. //    } 
  9.  
  10. //    @Bean({"dataSource""dataSourceA""dataSourceB"}) 
  11. //    public DataSource dataSource(){ 
  12. //        return null
  13. //    } 
  14.  
  15.     @Bean 
  16.     @Description("此方法的bean名称为dependency1"
  17.     public Dependency1 dependency1(){ 
  18.         return new Dependency1(); 
  19.     } 

使用@Configuration注解

已经把@Configuration的注解说明的比较详细了。

组成Java-Based环境配置的条件

Spring基于注解的配置能够允许你自定义注解,同时能够降低配置的复杂性。

使用@Import注解

就像在Spring XML文件中使用元素来帮助模块化配置一样,@Import 注解允许从另一个配置类加载@Bean定义,如下所示

  1. @Configuration 
  2. public class ConfigA { 
  3.  
  4.     @Bean 
  5.     public A a(){ 
  6.         return new A(); 
  7.     } 
  8.  
  9. @Configuration 
  10. @Import(ConfigA.class) 
  11. public class ConfigB { 
  12.  
  13.     @Bean 
  14.     public B b(){ 
  15.         return new B(); 
  16.     } 

现在,在实例化上下文时,不需要同时指定ConfigA.class 和 ConfigB.class ,只需要显示提供ConfigB

  1. public static void main(String[] args) { 
  2.     ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); 
  3.  
  4.     A a = ctx.getBean(A.class); 
  5.     B b = ctx.getBean(B.class); 

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求你在构造期间记住可能大量的@Configuration类

有选择性的包含@Configuration 类和@Bean 方法

选择性的允许或者禁止@Configuration注解的类和@Bean注解的方法是很有用的,基于一些任意系统状态。一个常见的例子是只有在Spring环境中启用了特定的配置文件时才使用@Profile注释激活bean。

@Profile注解也实现了更灵活的注解@Conditional,@Conditional 注解表明在注册@Bean 之前应参考特定的Condition实现。

实现Condition接口就会提供一个matched方法返回true或者false

更多关于@Conditional 的示例,请参考https://www.cnblogs.com/cxuanBlog/p/10960575.html

结合Java与XML配置

Spring @Configuration类能够100%替换XML配置,但一些工具(如XML命名空间)仍旧是配置容器的***方法,在这种背景下,使用XML使很方便的而且使刚需了。你有两个选择:使用以XML配置实例化容器为中心,例如:ClassPathXmlApplicationContext导入XML或者实例化以Java配置为中心的AnnotationConfigApplicationContext并提供ImportResource注解导入需要的XML配置。

将@Configuration声明为普通的bean元素

请记住,@Configuration类存放的是容器中的bean定义信息,下面的例子中,我们将会创建一个@Configuration类并且加载了外部xml配置。下面展示了一个普通的Java配置类

  1. @Configuration 
  2. public class AppConfig { 
  3.  
  4.     @Autowired 
  5.     private DataSource dataSource; 
  6.  
  7.     @Bean 
  8.     public AccountRepository accountRepository() { 
  9.         return new JdbcAccountRepository(dataSource); 
  10.     } 
  11.  
  12.     @Bean 
  13.     public TransferService transferService() { 
  14.         return new TransferService(accountRepository()); 
  15.     } 

下面是system-test-config.xml配置类的一部分

  1. <beans> 
  2.  
  3.       <!--允许开启 @Autowired 或者 @Configuration--> 
  4.     <context:annotation-config/> 
  5.  
  6.          <!-- 读取外部属性文件 --> 
  7.       <!-- 更多关于属性读取的资料,参考 https://www.cnblogs.com/cxuanBlog/p/10927819.html --> 
  8.     <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/> 
  9.  
  10.     <bean class="com.spring.annotation.config.AppConfig"/> 
  11.  
  12.     <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"
  13.           <property name="driverClassName" value="${jdbc.driverClassName}" /> 
  14.         <property name="url" value="${jdbc.url}"/> 
  15.         <property name="username" value="${jdbc.username}"/> 
  16.         <property name="password" value="${jdbc.password}"/> 
  17.     </bean> 
  18. </beans> 

引入jdbc.properties建立数据库连接

  1. jdbc.driverClassName=com.mysql.jdbc.Driver 
  2. jdbc.url=jdbc:mysql://localhost:3306/sys 
  3. jdbc.username=root 
  4. jdbc.password=123456 
  1. public static void main(String[] args) { 
  2.     ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/spring/annotation/system-test-config.xml"); 
  3.     TransferService transferService = ctx.getBean(TransferService.class); 
  4.     // ... 

在system-test-config.xml中,AppConfig 对应的标签没有声明id属性,虽然这样做是可以接受的,但是没有必要,因为没有其他bean引用它,并且不太可能通过名称从容器中获取它。同样的,DataSource bean只是按类型自动装配,因此不严格要求显式的bean id。

使用<> 挑选指定的@Configuration类

因为@Configuration的原注解是@Component,所以@Configuration注解的类也能用于组件扫描,使用与前一个示例中描述的相同的方案,我们可以重新定义system-test-config.xml以利用组件扫描。请注意,在这种情况下,我们不需要显式声明<context:annotation-config />,因为<context:component-scan />启用相同的功能。

  1. <beans> 
  2.  
  3.     <context:component-scan base-package="com.spring.annotation"/> 
  4.     <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/> 
  5.  
  6.     <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"
  7.           <property name="driverClassName" value="${jdbc.driverClassName}" /> 
  8.         <property name="url" value="${jdbc.url}"/> 
  9.         <property name="username" value="${jdbc.username}"/> 
  10.         <property name="password" value="${jdbc.password}"/> 
  11.     </bean> 
  12. </beans> 

@Configuration 类使用@ImportResource

在基于Java注解的配置类中,仍然可以使用少量的@ImportResource导入外部配置,***的方式就是两者结合,下面展示了一下Java注解结合XML配置的示例

  1. @Configuration 
  2. @ImportResource("classpath:/com/spring/annotation/properties-config.xml"
  3. public class AppConfig { 
  4.  
  5.     @Value("${jdbc.driverClassName}"
  6.       private String driver; 
  7.  
  8.     @Value("${jdbc.url}"
  9.     private String url; 
  10.  
  11.     @Value("${jdbc.username}"
  12.     private String username; 
  13.  
  14.     @Value("${jdbc.password}"
  15.     private String password
  16.  
  17.     @Bean 
  18.     public DataSource dataSource() { 
  19.         return new DriverManagerDataSource(url, username, password); 
  20.     } 

Properties-config.xml

  1. <beans> 
  2.     <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/> 
  3. </beans> 

jdbc.properties

  1. jdbc.driverClassName=com.mysql.jdbc.Driver 
  2. jdbc.url=jdbc:mysql://localhost:3306/sys 
  3. jdbc.username=root 
  4. jdbc.password=123456 
  1. public static void main(String[] args) { 
  2.     ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); 
  3.     TransferService transferService = ctx.getBean(TransferService.class); 
  4.     // ... 

【编辑推荐】

  1. 如何给Spring Boot 的嵌入式 Tomcat 部署多个应用?
  2. Spring Cloud Stream 使用延迟消息实现定时任务(RabbitMQ)
  3. Spring WebFlux 要革了谁的命?
  4. 没想到,看《复联4》也能理解Spring Cloud
  5. 开发 | Spring Boot是如何实现日志的?
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢