作者:陈本布衣

    

    

本文版权归作者和博客园共有,欢迎转载分享,但必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
博文目录

正文

  自动化装配的确有很大的便利性,但是却并不能适用在所有的应用场景,比如需要装配的组件类不是由自己的应用程序维护,而是引用了第三方的类库,这个时候自动装配便无法实现,Spring对此也提供了相应的解决方案,那就是通过显示的装配机制——Java配置和XML配置的方式来实现bean的装配。

 

Java配置类装配bean

  我们还是借助上篇博文中的老司机开车的示例来讲解。Car接口中有开车的drive方法,该接口有两个实现——QQCar和BenzCar

package spring.impl;

import spring.facade.Car;

public class QQCar implements Car {
@Override
public void drive() {
System.out.println("开QQ车");
}
}

  既然是通过Java代码来装配bean,那就是不是我们上一篇讲的通过组件扫描的方式来发现应用程序中的bean的自动装配机制了,而是需要我们自己通过配置类来声明我们的bean。我们先通过@Configuration注解来创建一个Spring的配置类,该类中包含了bean的创建细节——

import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.QQCar; /**
* @Configuration 表明该类是Spring的一个配置类,该类中会包含应用上下文创建bean的具体细节
* @Bean 告诉Spring该方法会返回一个要注册成为应用上下文中的bean的对象
*/
@Configuration
public class CarConfig { @Bean
public Car laoSiJi() {
return new QQCar();
}
}

  以上类中创建的bean实例默认情况下和方法名是一样的,我们也可以通过@Bean注解的name属性自定义ID,例如@Bean(name = "chenbenbuyi") ,那么在获取bean的时候根据你自己定义的ID获取即可。接着我们测试

package spring.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import spring.config.CarConfig;
import spring.facade.Car; public class CarTest {
@Test
public void carTest() {
ApplicationContext context = new AnnotationConfigApplicationContext(CarConfig.class);
//根据ID从容器容获取bean
Car car = (Car) context.getBean("chenbenbuyi");
car.drive();
}
}

  

  以上测试能够成功输出,这就表明我们能够获取到QQCar的实例对象的,而这也是最简单的基于Java配置类来装配bean的示例了。但是你可能会说,明明是我们自己创建的Car的实例,怎么就成了Spring为我们创建的呢?好吧,我们把@Bean注解拿开,测试当然是无法通过,会抛NoSuchBeanDefinitionException异常。这里,你可能需要好好理解控制反转的思想了:因为现在对于bean创建的控制权我们是交给了Spring容器的,如果没有@Bean注解,方法就只是一个普通方法,方法体返回的实例对象就不会注册到应用上下文(容器)中,也就说,Spring不会为我们管理该方法返回的实例对象,当我们在测试类中向容器伸手要对象的时候,自然就找不到。

  上述示例过于简单,现在,我们要更进一步,给简单的对象添加依赖,来完成稍微复杂一点的业务逻辑。车是需要老司机来开的,于是我们同上篇一样定义一个Man类,Man的工作就是开车

package spring.impl;

import spring.facade.Car;

public class Man {

    private Car car;public Man(Car car) {
this.car = car;
} public void work() {
car.drive();
}
}

  Car的对象实例是通过构造器注入,而Car的实例对象在配置类中通过方法laoSiJi()返回,所以我们在配置类中可以直接调用laoSiJi方法获取bean注入到Man的实例对象

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.BenzCar;
import spring.impl.Man; @Configuration
public class CarConfig { @Bean
public Car laoSiJi() {
return new BenzCar();
} @Bean
public Man work() {
return new Man(laoSiJi());
}
}

  测试类中通过上下文对象的getBean("work")方法就可以获取到Man的实例对象,从而完成对老司机开车的测试。或许,你会觉得,work方法是通过调用laoSiJi方法才获取的Car的实例的,实际上并非如此。因为有了@Bean注解,Spring会拦截所有对该注解方法的调用,直接返回该方法创建的bean,也即容器中的管理的bean。也就是说,laoSiJi方法返回的bean交给了Spring容器管理后,当其他地方需要实例对象的时候,是直接从容器中获取的第一次调用方法产生的实例对象,而不会重复的调用laoSiJi方法。我们可以如下测试

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.BenzCar;
import spring.impl.Man; @Configuration
public class CarConfig { @Bean
public Car laoSiJi() {
System.out.println("方法调用");
return new BenzCar();
} @Bean
public Man work() {
return new Man(laoSiJi());
} @Bean
public Man work2() {
return new Man(laoSiJi());
}
}

  如上测试你会发现,虽然我定义了两个方法来获取Man实例,但是控制台只输出了一次调用打印,即证明方法只在最初返回bean的时候被调用了一次,而后的实例获取都是直接从容器中获取的。这也就是默认情况下Spring返回的实例都是单例的原因:一旦容器中注册了实例对象,应用程序需要的时候,就直接给予,不用重复创建。当然,很多情况下我们不会如上面的方式去引入依赖的bean,而可能会通过参数注入的方式,这样你就可以很灵活的使用不同的装配机制来满足对象之间的依赖关系,比如下面这种自动装配的方式给Man的实例注入依赖的Car对象

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.Man; @Configuration
@ComponentScan("spring.impl")
public class CarConfig { @Bean
public Man work(Car car) {
return new Man(car);
}
}

  当然,如果你喜欢去简就繁,也可以通过XML配置文件配置依赖的bean。下面再来看看XML的方式如何装配bean。

 

XML配置文件装配bean

  使用XML配置文件的方式装配bean,首要的就是要创建一个基于Spring配置规范的XML文件,该配置文件以<beans>为根元素(相当于Java配置的@Configuration注解),包含一个或多个<bean>元素(相当于配置类中@Bean注解)。针对上文的汽车示例,如果改成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-->
<bean class="spring.impl.BenzCar"></bean>
</beans>

  然后,从基于XML的配置文件中加载上下文定义,我们就能根据ID获取到对应的bean了

package spring.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import spring.facade.Car; public class CarTest {
@Test
public void carTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("resource/applicationContext.xml");
//XML的方式如果没有明确给定ID,默认bean的ID会根据类的全限定名来命名,以#加计数序号的方式命名。
Car car = (Car)context.getBean("spring.impl.BenzCar#0");
car.drive();
}
}

  当然,示例中使用自动化的命名ID看起来逼格满满,但其实并不实用,如果需要引用bean的实例就有点操蛋了,实际应用中当然还是要借助<bean>的id属性来自定义命名。

  构造器注入

  给<bean>元素设置id属性,在构建另外的对象实例的时候,就可以很方便的引用,譬如上面基于Java的配置中的构造器注入,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="car" class="spring.impl.BenzCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过Man的构造器注入Car的实例对象-->
<constructor-arg ref="car"></constructor-arg>
</bean>
</beans>

  而有时候我们并不一定都是将对象的引用装配到依赖对象中,也可以简单的注入字面值

package spring.impl;

import spring.facade.Car;

public class Man {

    private Car car;
private String str;
public Man(String str ,Car car) {
this.car = car;
this.str = str;
} public void work() {
System.out.println(str);
car.drive();
}
} 
<?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="car" class="spring.impl.BenzCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--分别注入字面值和对象的应用-->
<constructor-arg value="陈本布衣"></constructor-arg>
<constructor-arg ref="car"></constructor-arg>
</bean>
</beans>

接着,我们继续对已有代码做些改动,将注入的参数改为Car的List集合

 public Man(List<Car> cars) {
this.cars = cars;
}

那么配置文件就可以这样配置

<?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="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过<list>子元素实现List集合对象的装配-->
<constructor-arg>
<list>
<ref bean="benzCar"/>
<ref bean="qqCar"/>
</list>
</constructor-arg>
</bean>
</beans>

如果是需要注入集合中的字面值,写法如下

<?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="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过<list>子元素实现List集合字面值的装配-->
<constructor-arg>
<list>
<value>这里直接填写字面值</value>
<value>陈本布衣</value>
</list>
</constructor-arg>
</bean>
</beans>

  我们可以采用同样的方式装配Set集合,只是Set集合会忽略掉重复的值,而且顺序也不保证。此处不做演示。

  属性注入

  构造器注入是一种强依赖注入,而很多时候我们并不倾向于写那种依赖性太强的代码,而属性的Setter方法注入作为一种可选性依赖,在实际的开发中是应用得非常多的。上面Man类如果要通过属性注入的方式注入Car的实例,就该是这样子

package spring.impl;

import spring.facade.Car;

public class Man {

    private Car car;

    public void setCar(Car car) {
this.car = car;
} public void work() {
car.drive();
}
}
<?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="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过属性注入的方式注入Car的实例-->
<property name="car" ref="benzCar"></property>
</bean>
</beans>

以上示例中,XML配置文件中属性注入的属性名必须要和Java类中Setter方法对应的属性名一致。而对于字面量的注入,和上面构造器的方式类似,只不过使用的元素名换成了<property>而已,下面仅做展示

<bean id="man" class="spring.impl.Man">
<property name="str" value="字面量的注入"></property>
<property name="list">
<list>
<value>集合的字面量注入1</value>
<value>集合的字面量注入2</value>
</list>
</property>
</bean>

<bean id="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--属性注入的方式注入集合-->
<property name="cars">
<list>
<ref bean="qqCar"></ref>
<ref bean="benzCar"></ref>
</list>
</property>
</bean>

三种装配方式的混合使用

  在同一个应用程序中,Spring常见的这三种装配方式我们可能都会用到,而对于不同的装配方式,他们之间如何实现相互引用从而整合到一起的呢?我们先看看Java配置类的引用问题。试想如果Java配置类中的bean数量过多,我们可能会考虑拆分。在本文的示例中,Man类实例的创建必须通过构造器注入Car的实例,如果把两个实例的产生分成两个配置类,那么在依赖注入的配置类中可以通过@Import注解引入被依赖的配置类

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import spring.facade.Car;
import spring.impl.Man; @Configuration
@Import(CarConfig.class) //通过@Import注解引入产生Car实例的配置类
public class ManConfig {
@Bean
public Man work(Car car) {
return new Man(car);
}
}

  但是如果Car的实例不是通过Java类配置的,而是通过XML方式配置的方式配置,我们只需通过@ImportResource注解将配置bean的XML文件引入即可,只不过这个时候要保证XML中被依赖的bean的id要和Java配置类中的形参保持一致

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import spring.facade.Car;
import spring.impl.Man; @Configuration
@ImportResource("classpath:resource/applicationContext.xml")
public class ManConfig {
@Bean
public Man work(Car car) {
return new Man(car);
}
}

而如果bean是采用XML进行装配,如果需要装配的bean过多,我们当然还是会根据业务拆分成不同的配置文件,然后使用<improt>元素进行不同XML配置文件之间的引入,形如: <import resource="classpath:xxx.xml" /> ;而如果要在XML中引入Java配置,只需将Java配置类当成普通的bean在XML中进行声明即可,但是在测试的时候要注意开启组件扫描,因为加载XML配置的上下文对象只会加载XML配置文件中的bean定义,无法让基于Java配置类产生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"
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"> <!--开启组件扫描,在测试的时候配置类才能向容器中注册类中声明的bean-->
<context:component-scan base-package="spring"/>
<!--XML中引入Java配置类:将配置类声明为bean-->
<bean class="spring.config.CarConfig"></bean>
<bean id="man" class="spring.impl.Man">
<constructor-arg ref="laoSiJi"></constructor-arg>
</bean>
</beans>

  

  最后说一点,不管是Java配置还是XML配置,有个通常的做法就是创建一个比所有配置都更高层次的根配置类/文件,该配置不声明任何的bean,只用来将多个配置组合在一起,从而让配置更易于维护和扩展。好了,以上便是两种bean的装配方式的简单讲解,如有纰漏,欢迎指正,不胜感激。

 

Spring基础篇——通过Java注解和XML配置装配bean(转载)的更多相关文章

  1. Spring基础篇——通过Java注解和XML配置装配bean

    自动化装配的确有很大的便利性,但是却并不能适用在所有的应用场景,比如需要装配的组件类不是由自己的应用程序维护,而是引用了第三方的类库,这个时候自动装配便无法实现,Spring对此也提供了相应的解决方案 ...

  2. Spring 通过XML配置装配Bean

    使用XML装配Bean需要定义对于的XML,需要引入对应的XML模式(XSD)文件,这些文件会定义配置Spring Bean的一些元素,简单的配置如下: <?xml version=" ...

  3. Spring基础篇——bean的自动化装配

    上篇博文讲Spring的IOC容器时说道,虽然容器功能强大,但容器本身只是个空壳,需要我们主动放入装配对象,并告诉它对象之间的协作关系,然后容器才能按照我们的指示发挥它的魔力,完成装配bean的使命. ...

  4. Spring基础知识之基于注解的AOP

    背景概念: 1)横切关注点:散布在应用中多处的功能称为横切关注点 2)通知(Advice):切面完成的工作.通知定了了切面是什么及何时调用. 5中可以应用的通知: 前置通知(Before):在目标方法 ...

  5. Spring基础篇——Spring的AOP切面编程

    一  基本理解 AOP,面向切面编程,作为Spring的核心思想之一,度娘上有太多的教程啊.解释啊,但博主还是要自己按照自己的思路和理解再来阐释一下.原因很简单,别人的思想终究是别人的,自己的理解才是 ...

  6. Spring系列(四):Spring AOP详解和实现方式(xml配置和注解配置)

    参考文章:http://www.cnblogs.com/hongwz/p/5764917.html 一.什么是AOP AOP(Aspect Oriented Programming),即面向切面编程, ...

  7. Spring使用AspectJ注解和XML配置实现AOP

    本文演示的是Spring中使用AspectJ注解和XML配置两种方式实现AOP 下面是使用AspectJ注解实现AOP的Java Project首先是位于classpath下的applicationC ...

  8. [spring]Bean注入——使用注解代替xml配置

    使用注解编程,主要是为了替代xml文件,使开发更加快速. 一.使用注解前提: <?xml version="1.0" encoding="UTF-8"?& ...

  9. @ComponentScan注解及其XML配置

    开发中会经常使用包扫描,只要标注了@Controller.@Service.@Repository,@Component 注解的类会自动加入到容器中,ComponentScan有注解和xml配置两种方 ...

随机推荐

  1. EF CodeFirst 之 Fluent API

    如何访问Fluent API: 在自定义上下文类中重写OnModelCreating方法,在方法内调用. 注:用法基本一样,配置类中的this就相当于modelBuilder.Entity<Pe ...

  2. PP:Classification of Time-Series Images Using Deep Convolutional Neural Networks

    The 10th international conference on machine vision; C类 Methodology: 非主流方法 2 stages: 1. convert time ...

  3. Visibility Graph Analysis of Geophysical Time Series: Potentials and Possible Pitfalls

    Tasks: invest papers  3 篇. 研究主动权在我手里.  I have to.  1. the benefit of complex network: complex networ ...

  4. javascript增强typeof 对复杂类型的判断

    js中有六种数据类型,包括五种基本数据类型(Number,String,Boolean,Undefined,Null),和一种复杂数据类型(Object). typeof 由于js中的变量是松散类型的 ...

  5. 初识eclipse-java

    开始时会有工程的地址需要设置,最好将程序放在一个单独的文件夹中 有时候会用到外部的驱动程序,如excel等,就需要导入jar包 具体的请看下篇博客.

  6. win 下 docker 环境配置

    声明 此文只针对 win7.win10 家庭版等用户操作系统,因为这些系统无法使用 windows 的 Hyper-V 虚拟技术.只能借助于 Virtual Box 虚拟机来使用 docker. Do ...

  7. javaFx中Image的路径问题

    网络图像文件前面加“http://”,而本地文件则要加“file:”.将源代码改为: Image image = new Image("file:image/qq.jpg"); I ...

  8. centos7 sshpass 用法详解

    可以参考文章:https://www.cnblogs.com/kaishirenshi/p/7921308.html 安装方式直接通过yum 安装 yum -y install sshpass 常用的 ...

  9. HTML 5 视频直播一站式扫盲(转载)

    http://www.alloyteam.com/2016/05/h5-camera-literacy/

  10. HTML学习(2)编辑器

    HTML编辑常用的编辑器:Notepad++.Sublime Text.VS Code 可以使用Emmet插件来提高编码速度. 注:emmet只支持32位的Notepad++. 注意这里要把这两个dl ...