spring应用手册 IOC(注解方式配置)第二部分

302 阅读8分钟

2.11 @Scope注解

@Scope注解源码:

 package org.springframework.context.annotation;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 public @interface Scope {
     @org.springframework.core.annotation.AliasFor("scopeName")
     java.lang.String value() default "";
 ​
     @org.springframework.core.annotation.AliasFor("value")
     java.lang.String scopeName() default "";
 ​
     org.springframework.context.annotation.ScopedProxyMode proxyMode() default org.springframework.context.annotation.ScopedProxyMode.DEFAULT;
 }

@Scope可以注解在某个交给spring管理的类上方,也可以注解在@Bean注解的方法上方。作用和配置文件中bean标签的scope属性一致。主要用于配置交给spring管理的bean的作用域。

主要有以下几个值:

   singleton:单例模式(默认);    prototype:多例模式;    request:作用于web应用的请求范围;    session:作用于web应用的会话范围;

当然我们还可以使用字符串常量配置:

ConfigurableBeanFactory.SCOPE_SINGLETON(单利)

ConfigurableBeanFactory.SCOPE_PROTOTYPE(多例)

WebApplicationContext.SCOPE_SESSION(会话范围)

WebApplicationContext.SCOPE_REQUEST(请求范围)

单利模式下,spring只会为当前bean创建一个实例,每次从spring容器中获取的都是同一个对象。并且在spring容器初始化时,就会创建该对象。

多利模式下,spring初始化时不会创建该对象,每次从spring容器中获取这个bean对象时,spring都会创建要给新的。

回话范围和请求范围是web应用专用的,回话范围,spring会为每次回话创建要给当前Bean的实例。请求范围,spring会为每次请求创建一个当前bean的实例。

案例:

 @Component
 @Scope(value= ConfigurableBeanFactory.SCOPE_SINGLETON)
 public class AuthorDAO implements IAutorDAO  {
     @Override
     public String get() {
         return "戴着假发的程序员";
     }
 }

注意:value属性和scopeName并不是互斥的,也就是可以同时配置,但是这两个配置的范围必须一致,如果不一致会抛异常。(当然我觉得不会有人非要两个属性一起配置)

2.12 @Scope的proxyMode属性

proxyMode是用来配置当前类的代理模式的。主要用于scope非singleton的情况。因为非singleton的bean spring并不会立刻创建对象,如果需要注入时就产生一个代理对象,这时代理模式就起作用了。

有下面的几个值:

 public enum ScopedProxyMode {
     DEFAULT, NO, INTERFACES, TARGET_CLASS;
     private ScopedProxyMode() { /* compiled code */ }
 }

默认值DEFAULT 就是 NO :意思就是不使用代理,如果需要就立刻创建。

INTERFACES : 表示使用jdk实现动态代理。

TARGET_CLASS:表示使用CGLib实现动态代理。

2.13 @Lazy注解

@Lazy注解,是用来配置bean是否延迟加载,默认是true,但是配置了才生效,不配置就不生效。

@Lazy源码:

 package org.springframework.context.annotation;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 public @interface Lazy {
     boolean value() default true;
 }

@Lazy可以注解在某个交给spring管理的类上方,也可以注解在@Bean注解的方法上方。作用和配置文件中bean标签的lazy-init属性一致。 主要用于表示这bean是否要延迟实例化。 默认值是true。所以我们一旦配置了@Lazy,那么这个Bean就会延迟加载。 如果不希望延迟加载就可以不配置或者配置之后,将属性value修改为false。

     @Bean
     @Lazy(false)
     public UserService userService(){
         return new UserService();
     }
 @Component
 @Lazy
 public class AuthorDAO implements IAutorDAO  {}

2.14@DependsOn

@DependsOn可以注解在某个交给spring管理的类上方,也可以注解在@Bean注解的方法上方。作用和配置文件中bean标签的DependsOn属性一致。主要是来配置当前类的依赖类。

spring会在实例化当前类之前,先实例化DependsOn指定的bean,在销毁当前类之后才会销毁DependsOn指定的bean。

案例:

我们给ArticleDAO和AuthroDAO都添加构造方法,并且输出信息。

 @Component
 public class AuthorDAO implements IAutorDAO  {
     public AuthorDAO(){
         System.out.println("实例化AuthorDAO");
     }
 }
 @Component
 public class ArticleDAO implements IArticleDAO {
     public ArticleDAO(){
         System.out.println("实例化ArticleDAO");
     }
 }

创建容器测试:

1585817865764.png

先实例化ArticleDAO,后实例化AuthorDAO。

我们再ArticleDAO上添加配置:

1585817948436.png 在测试:

1585817961079.png

2.15 @Conditional注解

源码:

 package org.springframework.context.annotation;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 public @interface Conditional {
     java.lang.Class<? extends org.springframework.context.annotation.Condition>[] value();
 }

@Conditional可以注解在由spring管理的类上方和@bean注解的方法上方。

主要作用是配置一些注册bean对应的条件,如果满足条件就注册bean,如果不满足就不进行bean的注册。

我看到 @Conditional属性value是一组Condition的类型数组。

我们再来看看Condition源码:

 package org.springframework.context.annotation;
 ​
 @java.lang.FunctionalInterface
 public interface Condition {
     boolean matches(org.springframework.context.annotation.ConditionContext conditionContext, org.springframework.core.type.AnnotatedTypeMetadata annotatedTypeMetadata);
 }

该接口中只有一个metches方法,返回true说明条件成立,返回false说明条件不成立。

我们可以自己实现一个。

看案例:

我们自己实现一个Condition。

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 public class WindosCondition implements Condition {
     /**
      *
      * @param conditionContext 判断条件的上下文环境,可以获取环境对象,和工厂对象
      * @param annotatedTypeMetadata  注解所在位置的注释信息
      * @return
      */
     @Override
     public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
         //获取环境对象
         Environment environment = conditionContext.getEnvironment();
         //判断环境的名字
         if(environment.getProperty("os.name").contains("Windows")){
             return true;
         }
         return false;
     }
 }

在AuthorDAO上添加配置:

1585818669745.png 测试:

1585818750026.png 修改条件,再测试。

2.16@Primary

 package org.springframework.context.annotation;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 public @interface Primary {
 }

@Primary可以注解在由spring管理的类上方和@bean注解的方法上方。作用和bean标签的primary属性一致。

就是让其他bean在把当前bean作为注入对象时,如果存在多个和当前Bean同类型的bean,则优先选择当前bean。当然如果有多个同类型的bean并且多个bean上都注释了@Primary,则会抛出异常。

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 @Primary
 public class ArticleDAO_other implements IArticleDAO {
     @Override
     public int save(String title) {
         System.out.println("ArticleDAO_other-save->保存文章:"+title);
         return 1;
     }
 }

2.17@Profile注解

 package org.springframework.context.annotation;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 @org.springframework.context.annotation.Conditional({org.springframework.context.annotation.ProfileCondition.class})
 public @interface Profile {
     java.lang.String[] value();
 }

@Profile和配置文件的Profile有一样的作用,就是可以在不同的环境(条件)下让配置(注册生效)。 看案例:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 @Profile("oracle")
 public class ArticleDAO_oracle implements IArticleDAO {
     public ArticleDAO_oracle(){
         System.out.println("实例化ArticleDAO_oracle");
     }
 }
 ​
 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 @Profile("mysql")
 public class ArticleDAO_mysql implements IArticleDAO {
     public ArticleDAO_oracle(){
         System.out.println("实例化ArticleDAO_mysql");
     }
 }

测试:

 /**
  * @author 戴着假发的程序员
  */
 public class SpringTest {
     @Test
     public void testAnnotation() throws IOException {
         AnnotationConfigApplicationContext ac =
                 new AnnotationConfigApplicationContext();
         //设置启动环境
         ac.getEnvironment().setActiveProfiles("mysql");
         //注册扫描包,注册所有的bean
         ac.scan("com.st");
         //刷新容器
         ac.refresh();
     }
 }

测试结果:

 实例化ArticleDAO_mysql

我们发现ArticleDAO_oracle根本就没有实例化。

当然激活环境的方法还有很多。可以参考 XML配置profile章节。

2.18 @Autowired注解

查看@Autowired注解源码:

 package org.springframework.beans.factory.annotation;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.ANNOTATION_TYPE})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Documented
 public @interface Autowired {
     boolean required() default true;
 }

我们会发现Autowired有属性required,默认是true。

@AutoWired注解和bean标签中的autoWired属性差不多,可以通知spring帮我们自动组装Bean,例如:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class ArticleService {
     @Autowired
     private IAutorDAO autorDAO;
     @Autowired
     private IArticleDAO articleDAO;
 ​
     public int save(String title){
         String author = autorDAO.get();
         System.out.println("ArticleService-save:");
         System.out.println("author:"+author);
         return articleDAO.save(title);
     };
 }

@Autowired注解可以写在成员变量上方,当然那也可以写在对应的setter方法上。 当然如果没有setter方法,我们会发现spring依然可以帮我们注入这些属性。

@Autowired默认首先是按照类型注入的,如果在当前容器中找到了多个同种类型的bean,就按照名称注入,如果一个都没找到就抛异常。

当然我们也可以通过修改属性required为false,通过spring如果找不到就组装。

2.19 @Qualifier注解

查看源码:

 package org.springframework.beans.factory.annotation;
 ​
 @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE})
 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
 @java.lang.annotation.Inherited
 @java.lang.annotation.Documented
 public @interface Qualifier {
     java.lang.String value() default "";
 }

@Qualifirer主要用来指定要注入的bean的限定符(beanName)。

当我们需要注入某个类型的bean时,同时spring容器中有多同样类型的bean,这时我们可以通过@Qualifirer指定要注入的beanName。

用法如下:

[1]可以和@Autowired配合使用

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class ArticleService {
     @Autowired
     @Qualifier("articleDAO_mysql")
     private IAutorDAO autorDAO;
    //...
 }

[2]也可以在setter方法的参数上使用

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class ArticleService {
     @Autowired
     private IAutorDAO autorDAO;
     public void setAutorDAO(@Qualifier("articleDAO_mysql") IAutorDAO autorDao){
         this.autorDAO = autorDao;
     }
    //...
 }

[3]也可以在构造方法中使用

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class ArticleService {
     @Autowired
     private IAutorDAO autorDAO;
     public ArticleService(@Qualifier("articleDAO_mysql") IAutorDAO autorDao){
         this.autorDAO = autorDao;
     }
    //...
 }

[4]你还可以扩展自己的限定符注解

 @Target({ElementType.FIELD, ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 @Qualifier
 public @interface Genre {
     String value();
 }

2.20 @Resource注解

@Resource是 JSR-250标准。可以注解在bean的成员变量或者setter方法上。主要也是用于通知spring当前的属性应该注入哪个bean。

@Resource和@Autowired不同的是@Resource会首先按照名称注入,如果名称找不到才会按照类型注入。

我们可以通过@Resource的属性name指定要注入的bean的beanName,当然如果不指定name属性的话spring或自动根据属性或者setter方法找到对应的beanName例如:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class ArticleService {
     @Resource  
     private IArticleDAO articleDAO;
     //...
 }

上面的程序中spring生成的beanName默认是"artilceDAO"。

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class ArticleService {
     private IArticleDAO articleDAO;
     @Resource
     public void setAuthorDAO(IArticleDAO articleDAO){
         this.articleDAO = articleDAO;
     }
     //...
 }

上面的程序中spring生成的beanName默认是"authorDAO"。

当然我们可以指定名称:

 /**
  * @author 戴着假发的程序员
  *  
  * @description
  */
 @Component
 public class ArticleService {
     @Resource(name="articleDAO_oracle")
     private IArticleDAO articleDAO;
     //...
 }

这是spring就会查找beanName为articleDAO_oracle的bean注入。

几个要注意的问题:

[1]@Resource默认会按照名称查找容器中的bean,如果名称找不到就会按照类型查找,如果这时同种类型的bean在容器中出现多个,一样会抛出异常。

[2]如果我们显式的指定了@Resource的name属性,那么spring如果通过这个名字找不到对应的bean就会报错,不会再按照类型查找。

未完待续。。。。 欢迎关注