【Spring(二)】依赖注入 以及 bean的配置方式

依赖注入

依赖注入的目的是降低对象之间的耦合度。任何一个程序都是有很多对象协作完成任务的。不可能每一个对象都没有关联。我们希望对象之间的耦合方式越松越好(不是越少越好)。那么什么是紧耦合?比方说A依赖B,那么直接在A的构造器或者任何初始化方法里面构建B的方式就是紧耦合,因为A的依赖已经写死在A的代码里面,如果所有的依赖都按照这种方式来写,那么整个程序就不是松耦合的。这样的坏处在于,一旦我们要修改依赖关系,就得从各种代码里识别出依赖,然后修改替换,如果我们有成千上万个类,改起来是一项很大的工程,还容易出错。另外这样的模块很难测试,因为我们想测一个模块A,我们无法直接把A模块拿出来测试,我们需要把A的各种依赖构造好,这又是一个递归的过程。

那么依赖注入是如何做到松耦合的呢?

我们每一个类的依赖都不做直接的初始化,这样就消除了直接依赖关系,但是我们需要提供初始化依赖的方法或者接口,比如说构造器参数或者setter方法等等。这样每一个类仍然会依赖其他类,但是不再直接依赖。最后,我们再根据依赖关系手动把各个对象的依赖组装起来。这其实就分为了两个过程,一是定义二是组装。

在spring框架下,我们需要提供定义,然后spring帮我们组装依赖。

这样,对象与对象之间不再紧耦合,如果我们想修改依赖,只需要修改这个类的依赖和注入依赖的方法即可。如果我们想测试一个类,我们完全可以把这个类单独拿出来,然后mock它的直接依赖,注入即可。这就是依赖注入的优点。

配置概述

Spring配置bean的方式按照配置文件来说只有两种,分别是基于java文件和基于xml文件。这两种模式又分别支持自动扫描和装配,所以可以看成是三种方式。

如何选择?

自动配置是最方便的,能用就用。但是某一些场景无法使用,比如引入第三方的依赖。这是就只能走配置文件方式了。xml配置文件时spring早期的主要方式,但是现在已经不推荐使用了。相比较于java配置,有如下缺点:

1.配置复杂,需要有xml文件头部,需要有各种xml标签(集合);

2.基于string配置,无法享受到java config在编译器的检查。比如我们的类名可能填错,但是编译器无能为力。

 

下面就以一个简单的CD机的场景作为例子来示范下这三种方式。

Player与CD之间通过接口耦合。

public interface CD {
    void sound();
}

1.xml

我们需要在一个或者多个xml文件中定义bean。

xml文件建议从spring指定位置下载(https://spring.io/tools/sts)或者借助ide模板生成,

<?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.xsd">

    <bean id = "cd3" class="spring.xml.config.CD3"></bean>
    <bean id = "player3" class="spring.xml.config.Player3">
        <constructor-arg ref="cd3"></constructor-arg>
    </bean>
    <bean id = "player4" class="spring.xml.config.Player3">
        <property name="CD" ref="cd3"></property>
    </bean>
</beans>

每一个bean通过<bean>定义。简单的只要指定下class全限定类名。如果有依赖的可以通过构造器和setter方法注入。由于xml用的不多,其他xml配置这里不做深入介绍了。

测试:

这里使用classpathApplication上下文,默认加载类路径下的xml配置文件。

public class Main {
    public static void main(String args[]) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Player3 player3 = (Player3) applicationContext.getBean("player3");
        player3.play();

        Player3 player4 = (Player3) applicationContext.getBean("player4");
        player4.play();
    }
}

cacaca
cacaca

Process finished with exit code 0

 

2.java config

其实和xml的原理一致。只是xml方式bean被定义在xml文件中,而java只是将bean定义安排在一个java类文件中,换汤不换药。

承载bean定义的java文件本质上与其他java文件没有任何区别,只是我们需要一些spring的标签来标识它,这样spring就知道从这个文件中加载bean。

首先定义player和cd类。

public class CD2 implements CD {

    public void sound() {
        System.out.println("bababa");
    }
}
public class Player2 {

    private CD cd;

    public void play(){
        cd.sound();
    }

    public void setCD(CD cd){
        this.cd = cd;
    }
}

然后写配置类,并且需要使用@Configuration标签标识。

@Configuration
public class JavaConfig {
    @Bean
    public CD2 getCD2(){
        return new CD2();
    }

    @Bean(name = "player2")
    public Player2 getPlayer2(CD cd){
        Player2 player2 = new Player2();
        player2.setCD(cd);
        return player2;
    }
}

先标识了一个简单的CD java bean,内部没有任何属性。然后是Player java bean,内部引用指向一个CD实例,我们通过方法的参数来让spring注入依赖。

最后测试下,上下文选择AnnotationConfigApplicationContext,传入配置类的类实例来告诉spring配置类是哪一个。

public class Main {
    public static void main(String args[]) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
        Player2 player2 = (Player2) applicationContext.getBean("player2");
        player2.play();
    }
}

输出:

bababa

Process finished with exit code 0

 

一般建议使用java config方式,原因是xml完全基于string,而java config则可以提供类型检查,更加安全。

 

3.自动化

可以基于xml也可以基于java config。

不论哪一种都包括两部分,定义bean和装配bean。

3.1 定义bean

需要使用@Component注解。

@Component
public class CD1 implements CD{
    public void sound() {
        System.out.println("lalal");
    }
}

 

@Component
public class Player {

    @Autowired
    private CD cd;

    public void play(){
        cd.sound();
    }
}

 

可以看到,这里如果要定义一个bean,需要在POJO类上使用@Component注解,spring就会自动识别出这个类的实例要被spring容器管理。

3.2 装配bean

需要使用@Autowired注解。表示某一个类的实例需要spring容器来注入。比方说上面的Player实例CD,就带有@Autowired注解,这个实例变量最终会被赋上值。

 

除此之外,我们还需要告诉spring我们使用的是自动装配的方式配置bean,因为默认情况下,是不开启自动装配的。

如果使用的是xml方式,我们需要在xml配置文件中使用如下标签启用扫描:

<context:component-scan base-package="xxx.xxx......" />

xxx部分为要被扫描的包名。

如果使用的是java config方式,我们需要在配置类即被@congfiguration标记的来上添加@ComponentScan注解来显式开始自动装配。

@Configuration
@ComponentScan
public class M {
    public static void main(String args[]){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(M.class);
        Player p = (Player) applicationContext.getBean("player");
        p.play();
    }
}

结果:

lalal

Process finished with exit code 0
 

@ComponentScan默认扫描被标记类所在包下的bean。比方说我们把CD1类移出M.class所在的包,再运行就会报错:

No qualifying bean of type [spring.test.CD] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.

因为CD1这个bean没有被扫描到,所以无法注入依赖。

当然我们可以指定扫描包的位置,使用@Component(“xxx”)。

 

4. bean定义导入

大型项目,我们如果把所有bean定义放在一个配置文件中,这个文件会太大,难以维护,我们可以拆分配置文件。

通常的做法呢,是定义一个顶级的配置文件,然后由它来聚合所有的模块配置文件。

4.1 xml方式

还是上面的例子,我们把cd和player的定义拆到两个文件内,然后定义一个顶层confi.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.xsd">

    <import resource="bean1.xml"></import>
    <import resource="bean2.xml"></import>
</beans>

该文件通过import标签导入了其他xml文件。

bean1.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.xsd">

    <bean id = "cd3" class="spring.xml.config.CD3"></bean>
</beans>

bean2.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id = "player3" class="spring.xml.config.Player3">
        <constructor-arg ref="cd3"></constructor-arg>
    </bean>
    <bean id = "player4" class="spring.xml.config.Player3">
        <property name="CD" ref="cd3"></property>
    </bean>
</beans>

4.2 java config方式

@Configuration
@Import({Bean1.class, Bean2.class})
public class JavaConfig {

}
public class Bean1 {
    @Bean
    public CD2 getCD2(){
        return new CD2();
    }

}
public class Bean2 {
    @Bean(name = "player2")
    public Player2 getPlayer2(CD cd){
        Player2 player2 = new Player2();
        player2.setCD(cd);
        return player2;
    }
}

5.条件化bean

使用的是@Conditional标签。该标签需要传入一个org.springframework.context.annotation.Condition接口的实现类,其中的match方法负责返回是否加载该bean。

我们可以在match方法内部根据环境变量等条件来决定是否加载。

举一个例子,根据环境变量是否加载。

public class Need implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        return environment.containsProperty("need");
    }
}
@Component
@Conditional(Need.class)
public class Service {

}
@Configuration
@ComponentScan
public class JavaConfig {

    public static void main(String args[]) {
        ApplicationContext app = new AnnotationConfigApplicationContext(JavaConfig.class);
        Service service = (Service) app.getBean("service");
        System.out.println(service);
    }
}

在idea中配置环境变量need。

最后测试下:

spring.condition.Service@543c6f6d

Process finished with exit code 0

说明加载到。

如果去掉,则报错:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'service' is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:638)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1159)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:282)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:973)
    at spring.condition.JavaConfig.main(JavaConfig.java:14)

Process finished with exit code 1

 

6.自动装配下bean的歧义性

我们使用@Autowired自动装配时,是按照类型装配的,如果上下文中有多个类型匹配的bean,那么spring将不知道用哪一个,会报错。

例子:

public interface Bean {
}
@Component
public class TypeA implements Bean {
}
@Component
public class TypeB implements Bean {
}
@Component
public class Service {

    @Autowired
    Bean bean;

}
@Configuration
@ComponentScan
public class JavaConfig {

    public static void main(String args[]) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
        System.out.println(applicationContext.getBean("service"));
    }
}

运行会报错:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [spring.qiyi.Bean] is defined: expected single matching bean but found 2: typeA,typeB

有两个实现,但是期望单一的。

这时有两种解决方案。

6.1 通过@Pimary

该标签表明 如果有歧义,首选被该标签标注的。

6.2 通过@Qualifier

指定要装配的bean的id

 

7.bean的作用域

Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

单例(Singleton):在整个应用中,只创建bean的一个实例。默认是单例。

原型(Prototype):每次注入或者通过Spring应用上下文获取的

时候,都会创建一个新的bean实例。

会话(Session):在Web应用中,为每个会话创建一个bean实例。

请求(Rquest):在Web应用中,为每个请求创建一个bean实例。

前两种只需要通过@Scope标签指定。或两者需要额外的属性proxyMode。

因为在单例的bean引用session或者request的bean时,会有问题。

首先,单例的bean在最开始初始化时,被依赖的bean还没有生成,因为还没有请求,故无法注入;

其次,即便可以被注入,也需要注入多次来对应不同的session或者request。所以这里实际上会注入一个proxy实例,当处理具体session或者request的时候,再懒加载被依赖bean实例。proxyMode用于指定代理的类型,是基于class还是interface。

 

 

最后附上pom依赖:

<properties>
      <junit.version>4.12</junit.version>
      <spring.version>4.3.9.RELEASE</spring.version>
  </properties>

    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- 添加Spring依赖的jar包-->
        <!--依赖注入包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--切片包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Beans包-->
        <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-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- 核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- 表达式包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--spring-framework-bom包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--spring-instrument包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--连接数据库包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--Spring消息包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--Spring信息包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--Spring对象映射包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--spring-oxm包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--Spring测试包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--Spring事物管理包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--Spring文本项目包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--SpringMVC包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--spring-websocket包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${spring.version}</version>
        </dependency>

    </dependencies>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值