自动化装配的确有很大的便利性,可是却并不能适用在全部的应用场景,好比须要装配的组件类不是由本身的应用程序维护,而是引用了第三方的类库,这个时候自动装配便没法实现,Spring对此也提供了相应的解决方案,那就是经过显示的装配机制——Java配置和XML配置的方式来实现bean的装配。spring
咱们仍是借助上篇博文中的老司机开车的示例来说解。Car接口中有开车的drive方法,该接口有两个实现——QQCar和BenzCarapp
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的建立细节——ide
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不会为咱们管理该方法返回的实例对象,当咱们在测试类中向容器伸手要对象的时候,天然就找不到。this
上述示例过于简单,如今,咱们要更进一步,给简单的对象添加依赖,来完成稍微复杂一点的业务逻辑。车是须要老司机来开的,因而咱们同上篇同样定义一个Man类,Man的工做就是开车——
spa
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的实例对象——code
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方法。咱们能够以下测试——component
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对象——xml
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,首要的就是要建立一个基于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的装配方式的简单讲解,若有纰漏,欢迎指正,不胜感激。