vlambda博客
学习文章列表

分析 Spring 框架中的装配模式

每天凌晨00点00分, 第一时间与你相约


每日英文

You can hurt with your words but sometimes you can hurt more with your silence. 

你说的话可能会伤到别人,但有时候,你的沉默会让人伤得更深。


每日掏心

永远不要去羡慕别人的生活,即使那个人看起来快乐富足。永远不要去评价别人是否幸福,即使那个人看起来孤独无助。幸福如人饮水,冷暖自知。




来自:SpaceCat | 责编:乐乐

链接:jianshu.com/p/06873e32efb3

程序员小乐(ID:study_tech)第 741 次推文   图片来自 Pexels


往日回顾:



   正文   


Spring框架中,对于不同的bean进行组合实现复杂功能的过程称为装配。

1、装配模式介绍

在Spring中,支持5种装配模式:

  • no – 缺省情况下,自动配置是通过“ref”属性手动设定。

  • byType – 按数据类型自动装配。如果一个bean的数据类型是用其它bean属性的数据类型,兼容并自动装配它。

  • byName – 根据属性名称自动装配。如果一个bean的名称和其他bean属性的名称是一样的,将会自装配它。

  • constructor – 在构造函数参数的byType方式。

  • autodetect – 如果找到默认的构造函数,使用“自动装配用构造”; 否则,使用“按类型自动装配”。

接下来,我们通过如下的代码对这几种装配模式做下简单测试。代码结构如下:

分析 Spring 框架中的装配模式

structure of the project

代码如下:

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.SpaceCat.SpringHelloWorld</groupId>
    <artifactId>SpringHelloWorld</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring Core -->
        <!-- http://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.1.4.RELEASE</version>
        </dependency>
        <!-- Spring Context -->
        <!-- http://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.4.RELEASE</version>
        </dependency>
    </dependencies>
</project>
// Interface HelloWorld
public interface HelloWorld {
    public void sayHello();
}

.

// Class implements HelloWorld
public class SpringHelloWorld implements HelloWorld {
   public void sayHello()  {
           System.out.println("Spring say Hello!");
   }
}

// Other class implements HelloWorld
public class StrutsHelloWorld implements HelloWorld {
   public void sayHello()  {
           System.out.println("Struts say Hello!");
   }
}

public class HelloWorldService {
    private HelloWorld helloWorld;

    public HelloWorldService() {

    }

    public HelloWorldService(HelloWorld h) {
        this.helloWorld = h;
    }

    public void setHelloWorld(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    public HelloWorld getHelloWorld() {
        return this.helloWorld;
    }
}

HelloWorldProgram.java:

package com.SpaceCat.HelloWorld.spring;

import com.SpaceCat.HelloWorld.spring.helloworld.HelloWorld;
import com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by chengxia on 2019/4/30.
 */

public class HelloWorldProgram {
    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        HelloWorldService service = (HelloWorldService) context.getBean("helloWorldService");
        HelloWorld hw= service.getHelloWorld();
        hw.sayHello();
    }
}

在下面的例子中,我们通过修改beans.xml配置文集爱你,然后,运行HelloWorldProgram.java就可以对这几种装配模式进行举例说明。

1.1 no

缺省情况下,可以通过“ref”属性手动设定bean之间的关联关系。

<?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="springHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.SpringHelloWorld">
    </bean>
    <bean id="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService">

        <property name="helloWorld" ref="springHelloWorld"/>
    </bean>
</beans>

运行结果:

Spring Say Hello!!

Process finished with exit code 0

1.2 byType

按数据类型自动装配。如果一个bean的数据类型是用其它bean属性的数据类型,兼容并自动装配它。

接下来的例子中,我们只定义一个HelloWord类型的bean,然后,在定义bean时,通过autowire属性设置自动装配类型为byType。即可完成bean装配。

<?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="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService" autowire="byType">

    </bean>
</beans>

运行结果:

Struts Say Hello!!

Process finished with exit code 0

1.3 byName

根据属性名称自动装配。如果一个bean的名称和其他bean属性的名称是一样的,将会自装配它。

接下来,我们将一个bean的id设置为与HelloWorldService类的属性名(helloWorld)一致。这样,这个bean就会自动被装配到HelloWorldService。

<?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="helloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.SpringHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService" autowire="byName">

    </bean>
</beans>

运行结果:

Spring Say Hello!!

Process finished with exit code 0

1.4 constructor

在构造函数参数的byType方式。就是根据构造函数参数的类型进行自动装配,如果有一个bean的类型和HelloWorldService类的构造函数类型一样,就会被自动装配过去。

<?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="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService" autowire="constructor">

    </bean>
</beans>

运行结果:

Struts Say Hello!!

Process finished with exit code 0

1.5 autodetect

这种有些资料写的有些歧义,下面是Stack Overflow上面的一个解释:

Autowiring by autodetect uses either of two modes i.e. constructor or byType modes. First it will try to look for valid constructor with arguments, If found the constructor mode is chosen. If there is no constructor defined in bean, or explicit default no-args constructor is present, the autowire byType mode is chosen.

意思就是:如果发现带参数的构造函数,就使用constructor模式进行装配;如果没有定义构造函数,或者是显式定义了没有参数的构造函数,就使用byType模式进行装配。

这里我测试用的环境中,spring的版本是4.1.4.RELEASE,在测试时IDEA提示报错,如下图:

分析 Spring 框架中的装配模式

autodetect error tips

可能在高版本的Spring框架中这种装配模式已经不推荐使用了。但是,运行时仍然可以正常运行,如下:

<?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="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService" autowire="autodetect">

    </bean>
</beans>

运行结果:

Struts Say Hello!!

Process finished with exit code 0

2、更加细粒度的装配控制

2.1 特定属性的自动装配

在前面装配模式介绍的例子中,如果一个bean配置了autowire属性,那么它的所有属性都会被自动装配。但是,有时候,我们只希望某些特定的属性配装配。这时候,就需要用到@Autowired注解。

在Spring框架中,可以使用@Autowired注解通过setter方法,构造函数或字段自动装配Bean。通过它,可以对某个特定的bean属性完成自动装配。@Autowired注解是通过匹配数据类型自动装配Bean的。

启用@Autowired需要注册“AutowiredAnnotationBeanPostProcessor',可以通过两种方式:

在beans.xml文件中,加入(包括xmlns:context="http://www.springframework.org/schema/context")

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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">

    <context:annotation-config />
    <bean id="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService">

    </bean>
</beans>

在beans.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 class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    <bean id="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService">

    </bean>
</beans>

这样,我们就可以通过将@Autowired注解用到bean的特定属性、setter方法或者构造函数等,来实现对自动装配更加细粒度的控制。


下面的例子中,三种实现的效果一样:

1. 用在属性上

package com.SpaceCat.HelloWorld.spring.helloworld;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by chengxia on 2019/4/30.
 */

public class HelloWorldService {
    @Autowired
    private HelloWorld helloWorld;

    public HelloWorldService() {

    }

    public HelloWorldService(HelloWorld h) {
        this.helloWorld = h;
    }

    public void setHelloWorld(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    public HelloWorld getHelloWorld() {
        return this.helloWorld;
    }
}

2. 用在构造函数上

package com.SpaceCat.HelloWorld.spring.helloworld;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by chengxia on 2019/4/30.
 */

public class HelloWorldService {
    private HelloWorld helloWorld;

    public HelloWorldService() {

    }

    @Autowired
    public HelloWorldService(HelloWorld h) {
        this.helloWorld = h;
    }

    public void setHelloWorld(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    public HelloWorld getHelloWorld() {
        return this.helloWorld;
    }
}

3. 在setter方法上

package com.SpaceCat.HelloWorld.spring.helloworld;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by chengxia on 2019/4/30.
 */

public class HelloWorldService {
    private HelloWorld helloWorld;

    public HelloWorldService() {

    }

    public HelloWorldService(HelloWorld h) {
        this.helloWorld = h;
    }

    @Autowired
    public void setHelloWorld(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    public HelloWorld getHelloWorld() {
        return this.helloWorld;
    }
}

运行结果如下:

Struts Say Hello!!

Process finished with exit code 0

这里需要注意的是,这个注解并不是只通过byType的方式进行自动装配。准确地说:

@Autowired注入首先根据byType注入,当类型大于1时在根据byName注入。

2.2 指定自动装配特定的bean

在上面介绍的装配模式中,如果有两个符合条件的bean,框架就会报错,如下:

package com.SpaceCat.HelloWorld.spring.helloworld;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by chengxia on 2019/4/30.
 */

public class HelloWorldService {
    private HelloWorld helloWorld;

    public HelloWorldService() {

    }

    public HelloWorldService(HelloWorld h) {
        this.helloWorld = h;
    }

    @Autowired
    public void setHelloWorld(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    public HelloWorld getHelloWorld() {
        return this.helloWorld;
    }
}

.

<?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 class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    <bean id="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>
    <bean id="springHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.SpringHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService">

    </bean>
</beans>

运行如下:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'helloWorldService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService.setHelloWorld(com.SpaceCat.HelloWorld.spring.helloworld.HelloWorld); nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.SpaceCat.HelloWorld.spring.helloworld.HelloWorld] is defined: expected single matching bean but found 2: strutsHelloWorld,springHelloWorld
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1202)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:762)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at com.SpaceCat.HelloWorld.spring.HelloWorldProgram.main(HelloWorldProgram.java:14)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService.setHelloWorld(com.SpaceCat.HelloWorld.spring.helloworld.HelloWorld); nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.SpaceCat.HelloWorld.spring.helloworld.HelloWorld] is defined: expected single matching bean but found 2: strutsHelloWorld,springHelloWorld
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:649)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
    ... 13 more
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.SpaceCat.HelloWorld.spring.helloworld.HelloWorld] is defined: expected single matching bean but found 2: strutsHelloWorld,springHelloWorld
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1061)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:949)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:606)
    ... 15 more
Process finished with exit code 1

这时候,可以通过@Quanlifier告诉Spring哪些bean应当自动装配。如下:

package com.SpaceCat.HelloWorld.spring.helloworld;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

/**
 * Created by chengxia on 2019/4/30.
 */

public class HelloWorldService {

    @Autowired
    @Qualifier(value = "strutsHelloWorld")
    private HelloWorld helloWorld;

    public HelloWorldService() {

    }

    public HelloWorldService(HelloWorld h) {
        this.helloWorld = h;
    }

    public void setHelloWorld(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    public HelloWorld getHelloWorld() {
        return this.helloWorld;
    }
}

注意,这里其实要同时修改beans.xml文件,否则,@Qualifier注解不起作用(应该主要是为了添加)。如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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">

    <context:annotation-config />
    <bean id="strutsHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.StrutsHelloWorld">
    </bean>
    <bean id="springHelloWorld" class="com.SpaceCat.HelloWorld.spring.helloworld.impl.SpringHelloWorld">
    </bean>

    <bean id="helloWorldService"
          class="com.SpaceCat.HelloWorld.spring.helloworld.HelloWorldService">

    </bean>
</beans>

运行结果:

Struts Say Hello!!

Process finished with exit code 0

参考资料

  • Spring Autowiring autodetect
    stackoverflow.com/questions/27312693/spring-autowiring-autodetect

  • Spring 4.1 @Qualifier doesnt'work
    stackoverflow.com/questions/26426694/spring-4-1-qualifier-doesntwork

  • Spring自动装配Beans
    yiibai.com/spring/spring-auto-wiring-beans-in-xml.html

  • 一篇文章告诉你spring中的@Autowired注入时到底是根据byType还是byName
    blog.csdn.net/weixin_40698359/article/details/86065031

分析 Spring 框架中的装配模式

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

猜你还想看






关注「程序员小乐」,收看更多精彩内容
嘿,你在看吗