关于Bean的注入

  在上一篇中,已经说到虽然注入确实可以降低类与类之间的耦合,但并没有解决调用者必须知道类的创建方法的问题,也可以说是没有实现调用者与类实现的解耦,我们也提到,为了实现两者的解耦,可以采取工厂模式,而事实上Spring也是这么做的,因此接下来我们就了解一下在Spring中Bean是如何被注入到对应实体类中的。

  Spring提供了两种类型来实现Bean容器,一个是bean工厂,另一个是应用上下文,其中bean工厂对大多数应用而言太低级,所以直接讨论如何使用应用上下文来获取Bean对象。

  Spring自带了多种类型的应用上下文,其中有:

    ① AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;

    ② ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;

    ③ FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件;

    ④ AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式;

    ⑤ XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

  这里我还没有看到Web应用,所以就只看一下前两个(第三个与第二个一样,只不过是变成从任意位置而不单单是从类路径下查找了),他们分别代表了两种不同的Bean装配方式:Java中装配和xml中装配,当然装配还有第三种方式:自动化装配,这也是最为推荐的一种方式。

  接下来,就先从Java中装配开始看起吧。

  我是使用Maven来构建的Spring项目,下面是需要引入的依赖

    <!-- 定义maven变量 -->
<properties>
<!-- spring -->
<spring.version>5.1.4.RELEASE</spring.version>
</properties> <dependencies>
<!-- Spring IOC 核心容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency> <!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency> <!-- Spring AOP 切面 模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency> <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>

一、Java中装配

  对于可传入构造器的对象类型,共有:int、float之类的基本类型,自定义类型、列表集合等类型,我们先从无参构造看起:

    ①无参构造

   先定义一个接口

public interface Animal {
public void eat();
}

    其次,我们定义其实现类:

public class Panda implements Animal {
@Override
public void eat() {
System.out.println("熊猫吃竹子");
}
}

    在使用java配置的时候,需要写一个配置类:

    配置类其实跟普通的类并没有什么区别,只不过需要加一些注释。先看一下配置类的书写,再对里面的注解一一理解:

    在同包下定义一个Java类,取名为ContextTestConfig:

@Configuration
public class ContextTestConfig {
@Bean
public Animal Panda(){
return new Panda();
}
}

    这样就算完成了一个配置类,其中有两个注解:@Configuration和@Bean

    @Configuration:表明这是一个配置类,在上面写的几种应用上下文类型中,有一种类型为:AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;当选用的应用上下文用这个类来生成的时候,就要传入对应的配置类来作为参数,从而使获得的应用上下文可以得到此配置类所配置的Bean对象。加上注解后变为配置类,可以启动组件扫描,用来将带有@Bean的实体进行实例化bean等。

    @Bean:表示要创建一个Bean对象,并将该对象交给Spring管理。看一下@Bean的源码,里面有这样的属性:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {}; @AliasFor("value")
String[] name() default {}; /** @deprecated */
@Deprecated
Autowire autowire() default Autowire.NO; boolean autowireCandidate() default true; String initMethod() default ""; String destroyMethod() default "(inferred)";
}

    其中,现在注意的是里面的name和value属性,他们是一个效果,都是表示给该Bean对象起一个别名,如果不写,那么默认Bean的id就是函数名。

    现在,配置已经完成了,来使用测试方法来看看装配有没有成功,新建一个ContextJavaTest类,代码如下:

public class ContextJavaTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(com.xiaoxin.ContextTest.ContextTestConfig.class);
Animal panda = applicationContext.getBean("Panda",Panda.class);
panda.eat();
}
}

    输出正常,为“熊猫吃竹子”。

    我们再来试一试对应的name属性:现在我在配置类中的代码给@Bean给上name属性,再次测试:

//Panda类修改后的代码
@Bean("Panda02")
public Animal Panda(){
return new Panda();
}

    发现报错了,错误为:

    也就是没有找到一个叫"Panda"的Bean对象,因为此时在配置类中,Panda对象已经改名为Panda02了,当把ContextJavaTest中名字改为Panda02后,又可以找到对应的Bean对象,正常执行。

    这是无参的情况,若是有参数的情况下,在Java中配置其实也是很方便的:

   ②含参构造和Set方法

    现在假设有Person这样一个接口和Man的实现类,对应代码为:

public interface Person {
public void give();
public void playWithAnimal();
}
public class Man implements Person {
Animal animal;
public Man(Animal animal) {
this.animal = animal;
}
@Override
public void give() {
System.out.println("给动物食物");
}
@Override
public void playWithAnimal() {
give();
animal.eat();
}
}

    现在,Man对象在创建的时候,必须要传入一个Animal的对象,有了实现类和创建逻辑以后,我们看配置类如何配置:

@Bean("Man")
public Person Man(){
return new Man(new Panda());
}

    只需要这样配置,就可以在测试代码中直接获得对应的类了。

    如果不是用构造器传入,而是用set方法传入,是一样的:

Animal animal;
public Man(){} public void setAnimal(Animal animal) {
this.animal = animal;
}

    那么只需在配置类里,用创建对象的方法写上即可,如果是列表之类的,也是同样的办法,这也是Java配置要比xml配置好的地方,配置类其实跟创建对象是一样的,只是加注释来表明是一个配置类而已。

二、xml装配

  这种方法就是直接在xml中配置对应的bean对象。

    在resources下创建xml文件夹,内放ContextTest.xml

   文件结构如图:

    在创建好文件结构后,就可以开始写xml文件了

    在一开始,xml基本的样子是:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<beans>

    如果要使用无参构造的话,直接加入一个Bean标签即可:

<bean id="Panda" class="com.xiaoxin.ContextTest.Panda"/>

    如果使用含参构造函数的话,可以使用<constructor-arg>标签来注入对应属性

<bean id="Man" class="com.xiaoxin.ContextTest.Man">
<constructor-arg name="animal" ref="Panda"></constructor-arg>
</bean>

    如果注入的是对象的话,那么需要使用ref来引入,当然,引入的对象也必须是在xml中定义的bean。

    如果注入只是字面值的话,可以使用value,比如给Man注入一个Name属性: 

<bean id="Man" class="com.xiaoxin.ContextTest.Man">
<constructor-arg name="animal" ref="Panda"></constructor-arg>
<constructor-arg name= "name" value="小新"></constructor-arg>
</bean>

     这些都是直接使用构造器构造,也可以使用set方法来注入属性:

<bean id="Man2" class="com.xiaoxin.ContextTest.Man">
<property name="animal" ref="Panda"></property>
<property name="name" value="小新"></property>
</bean>

    其实跟上面没啥区别,只不过把<constructor-arg>标签换成了<property>标签。

    如果还需要使用列表等注入,里面有专门的<list>、<set>等标签来使用:

    现在创建一个Woman类:

public class Woman implements Person {
List<Animal> animals;
List<String> strs;
public void setStrs(List<String> strs) {
this.strs = strs;
}
public void setAnimals(List<Animal> animals) {
this.animals = animals;
}
@Override
public void give() {
}
@Override
public void playWithAnimal() {
}
}

    在这个类中,有String类型的列表,也有自定义对象类型的列表,那么注入方法为:

<bean id="Woman" class="com.xiaoxin.ContextTest.Woman">
<property name="animals">
<list>
<ref bean="Panda"/>
</list>
</property>
<property name="strs">
<list>
<value>一盒</value>
</list>
</property>
</bean>

    如果是构造器实现注入,也只需要把标签改一下即可。

三、注解装配

  注解装配是最为推荐的类型,配置起来也很方便。

    使用注解装配需要几个方面:

    ①在创建的实体类上加@Component注解,在@Component上,可以用name属性来设定id,如果没有的话,默认为类名首字母小写为这个bean对象的id。

    ②在需要注入的属性上加@Autowired,这个会首先进行类型匹配,如果只有一个类型的话,那可以直接匹配,但如果有多个类型的类,那么spring会不知道注入哪一个,因此如果所依赖的接口有多个实现类的话,必须有另外的方式来匹配。

   ③需要配置扫描包,有两种方式:在applicationContext.xml中进行配置,或者直接在对应类包上使用@ComponentScan注解。

    现在有以下的几个类:

    定义一个Animal_2接口:

public interface Animal_2 {
public void doSomething();
}

   对应的有一个Ear类:

@Component
public class Ear {
public void move(){
System.out.println("耳朵动");
}
}

    还有一个Animal_2的实现类:

@Component
public class Cat implements Animal_2{
private Ear ear;
private String name;
@Autowired
public void setEar(Ear ear) {
this.ear = ear;
}
@Value("小黄")
public void setName(String name) {
this.name = name;
}
public void doSomething(){
ear.move();
System.out.println(name+"猫猫");
}
}

   这些类都有@Component注解,表示这是一个组件,当扫描的时候,可以作为Bean对象被扫描到。

   对应的还有@Service、@Mapper、@Controller等,他们其实跟@Component的功能是一样的,只是分成那么多以后,可以清楚地分清楚层次结构。

  

     在这几个类中,有两个注解:一个是@Autowired,另一个是@Value。

     @Autowired注解对应的是自动注入Bean对象,推荐使用在set方法上,而不是对象名上。

     而@Value也是用于注入的,它所注入的是基本类型的值。

    在配置扫描包的时候,有两种方法:

    第一个是基于xml文件来进行配置,在applicationContext.xml中配置:

<context:component-scan base-package="com.xiaoxin.ContextTest"></context:component-scan>

    其中base-package表示要扫描的包

    第二种方法是使用注解来配置,在对应的包下面建立类:Config

@ComponentScan
public class Config {
}

  使用这个代码,会自动扫描该类所在包下的所有组件。

  下面我们进行测试:

  测试使用junit进行测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
// locations = {"classpath:applicationContext.xml"}
classes = com.xiaoxin.ContextTest.Config.class
)
public class ContextAnnotationTest {
@Autowired
private Animal_2 animal;
@Test
public void name() {
animal.doSomething();
}
}

  其中@Runwith和@ContextConfiguration经常在一起使用,@ContextConfiguration里面有两个类型,分别表示使用xml和使用注解设置包扫描的两种配置方法。

  但如果有多个Animal_2的实现类的时候,如何自动装配呢?

  这里假设我们还有一个Animal_2的实现类:

@Component
public class Dog implements Animal_2 {
@Override
public void doSomething() {
System.out.println("我是狗狗");
}
}

  那么这个时候,如果再次运行测试代码,发现无法正常运行:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.xiaoxin.ContextTest.ContextAnnotationTest':
Unsatisfied dependency expressed through field 'animal'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.xiaoxin.ContextTest.Animal_2' available: expected single matching bean but found : cat,dog

  大体意思是说,没有找到合适的bean对象,她所需要的Animal_2有两个已经注册的Bean对象,他不知道该选用哪一个。

  此时的解决方法就是:

  1、在@Autowired注解下,再添加@Qualifier(value = "xx")的注解,来显式表明使用的是哪个bean对象。

  上面也提到了,每个Bean对象,如果没有特别写明名称的话,它的id就是类名且第一个字母小写。

  因此在这里,Cat类的Bean对象,id就是cat,Dog类的Bean对象,id就是dog。

  也可以修改对应Bean对象的id,在@Component(Value = "xx"),可以自己定义对应Bean对象的ID。

  2、上面的方法如果只是使用@Qualifier而不对Bean修改ID的话,会存在一个问题:如果对应的类名发生改变,那么对应的@Qualifier里的内容也需要调整,否则会找不到Bean对象,解决方法是在@Component上赋值,或者也可以为Bean对象添加@Qualifier注解,表明其限定符。  

  3、在自己最需要的类上加@Primary注解,表示这个默认的首选Bean

  

  这样使用注解装配,确实很方便,但可以发现一个问题,就是我们写的类只有一个,如果使用xml或者Java装配的话,我们可以创建很多同一类的对象,只要附上不同的ID即可,但在注解装配中,我们只能配置一个,这就会使得有的时候不能满足我们的要求,因此也需要部分使用xml或Java配置。

  还有一种情况,就是假设我们有多个xml文件和多个java配置类,我们如何将他们合在一起,共同去配置装载Bean对象呢?

  对于这个问题,要认清楚就是,不管是三种装配方式的哪一种,他们实质上都是注册Bean对象的方法,也就是说三种方法,不管怎么配置,只要被扫描到了,就都可以有效。

  再回顾使用注解自动装配,我们可以发现,所谓的自动装配,并不是全自动的,使用注解完成了Bean对象的配置以后,我们还有一个任务就是配置扫描包:而扫描包的配置并不是自动的,也是需要使用xml或者java配置类的注解来完成的,而在使用的时候,也是需要配置对应的xml文件或者配置类,才能扫描到对应的Bean对象

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
// locations = {"classpath:applicationContext.xml"}
classes = com.xiaoxin.ContextTest.Config.class
)

  这样,应该可以明白了:其实怎么配置Bean对象并不重要,哪种方法简单哪种方法就可以使用,他们都是相同的作用,即把Bean对象注册到容器以供使用,而对于上面所说的三种配置方法同时使用的问题,其本质其实变成了怎么让多个配置xml文件或者Java类,能够都被扫描到。

  一、在JavaConfig配置类中引入xml配置

    现在新建一个包,不妨取名为ContextTest02,里面有这两个类:

public class Fish {
private String name;
public Fish(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

    有鱼类,还有猫类,其中猫类中有一个鱼类对象:

public class Cat {
Fish fish ;
public Cat(Fish fish) {
this.fish = fish;
}
public void eat(){
System.out.println("猫猫吃"+fish.getName());
}
}

    定义好这两个类后,就可以开始测试了:

    我们先看看多个配置类怎么合并在一起扫描:

    在Config01中,我们定义一个鱼的Bean对象,并赋ID为"fish01":

@Configuration
public class Config01 {
@Bean(name = "fish01")
public Fish GetFish(){
return new Fish("黄花鱼");
}
}

    在Config02中,我们定义一个猫的Bean对象,并且传入fish01:

@Configuration
public class Config02 {
@Bean("cat01")
public Cat GetCat(Fish fish01){
return new Cat(fish01);
}
}

    现在这样的状况下,还没有进行包扫描,在Config02中并没有查找到对应的fish01,因此下一步我们要建立两者的关联:

    现在不妨先测试一下,到底是否可以直接执行:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = Config02.class
)
public class TestAll {
@Autowired
@Qualifier("cat01")
private Cat cat; @Test
public void test(){
cat.eat();
}
}

    在测试中,因为cat01对象是在Config02包下的 所以直接扫描02包试一下:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'cat01' defined in com.xiaoxin.ContextTest02.Config02:
Unsatisfied dependency expressed through method 'GetCat' parameter 0;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.xiaoxin.ContextTest02.Fish' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

    使用失败,原因是没有找到Cat01Bean对象所依赖的Fish对象,这是因为Config01没有被扫描到,它所设置的Fish对象没有被创建。

    解决方法:在Config02包中,引入Config01配置类:

@Import(Config01.class)

    再次运行,发现打印出结果了。

猫猫吃黄花鱼

    这样确实解决了问题,但仔细想想会发现这样很繁琐,每一个依赖的需要单独导入,如果Fish对象被好几个配置类依赖,那就要写好几次,因此我们可以改进代码,创建一个更高级的配置类,它只负责把所有的配置文件、配置类合并在一起,统一扫描:

@Configuration
@Import({Config02.class,Config01.class})
public class ConfigAll {
}

    这样配置以后,所有的配置类都可以往里面添加,扫描的时候,只需要扫描这个总的配置类,就可以了,在测试代码中,配置的配置类改为:

@ContextConfiguration(
classes = ConfigAll.class
)

    这样实现了Java配置类之间的合并,而对于xml文件,也是一样,可以使用注解:@ImportResource

@Configuration
@Import({Config02.class,Config01.class})
@ImportResource("classpath:xml/ConfigXml01.xml")
public class ConfigAll {
}

    在上图的文件结构上,在xml文件夹下配置对应的xml配置文件,导入即可。

    二、使用xml文件导入:

    也是一样的,推荐建立一个总的文件,只是用来引入合并:

    使用<bean>来引入Java配置类

    使用<import>引入xml配置文件

<bean class="com.xiaoxin.ContextTest02.Config01"></bean>
<bean class="com.xiaoxin.ContextTest02.Config02"></bean>
<import resource="ConfigXml01.xml"></import>

    在测试代码上,导入的配置改为xml文件:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
// classes = ConfigAll.class
locations = {"classpath:xml/ConfigXmlAll.xml"}
)

    即可

Spring初学笔记(二):Bean的注入的更多相关文章

  1. Spring学习笔记(3)——Bean的注入方式

    依赖注入 依赖注入支持属性注入.构造函数注入.工厂注入. 属性注入: 属性注入即通过setXxx()方法注入Bean的属性值或依赖对象 属性注入要求Bean提供一个默认的构造函数(无参构造函数),并为 ...

  2. spring学习笔记之---bean属性注入

    bean属性注入 (一)构造方法的属性注入 1.Student.java package entity; public class Student { private String name; pri ...

  3. Spring 初学笔记

    Spring 初学笔记: https://blog.csdn.net/weixin_35909255/article/category/7470388

  4. Spring学习笔记(二)之装配Bean

    一,介绍Bean的装配机制 在Spring中,容器负责对象的创建并通过DI来协调对象之间的关系.但是我们要告诉Spring创建哪些Bean并且如何将其装配在一起.,装配wiring就是DI依赖注入的本 ...

  5. Spring学习笔记二:注入方式

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6774608.html  我们说,IOC的实现方式是依赖注入,也就是把被依赖对象赋值到依赖对象的成员属性.怎么做 ...

  6. Spring框架系列(二)--装配和注入Bean

    企业日常开发中,几乎都是Spring系的框架,无论是SSM.还是现在大火的SpringBoot+JPA/MyBatis,使用最大的目的就是简化开发 基本模块: 核心容器:Beans.Core.Cont ...

  7. Spring学习笔记—装配Bean

    在Spring中,对象无需自己负责查找或创建与其关联的其他对象.相反,容器负责把需要相互协作的对象引用赋予各个对象.创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入的本质. ...

  8. spring实战一:装配bean之注入Bean属性

    内容参考自spring in action一书. 创建应用对象之间协作关系的行为通常称为装配,这也是依赖注入的本质. 1. 创建spring配置 spring是一个基于容器的框架.如果没有配置spri ...

  9. spring学习(二)---依赖注入

    spring第二个特性是依赖注入. 学习依赖注入,首先应该明白两个问题:1,谁依赖谁:2,谁注入,注入什么? 首先还是看代码: 还是这个bean: package testSpring.busines ...

随机推荐

  1. Caused by: java.lang.ClassCastException: class java.lang.Double cannot be cast to class org.apache.hadoop.io.WritableComparable

    错误: Caused by: java.lang.ClassCastException: class java.lang.Double cannot be cast to class org.apac ...

  2. shell脚本:备份数据库、代码上线

    备份MySQL数据库场景:一台MySQL服务器,跑着5个数据库,在没有做主从的情况下,需要对这5个库进行备份 需求:1)每天备份一次,需要备份所有的库2)把备份数据存放到/data/backup/下3 ...

  3. Windows 挂起进程

    A thread can suspend and resume the execution of another thread. While a thread is suspended, it is ...

  4. 查看现有的 cipher suite

    openssl ciphers [-v] [-ssl2] [-ssl3] [-tls1] [cipherlist]

  5. scala教程之:可见性规则

    文章目录 public Protected private scoped private 和 scoped protected 和java很类似,scala也有自己的可见性规则,不同的是scala只有 ...

  6. Session服务器之Redis

    Session服务器之Redis Redis与Memcached的区别内存利用率:使用简单的key value (键值对)存储的话,Mermcached 的内存利用率更高,而如果Redis采用hash ...

  7. tensor的复制函数torch.repeat_interleave()

    1. repeat_interleave(self: Tensor, repeats: _int, dim: Optional[_int]=None) 参数说明: self: 传入的数据为tensor ...

  8. SaaS 公司如何切入大客户

    编者按:本文作者是氪空间第四期项目 Kuick 创始人崔超,其现在的产品KuickDeal是一款销售活动管理工具.本文来自作者投稿,36 氪经授权转载. 首先,今天我们不讨论 SaaS 公司应该做中小 ...

  9. 一个简易的SocketIM

    今天做了一个简易的socketIM的小示例.基本思想是开启两个winform,每个winform既充当服务器也充当客户端.一个监听8000端口,另外一个监听8001端口,两个winform接收到信息之 ...

  10. 信息奥赛一本通1486: CH 6202 黑暗城堡 最短路径生成树计数

    1486:黑暗城堡 [题目描述] 知道黑暗城堡有 N 个房间,M 条可以制造的双向通道,以及每条通道的长度. 城堡是树形的并且满足下面的条件: 设 Di为如果所有的通道都被修建,第 i 号房间与第 1 ...