理论
1.依赖注入(DI dependency injection):
-
依赖:我们要完成一件事不能靠自己完成,需要依赖于其一些外部的一些事物,吃饭这件事我们就不能独立完成,我们除了带上自己的嘴,我们要依赖餐具,食物。对象的依赖同理。
-
注入:中国式的教育往往不是学生主动去获取知识,而是老师给学生脑袋注入知识。
-
依赖注入:java是面向对象的编程,当我们要做很多事情时往往都会依赖于诸多的对象协作来相互完成一些操作,我们知道对象的方法和属性的调用都要依赖于类的实例来完成的我们要用其他对象的方法和属性通常要先拥有对应的实例,也就是需要两步。
第一步:创建对象(A a=new A()
第二步:使用创建的对象实例去调用方法和属性(a.method和a.variable)。
如果我们做的事情很简单只有几个对象的相互依赖显然用上述的方式就可以完成,但是当我们做的事情很复杂时,可能涉及到几十,几百,几千,上万甚至更多的对象时,对象之间又相互依赖我们都用上述的方法去实现就复杂得不可想象了,首先创建对象的代码繁琐,其次对象大量创建后,考虑资源我们需要去维护对象的生命周期,维护难浪费资源,对象之间依赖关系变得不可维护。在这种背景下spring到来了,它提供了对象的管理容器,实例的生命周期(创建(init-mothod),使用,销毁(destroy-mothod))由它来维护.对象之间相互依赖不再是需要某个对象就自己去创建对象,然后使用对象,而是变为需要某个对象,spring就会为我们注入某个依赖的对象。对象的获取从主动变为被动,这就是依赖注入。
实践
依赖注入的三种方式(装配bean的方式)逐渐脱离xml配置,最后在spring5中进化为springBoot
1.spring原始xml配置bean
-
什么是xml配置bean?
把对象的创建和对象之间的依赖放到xml中来完成,然后交给spring的容器管理。
-
为何要用xm配置bean的方式?
对象的管理容易,对象间的依赖简单,解决了传统对象之间依赖调用麻烦问题。
-
如何使用xml配置bean?
1)添加接口
2)添加接口实现类
3)配置xml
4)添加单元测试
- 添加接口
package com.example.demo;
//定义一个充电工具的接口
public interface ChargeTool {
// 具有充电的这个功能
public void charge(String name);
}
- 添加接口实现类
package com.example.demo.impl;
import com.example.demo.ChargeTool;
//实现充电接口的具体实现类有了充电功能
public class ChargeToolImpl implements ChargeTool {
@Override
public void charge(String name) {
System.out.println(name + "正在开始充电...");
}
}
- 配置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" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="charge" class="com.example.demo.impl.ChargeToolImpl"></bean>
<!-- 构造方法注入 -->
<bean id="phone" class="com.example.demo.Phone">
<constructor-arg name="charge" ref="charge"/>
</bean>
<!--
<!-- setter方法注入使用这种方式需要Phone中要给charg增加setter方法-->
<bean id="phone" class="com.example.demo.Phone">
<property name="charge" ref="charge"/>
</bean> -->
</beans>
- 添加测试
为了和没有spring依赖注入思想之前对象之间依赖调用做对比,把之前的方式也列出来
1)没有使用依赖注入思想之前的实现
package com.example.demo;
import org.junit.Test;
import com.example.demo.impl.ChargeToolImpl;
public class Phone1 {
private ChargeToolImpl charge;
public void charging() {
charge = new ChargeToolImpl();
charge.charge("手机");
}
@Test
public void test() {
charging();
}
}
2)使用依赖注入后实现
package com.example.demo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.demo.impl.ChargeToolImpl;
public class Phone {
private ChargeToolImpl charge;
public Phone(ChargeToolImpl charge) {
this.charge =charge;
}
public void charging() {
charge.charge("手机");
}
public static void main(String args[]) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test.xml");
Phone phone = (Phone) context.getBean("phone");
phone.charging();
}
}
-
xml配置bean有何优缺点?
显示的一些配置多,它们将bean的类型以字符串的形式设置在了class属性中。谁能保证设置给class属性的值是真正的类呢?Spring的XML配置并不能从编译期的类型检查中受益。即便它所引用的是实际的类型,如果你重命名了类,尽管借助IDE检查XML的合法性,使用能够感知Spring功能的IDE,如Spring Tool Suite,能够在很大程度上帮助你确保Spring XML配置的合法性。
- 自动化配置:自动扫包和注解配置bean
- 什么是自动扫包和注解配置bean?
spring自动去扫描我们配置路径下面的类,根据一些注解标识来自动创建bean,如果我们要在bean引入另一个bean,那我们只需要添加一些简单注解就可以引入使用了,不用去配置xml。
- 为何要用自动扫包和注解配置bean的方式?
当我们使用了spring的xml配置文件体验完bean的创建和注入后,似乎还是不满足于现状,还是嫌配置太麻烦了,而且还有上述xml配置存在的问题也是不可忽视的。所以我们想要一种简化配置工作的方式,自动给我们创建装配bean,我们只需要简单的使用一些配置就可以使用。那么这种方式就产生了,不再需要我们去显示的配置每一个bean和装备bean之间的依赖。
- 如何使用自动扫包和注解配置bean?
1)配置自动扫包
<?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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.demo"/>
</beans>
2)添加注解
package com.example.demo.impl;
import org.springframework.stereotype.Component;
import com.example.demo.ChargeTool;
//实现充电接口的具体实现类有了充电功能
@Component
public class ChargeToolImpl implements ChargeTool {
@Override
public void charge(String name) {
System.out.println(name + "正在开始充电...");
}
}
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Phone {
@Autowired
private ChargeTool charge;
public void charging() {
charge.charge("手机");
}
}
3)添加测试
package com.example.demo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String args[]) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test.xml");
Phone phone = (Phone) context.getBean("phone");
phone.charging();
}
}
-
自动扫包和注解配置bean有何优缺点?
相比原始的spring的xml配置bean的方式,自动扫包已经简化我们手动的很多配置,我们更多的就只是通过注解引入使用。开发中总会遇到一些场景,我们开发某个功能需要借助第三方库中的组件装配到我们的应用中,在这种情况下因为类不在我们编写的代码包下,又不想用再去引入xml装配进来,我们不能通过添加注解方式就把他们引入进来。
3.显示通过javaConfig配置
- 什么是 javaConfig置bean?
学习完 spring通过xml配置装配bean和自动扫包装配bean后,我们还想要一种创建灵活并且有,spring的bean管理的功能,显示去看到对象的创建。
- 为何要用 javaConfig配置bean的方式?
尽管自动扫包引入注解方式已经够简洁方便了,我们开发中总会遇到一些场景,我们开发某个功能需要借助第三方库中的组件装配到我们的应用中,在这种情况下因为类不在我们编写的代码包下,我们不能通过添加注解方式就把他们引入进来,我们又不想用再去引入xml装配进来,这个时候javaConfig的配置就非常重要了,我们就可以采用这种显示方式配置,更为强大灵活,类型安全对重构非常友好,因为他就是通过java代码实现,我们只需要把javaConfig放入单独包中,这样对于它的意图就不会产生困惑了,里面不能又任何逻辑业务相关代码。更好的让我们理解自动扫包装配和更加灵活的显示创建bean的管理,在不同场景可以多种混合使用。
- 如何使用javaConfig置bean?
1)创建 javaConfig,去除自动扫包 Componentscan
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.demo.ChargeTool;
import com.example.demo.Phone;
import com.example.demo.impl.ChargeToolImpl;
//@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节
@Configuration
public class BeanConfig{
@Bean(name="phone")
public Phone getPhoneBean(ChargeTool charge) {
Phone phone=new Phone();
phone.setCharge(charge);
System.out.println("...创建了Phone的对象:"+phone);
return phone;
}
@Bean(name="charge")
public ChargeTool getChargeToolBean() {
ChargeTool charge=new ChargeToolImpl();
System.out.println("...创建了Phone的对象:"+charge);
return charge;
}
}
2)去除创建bean上的注解
package com.example.demo.impl;
import com.example.demo.ChargeTool;
//实现充电接口的具体实现类有了充电功能
public class ChargeToolImpl implements ChargeTool {
@Override
public void charge(String name) {
System.out.println(name + "正在开始充电...");
}
}
package com.example.demo;
public class Phone {
private ChargeTool charge;
public void setCharge(ChargeTool charge) {
this.charge = charge;
}
public void charging() {
charge.charge("手机");
}
}
3)创建单元测试,验证 javaConfig中同个类的实例是否只创建一次(创建bean的方法只执行一次)
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.demo.config.BeanConfig;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=BeanConfig.class)
public class SpringTest {
@Autowired
private Phone phone;
@Autowired
private Phone phone1;
@Test
public void test() {
phone.charging();
phone1.charging();
}
}
- javaConfig配置bean有何优缺点?
@Bean注解的方法可以采用任何必要的Java功能来产生bean实例,这里所存在的可能性仅仅受到Java语言的限制,所以创建bean的方式更加明确灵活,显示的可以使用原始的对象创建,使用。同时把bean的管理交给了spring容器,还可以把第三方仓库中的bean装配进来,两者方式更好的结合,
如果我们一个接口有多个实现,依赖装配会有问题吗?
如果一个接口有多个实现类时,在自动依赖注入中 spring就会无法抉择产生歧义抛出昇常。 ChargeTool是一个接口,并且有两个类实现了这个接口,分别为PhoneChargeToolImpl,VideoChargeToolImpl因为这两个实现均在javaConfig中配置了bean,在创建应用上下文时将其创建为Spring应用上下文里面的bean。然后当Spring试图自动装配ChargeTool参数时,它并没有唯一、无歧义的可选值。当确实发生歧义性的时候, Spring提供了多种可选方案来解决这样的问题。你可以将可选bean中的某一个设为首选( primary)的bean,或者使用限定符( qualifier)来帮助,Spring将可选的bean的范围缩小到只有一个bean.或许你会说把方法中注入参数ChargeTool换成对应实现类PhoneChargeToolImpl和VideoChargeToolImpl就可以解决了,但是这样就失去了接口定义的意义了。
1)定义接口动作
package com.example.demo;
//定义一个充电工具的接口
public interface ChargeTool {
// 具有充电的这个功能
public void charge();
}
2)定义接口动作实现类
package com.example.demo.impl;
import com.example.demo.ChargeTool;
public class VideoChargeToolImpl implements ChargeTool {
@Override
public void charge() {
System.out.println("电视正在开始充电...");
}
}
package com.example.demo.impl;
import com.example.demo.ChargeTool;
public class PhoneChargeToolImpl implements ChargeTool {
@Override
public void charge() {
System.out.println("手机正在开始充电...");
}
}
3)定义接口动作主体
package com.example.demo;
public class Phone {
private ChargeTool charge;
public void setCharge(ChargeTool charge) {
this.charge = charge;
}
public void charging() {
charge.charge();
}
}
package com.example.demo;
public class Video {
private ChargeTool charge;
public void setCharge(ChargeTool charge) {
this.charge = charge;
}
public void charging() {
charge.charge();
}
}
4)定义javaConfig
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.demo.ChargeTool;
import com.example.demo.Phone;
import com.example.demo.Video;
import com.example.demo.impl.PhoneChargeToolImpl;
import com.example.demo.impl.VideoChargeToolImpl;
//@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节
@Configuration
public class BeanConfig{
@Bean(name="phone")
public Phone getPhoneBean(@Qualifier("phoneCharge") ChargeTool charge) {
Phone phone=new Phone();
phone.setCharge(charge);
System.out.println("...创建了Phone的对象:"+phone);
return phone;
}
@Bean(name="video")
public Video getVideoBean(@Qualifier("videoCharge") ChargeTool charge) {
Video video=new Video();
video.setCharge(charge);
System.out.println("...创建了video的对象:"+video);
return video;
}
@Bean(name="phoneCharge")
public ChargeTool getChargeToolBean() {
ChargeTool phoneCharge=new PhoneChargeToolImpl();
System.out.println("...创建了PhoneChargeToolImpl的对象:"+phoneCharge);
return phoneCharge;
}
@Bean(name="videoCharge")
public ChargeTool getVideoToolBean() {
ChargeTool videoCharge=new VideoChargeToolImpl();
System.out.println("...创建了VideoChargeToolImpl的对象:"+videoCharge);
return videoCharge;
}
}
4)定义单元测试
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.demo.config.BeanConfig;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=BeanConfig.class)
public class SpringTest {
@Autowired
private Phone phone;
@Autowired
private Video video;
@Test
public void test() {
phone.charging();
video.charging();
}
}
spring相关博客请