Spring--基于注解的容器配置介绍

Spring–基于注解的容器配置介绍

一、Spring IoC容器简介

1.前言

在JavaEE领域,Spring是当之无愧的王者,它简化了Java应用程序企业级开发的工作,提供了在企业环境中使用Java语言所需的一切。很多初学者学习Spring都是通过网课,整个学习过程十分零碎,学到的也只有单纯的简单应用,而对Spring的设计原则、实现原理都不甚了解,导致的最直接的后果就是:学习过的东西很快就会遗忘,这次解决了程序bug却没搞懂原因,继而下次又会重复碰到类似的bug。

2.什么是IoC?

IoC(Inversion of Control ,控制反转)也称为依赖注入(Dependency Injection)。 在此过程中,对象仅通过

  • 构造函数参数

  • 工厂方法的参数

  • 在构造函数、工厂方法返回对象实例后,在对象实例上设置属性

来定义对象的依赖项 , Spring IoC容器在创建bean时,会自动注入这些依赖项。

举个栗子,在没有引入IOC容器之前,如果对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B,而无论是创建还是使用对象B,控制权都在自己手上。 在引入IOC容器之后,这种情形就完全改变了,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。 通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

3.IoC容器配置的几种方式

  • 基于注解的容器配置(Annotation-based Container Configuration)
  • 基于Java的容器配置(Java-based Container Configuration)

二、基于注解的容器配置

1.XML配置与注解配置

在配置Spring时,注释是否比XML更好?Spring文档叙述翻译如下:

基于注释的配置的引入提出了一个问题,即这种方法是否比XML“更好”。简短的答案是“看情况”。长的答案是每种方法都有其优缺点,通常,由开发人员决定哪种策略更适合他们。由于定义方式的不同,注释在声明中提供了很多上下文,从而使配置更短,更简洁。但是,XML擅长连接组件而不接触其源代码或重新编译源代码。一些开发人员更喜欢将配置项放置在靠近源代码的位置,而另一些开发人员则认为带注解的类不再是POJO,而且,该配置变得分散且难以控制

无论选择如何,Spring都可以容纳两种样式,甚至可以将它们混合在一起。值得指出的是,通过Spring的JavaConfig(Java配置)选项,Spring允许以非侵入方式使用注解,无需接触目标组件的源代码。

另外需要注意的是,注解注入在XML注入之前执行。因此,如果一个属性同时采用了XML配置和注解配置,注解配置将会被XML配置所覆盖。

2.注解配置简介

基于注解的配置提供了XML配置的替代方法,该配置依赖字节码元数据( bytecode metadata )来连接组件,而不是尖括号声明。通过使用相关类,方法或字段声明上的注解,开发人员无需使用XML来描述bean的配置,而是将配置移入组件类本身。

3.用于依赖注入的各种注解

1.@Required

@Required注解适用于bean属性的Setter方法,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

在此注解下,程序在注入配置时,需要通过bean定义中的显式属性值或通过自动装配来填充相关的bean属性。 如果没有填充相关的bean属性,则容器将引发异常,这种急切的显式失败,可以避免程序运行后再出现空指针异常。

从Spring Framework 5.1版本开始,@Required注解已正式弃用,转而使用构造函数注入进行必需的设置。

2.@Autowired

  • 用在构造方法上

    public class MovieRecommender {
    
        private final CustomerPreferenceDao customerPreferenceDao;
    
        @Autowired
        public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
            this.customerPreferenceDao = customerPreferenceDao;
        }
    
        // ...
    }
    

    从Spring Framework 4.3版本开始,如果目标bean仅有一个构造函数,则不再需要在构造函数上使用@Autowired批注。 但是,如果目标bean有多个构造函数,则必须至少使用@Autowired注释其中一个,以指示容器使用哪个构造函数进行注入。

  • 用在Setter方法上

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        @Autowired
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // ...
    }
    
  • 用在具有任意名称和多个参数的方法上

    public class MovieRecommender {
    
        private MovieCatalog movieCatalog;
    
        private CustomerPreferenceDao customerPreferenceDao;
    
        @Autowired
        public void prepare(MovieCatalog movieCatalog,
                CustomerPreferenceDao customerPreferenceDao) {
            this.movieCatalog = movieCatalog;
            this.customerPreferenceDao = customerPreferenceDao;
        }
    
        // ...
    }
    
  • 用在成员变量上,并且可以与构造函数混合使用

    public class MovieRecommender {
    
        private final CustomerPreferenceDao customerPreferenceDao;
    
        @Autowired
        private MovieCatalog movieCatalog;
    
        @Autowired
        public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
            this.customerPreferenceDao = customerPreferenceDao;
        }
    
        // ...
    }
    

    在使用@AutoWired注解时,注入的Bean的类型要和注入点处的方法参数对象类型一致。 否则,运行时会出现"no type match found" 错误,而导致注入失败。

  • 用在类型为数组或集合的成员变量上,指示Spring从ApplicationContext提供特定类型的所有bean

    public class MovieRecommender {
    
        @Autowired
        private MovieCatalog[] movieCatalogs;
    
        // ...
    }
    
    public class MovieRecommender {
    
        private Set<MovieCatalog> movieCatalogs;
    
        @Autowired
        public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
            this.movieCatalogs = movieCatalogs;
        }
    
        // ...
    }
    

    如果希望数组或集合中的元素按特定顺序排序,可以在目标bean上使用@Order@Priority注解进行指定,否则数组或集合中的元素的顺序将按照容器中相应目标bean的注册顺序。

  • 只要预期的key类型为String,Map实例也可以自动装配。

    public class MovieRecommender {
    
        private Map<String, MovieCatalog> movieCatalogs;
    
        @Autowired
        public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
            this.movieCatalogs = movieCatalogs;
        }
    
        // ...
    }
    

    映射值(map values)包含所有预期类型的bean,并且键值(map key)包含相应的bean名称。 就拿上面例子来说,只要是MovieCatalog类型的bean都可以被自动注入到Map<String, MovieCatalog>中,键值就是bean名称,映射值是被注入的bean对象。

    默认情况下,当给定注入点没有匹配的候选bean可用时,自动装配将失败。 对于声明的数组,Set或Map,至少应有一个匹配元素。

注入时,默认是将带注解的方法和字段视为必需的依赖项,如果注入失败,会抛出Injection of autowired dependencies failed错误,但可以通过将@Autowired中的required属性设置为false,表示该属性对于自动装配不是必需的,如果无法自动装配,该属性将被忽略。

3.@Primary

使用@Primary可以微调基于注解的自动装配。由于按类型自动注入可能会导致出现多个候选bean对象,因此通常有必要对选择过程进行更多的控制。 当有多个bean作为自动注入到单值依赖项的候选对象时,应优先使用被@Primary修饰的bean,即 如果候选bean中恰好存在一个被@Primary修饰的bean,它将成为自动装配的值。

举个栗子,下面的配置创建了了两个MovieCatalog类型的bean,其中一个bean被@Primary修饰:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

使用上面的配置,下面的MovieRecommender将自动注入firstMovieCatalog,而不会是secondMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

4.@Qualifier

当需要对bean的选择过程进行更多控制时,可以使用@Qualifier注解。 可以通过将限定符值(qualifier)与特定的参数相关联,从而缩小类型匹配的范围,以便为每个参数选择特定的bean。 在最简单的情况下,限定符值可以是简单的描述性值,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

还可以在各个构造函数参数或方法参数上指定@Qualifier注解,如以下示例所示:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

在自动注入时,限定符值必须和Qualifier("qualifier")中的qualifier相同(如上面的“main”),bean才能被成功注入。 默认情况下,bean的名称被视为默认的限定符值,也可以把bean的id属性的值作为限定符值,如@Bean(id="main"),此时bean的限定符值就是main了,与bean的名称无关。

限定符还适用于类型化的集合,如前面所述(例如,应用于Set )。 在这种情况下,根据声明的限定词,所有匹配的bean都作为一个集合注入。 这意味着限定词不必是唯一的。 相反,它们构成了过滤标准。 例如,可以使用相同的限定符值“ action”定义多个MovieCatalog Bean,所有这些bean都注会入到带@Qualifier("action")注解的Set 中。

在类型匹配的注入中(@Autonwired就是按类型匹配),会默认根据目标Bean的名称进行选择,在注入点是不需要@Qualifier注释的。 如果没有其他解析指示符(例如qualifier 或 primary marker ),对于非唯一依赖的情况,Spring会将注入点名称(即字段名称或参数名称)与目标Bean名称进行匹配,然后选择同名的候选bean(如果有)。

5.@Resource

Spring还支持 通过@Resource注解(javax.annotation.Resource包)在字段或bean属性Setter方法上注入。

@Resource注解具有name属性。 默认情况下,Spring将该值解释为要注入的Bean名称。 如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;
	
    @Resource(name="myMovieFinder")// bean名称为myMovieFinder的bean会被注入
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果未明确指定name,则默认name是从字段名称或setter方法派生的。 如果是字段,则采用字段名称。 在使用Setter方法的情况下,它采用bean属性名称。以下示例将把名为movieFinder的bean注入其setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果@Resource不指定显式名称,在这种特殊情况下,@Resource按名称未找到bean后,会类似于@Autowired,查找类型匹配的bean。因此,在以下示例中,customerPreferenceDao字段首先查找名为“ customerPreferenceDao”的bean,没找到的话,转而查找类型为CustomerPreferenceDao的类型匹配项:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;
    
    // ...
}

6.@Value

@Value通常用于注入外部属性。

  • 常用在加载配置文件(如application.properties、application.yml)里的配置项。

    举个栗子,现在有一个application.properties配置文件,如下:

    catalog.name=MovieCatalog
    

    现在要加载catalog.name属性到MovieRecommender类的catalog属性上,首先MovieRecommender类上需要加一个@Component注解,用@Value("${PROPERTITYNAME}")来加载配置项,具体写法如下:

    @Component
    public class MovieRecommender {
    
        private final String catalog;
        
    	// 通过注入,catalog参数、字段会等于MovieCatalog值
        public MovieRecommender(@Value("${catalog.name}") String catalog) {
            this.catalog = catalog;
        }
    }
    
  • 用在bean属性、系统属性、Spel表达式注入,写法:@Value("#{}")

7.@PostConstruct@PreDestroy

@PostConstruct@PreDestroy注解提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方法。 在以下示例中,缓存在初始化时预先填充,并在销毁时清除:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

需要特别注意@Autowired、类构造方法和@PostConstruct三者的加载顺序。正确的顺序是:类构造方法最先执行,然后是@Autowired进行对象的自动装配,最后才是PostConstruct注解执行。

举个栗子,先创建好一个String类型的值为"This is an AutoWire Message"的bean:

@Component
public class Test {
    @Autowired
    private String message;

    @PostConstruct
    public void postConstruct() {
        System.out.println("执行postConstruct方法");
        System.out.println("此时message属性值为:" + message);
    }

    Test() {
        System.out.println("执行构造方法");
        System.out.println("此时message属性值为:" + message);
    }
}

打印结果如下:
mark

参考资料

[1]:Spring的IOC原理–牧涛

[2]:SpringFrameworkReference

posted on 2020-02-20 10:27  JavaCoder567  阅读(179)  评论(0编辑  收藏  举报

导航