Spring初学笔记(二):Bean的注入

关于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的实现类的时候,如何自动装配呢?(使用@Quariter)

  这样就算自动装配,也只能装配一个对应的类对象,如果要装配的对象是同一个类的多个呢,那要怎么变化?(xml/java与自动装配结合)

猜你喜欢

转载自www.cnblogs.com/xiaoxineryi/p/12322152.html
今日推荐