1. Spring依赖注入起步

首先,提供一些配置信息(比如XML)来描述类与类之间的关系,然后由IOC容器(Spring Context)去解析这些配置信息,继而维护好对象之间的关系

<!-- 配置信息:在XML中定义Bean -->
<bean id="person" class="com.bravo.annotation.Person">
    <property name="car" ref="car"></property>
</bean>


<bean id="car" class="com.bravo.annotation.Car"></bean>

其次,除了配置信息,对象之间也要体现依赖关系

public class Person {
    // Person类中声明了Car,表示Person依赖Car
    private Car car;
    // 由于上面XML使用了<property>标签,表示setter方法注入,所以必须提供setter方法
    public void setCar(Car car) {
        this.car = car;
    }
}

总结起来就是:

  • 编写配置信息描述类与类之间的关系(XML/注解/Configuration配置类均可)
  • 对象之间的依赖关系必须在类中定义好(一般是把依赖的对象作为成员变量)
  • Spring会按照配置信息的指示,通过构造方法或者setter方法完成依赖注入

2. Spring的三种编程风格

所谓的编程风格其实指的是“将Bean交给Spring管理的3种方式(IOC)”,按照Spring官方文档的说法,Spring的容器配置方式可以分为3种:

  • Schema-based Container Configuration(XML配置)
  • Annotation-based Container Configuration(注解)
  • Java-based Container Configuration(@Configuration配置类)

注入方式即DI,是建立在IOC的基础上的,Spring支持的2种注入方式:

  • 构造方法注入
  • setter方法注入

Spring注入的是Bean,Bean是从IOC容器中来的

spring 指定编译环境 spring编程_spring 指定编译环境

1️⃣ XML配置开发:描述依赖关系

① XML管理bean + set注入/构造方法注入

Person

public class Person {
    // Person依赖Car
    private Car car;
    // 无参构造
    public Person(){}

    // 有参构造
    public Person(Car car){
        this.car = car;
        System.out.println("通过构造方法注入...");
    }


    // setter方法
    public void setCar(Car car) {
        this.car = car;
        System.out.println("通过setter方法注入...");
    }

}

Car

public class Car {
}

配置文件 里面构造器注入或者set方法注入选一个使用

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


    <!-- 在xml中描述类与类的配置信息 -->
    <bean id="person" class="com.bravo.xml.Person">
        <!-- property标签表示,让Spring通过setter方法注入-->
        <property name="car" ref="car"></property>
        <!-- 或者使用构造器注入 上下两种方式选一个-->
        <!-- constructor-arg标签表示,让Spring通过构造方法注入-->
        <constructor-arg ref="car"></constructor-arg>
    </bean>


    <bean id="car" class="com.bravo.xml.Car"></bean>

</bean>

在上面你会发现,配置文件中<bean>这个标签,其实承载着两个作用:
① 定义bean,告诉Spring哪个Bean需要交给它管理(放入容器)
② 维护beanbean之间的依赖关系

但是实际上在Person类中,我们已经将Car作为Person的成员,也就是在类中描述了两者的依赖关系,在XML中继续用<property>或者<constructor-arg>反而成了累赘

  • 既然类结构本身包含了依赖信息,<bean>再用<property>等去描述就显得多余了
  • 如果类结构变动,我们还需要额外维护<bean>的依赖信息,很麻烦。比如Person新增了一个shoes字段,那么<bean>又要写一个<property>表示shoes

所以,最好的做法是把让标签职责单一化,让它只负责定义bean,把beanbean的依赖关系转交给类自身维护(有这个字段就说明有依赖),这就是Spring的自动装配

② 自动装配,让<bean>职责单一化

对于自动装配,并不是只要注解式的@Autowired,XML也支持自动装配,这里先看基于XML的自动装配,XML实现自动装配可以分为两种:全局、局部

全局自动装配(XML根标签<beans>末尾加default-autowire配置)

<?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"
        default-autowire="byName">

    <!-- 在xml中只定义bean,无需配置依赖关系 -->
    <bean id="person" class="com.bravo.xml.Person"></bean>
    <bean id="car" class="com.bravo.xml.Car"></bean>
  
</beans>

所谓全局,就是在XML根标签末尾再加一个配置default-autowire="byName",那么在此XML中配置的每一个<bean>都遵守这个自动装配模式,可选值有4个

byName:根据属性和组件的名称(id)匹配关系来实现bean的自动装配
byType:根据属性和组件的类型匹配关系来实现bean的自动装配,有多个适合类型的对象时装配失败
constructor:与byType类似是根据类型进行自动装配,但是要求待装配的bean有相应的构造函数
no: 显式指明不使用Spring的自动装配功能

局部自动装配(每一个<bean>单独设置autowire

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

    <!-- 在xml中只定义bean,无需配置依赖关系 -->
    <bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean>
    <bean id="car" class="com.bravo.xml.Car"></bean>

</bean>

使用自动装配(全局/局部),就实现了把原先<bean>标签的职责单一化,只定义bean,而依赖关系交给类本身维护

2️⃣XML+注解:XML+<context:component-scan>+@Component

上面通过自动装配,把依赖信息交给类本身维护,从此<bean>只负责bean定义,能不能干脆把bean定义也剥离出来?这样就不需要在XML中写任何<bean>标签了,否则,要是bean多了,就很臃肿,如何做到这一点呢?

想想在之前,为了完成注入,我们把<bean>写在XML中,再把XML交给Spring

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");

而XML中配置的bean实际上只包含两点:要交给Spring的类的类名 + 装配模式

<!-- 在xml中只定义bean,无需配置依赖关系 -->
<bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean>
<bean id="car" class="com.bravo.xml.Car"></bean>

而类名其实很好得到,我们自己写的类不就有吗?至于自动装配的模式,也完全可以在类中通过注解指定。于是,我们找到了改造的方向:用带注解的类代替<bean>标签

Spring2.5开始提供了一系列注解,比如@Component、@Service等,这些注解都是用来表示bean的。而@Service等注解底层其实还是@Component:之所以做一层封装,是为了赋予它特殊的语义:定义Service层的bean

spring 指定编译环境 spring编程_xml_02


这样我们在要交给Spring管理的类上加上注解就可以把类交给Spring管理

@Component //带注解的类,我们希望用这种方式定义bean,并让Spring把它吃进去
public class Person {
    // Person依赖Car
    private Car car;
    @Override
    public String toString() {
        return "Person{" +
                "car=" + car +
                '}';
    }
}
@Component
public class Car {
}

在配置文件中就不需要再配置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">


</beans>

那么问题来了,既然没有将配置写再配置文件中,那么按照以前将xml配置文件交由Spring容器类管理的方式就不再可行了,xml文件中没有东西

// XML配置方式,对应的Spring容器是ClassPathXmlApplicationContext,传入配置文件告知Spring去哪读取配置信息
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");

所以我们要通知Spring:我们改用注解了,有@Component注解的类就是bean,和以前<bean>是一样的,以后你自己去扫描我的类去找哪个类上面有这个注解吧;为了达到这个目的,我们在xml中配置以下注解扫描的标签就行了

<context:component-scan base-package="com.bravo.annotation"/>

使用<context:component-scan>隐式地启用了<context:annotation-config>的功能。<context:annotation-config>的作用是让Spring具备解析@Component等注解的功能。当使用<context:component-scan>时,通常不需要包含<context:annotation-config>元素,也就是说,</context:component-scan>标签的作用有两个

① 扫描:原先我们把写有bean定义的XML文件喂给Spring,现在则让Spring自己去指定路径下扫描bean定义
② 解析:让Spring具备解析注解的功能

以上就能把bean交给Spring容器管理了,但是我们还要解决注入的问题

Spring提供了很多注入方式的标签

@Autowired:自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错
----------------------------------------------------------------------------------------------------------------------------------
@Qualifier:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用
@Autowired 
@Qualifier("beanid") 
----------------------------------------------------------------------------------------------------------------------------------
@Resource:直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型
@Resource(name = "beanid")
----------------------------------------------------------------------------------------------------------------------------------
@Value:注入基本数据类型和 String 类型数据的
@Component
public class Person {
    //用@Autowired告知Spring:请把Car装配进来
    @Autowired
    private Car car;
    @Override
    public String toString() {
        return "Person{" +
                "car=" + car +
                '}';
    }
}

以上我们就实现了<context:component-scan>+@Component彻底解放IOC配置 + @Autowired完成自动装配

JavaConfig+注解:@Configuration+@ComponentScan+@Component

在上面,注解+XML的配置方式中注解并不能单独使用,必须要XML中配置开启注解扫描才能生效
你说它叫XML配置开发吧,它又有@Component注解。你说它是注解开发吧,XML中还有一个<context:component-scan>在那嘚瑟呢。所以如何才能完全消灭XML呢?

究其根本,我们发现无法消灭XML的原因在于:注解的读取和解析必须依赖于<context:component-scan>标签!因为我们要帮Spring开启注解扫描,不然他不知道去哪读取bean。

既然<bean>标签可以被@Component代替,那么<context:component-scan>标签应该也能找到对应的注解。

不错!这个注解就是@ComponentScan!如此一来我们就再也不需要spring-context.xml

但是转念一想,脊背发凉…ClassPathXmlApplicationContext这个类要求我们必须传一个XML,怎么办?别担心,Spring同样提供了一个注解@Configuration,目的是让我们可以把一个普通的Java类等同于一个XML文件,而这个Java类就是JavaConfig,我们习惯称之为配置类。

@Configuration //表示这个Java类充当XML配置文件
@ComponentScan(basePackages = "com.bravo.javaconfig") //相当于XML中的<context:component-scan>标签
public class AppConfig {

}

ApplicationContext的子类除了ClassPathXmlApplicationContext,还有一个专门针对注解开发的:AnnotationConfigApplicationContext

public class Test {
    public static void main(String[] args) {
        // AnnotationConfigApplicationContext是Spring用来专门针对注解开发的ApplicationContext子类
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        // 从容器中获取Person
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }
}

这样我们完全消灭了XML

3️⃣JavaConfig方式:@Configuration+@Bean

严格来说,上面的做法并不是所谓的Java-based Container Configuration(@Configuration配置类)风格。我们虽然用到了@Configuration,但只是为了让Java配置类替代XML,最终消灭XML。这也太大材小用了…本质上,这还是@Component+@Autowired注解开发,只是开启注解扫描的方式从<context:component-scan>标签变为@ComponentScan

实际上,真正的Java-based Container Configuration编程风格是这样的:

  • @Configuration把一个普通Java类变成配置类,充当XML
  • 在配置类中写多个方法,加上@Bean把返回值对象加到Spring容器中
  • 把配置类AppConfig喂给AnnotationConfigApplicationContext,让它像解析XML一样解析配置类
  • 无需加@Component注解,因为我们可以手动new之后通过@Bean加入容器

AppConfig(如果你不扫描@Component,则不需要@ComponentScan

@Configuration
public class AppConfig {

    //new一个Benz对象,通过@Bean注解告知Spring把这个bean加到容器
    @Bean
    public Car benz(){
       return new Benz();
    }
    
    //new一个Bmw对象,通过@Bean注解告知Spring把这个bean加到容器
    @Bean
    public Car bmw(){
        return new Bmw();
    }
    
    //new一个Person对象,通过@Bean注解告知Spring把这个bean加到容器
    @Bean
    public Person person(){
        Person p = new Person();
        p.setCar(new Benz());
        return p;
    }

}

Benz(去除@Component,那是注解开发方式)

public class Benz implements Car {
}

Bmw(去除@Component,那是注解开发方式)

public class Bmw implements Car {
}

Person(去除@Component,那是注解开发方式)

public class Person {

    private Car car;

    // setter方法。在@Bean场景下,手动调用setter方法设置成员变量
    public void setCar(Car car) {
        this.car = car;
    }
 
    @Override
    public String toString() {
        return "Person{" +
                "car=" + car +
                '}';
    }
}

通常来说,我们日常开发一般是注解+JavaConfig混用。也就是@ComponentScan+@Configuration+@Component+@Bean