springIOC(Inversion of Control)控制反转

本文深入解析Spring框架中的IOC(Inversion of Control)与DI(Dependency Injection)核心概念,阐述了控制反转的基本原理,以及依赖注入的各种实现方式,包括XML配置、注解配置等,并探讨了它们对代码解耦的影响。

基本概念

IOC与大家熟知的依赖注入同理,这是一个通过依赖注入对象的过程 也就是说,它们所使用的对象,是通过构造函数参数,工厂方法的参数或这是从工厂方法的构造函数或返回值的对象实例设置的属性,然后容器在创建bean时注入这些需要的依赖。 这个过程相对普通创建对象的过程是反向的(因此称之为IoC),bean本身通过直接构造类来控制依赖关系的实例化或位置,或提供诸如服务定位器模式之类的机制。

1、谁控制谁:在之前的编码过程中,都是需要什么对象自己去创建什么对象,有程序员自己来控制对象,而有了IOC容器之后,就会变成由IOC容器来控制对象,
2、控制什么:在实现过程中所需要的对象及需要依赖的对象
3、什么是反转:在没有IOC容器之前我们都是在对象中主动去创建依赖的对象,这是正转的,而有了IOC之后,依赖的对象直接由IOC容器创建后注入到对象中,由主动创建变成了被动接受,这是反转
4、哪些方面被反转:依赖的对象

DI与IOC

IOC和DI是从不同的角度描述的同一件事,IOC是从容器的角度描述,而DI是从应用程序的角度来描述,也可以这样说,IOC是设计思想,而DI是具体的实现方式

解耦

在这里插入图片描述

在这里插入图片描述

使用maven的方式来构建项目

导包 添加对应的pom依赖

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
    </dependencies>

spring对象的获取及属性赋值方式

1、通过bean的id获取IOC容器中的对象
        ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
        Person person = (Person) context.getBean("person");
        System.out.println(person);
2、通过bean的类型获取对象
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person bean = context.getBean(Person.class);
System.out.println(bean);
3、通过构造器给bean对象赋值
<!--使用构造器方法赋值的时候,参数的name是由构造方法的参数名称决定的-->
<bean id = "person2" class="com.petrel.bean.Person">
    <constructor-arg name="id" value="2"></constructor-arg>
    <constructor-arg name="name" value="lisi"></constructor-arg>
    <constructor-arg name="age" value="22"></constructor-arg>
    <constructor-arg name="gender" value="男"></constructor-arg>
</bean>
<!--当通过构造器方法赋值的时候,可以把name属性省略不写,但是要注意必须要保证参数值跟构造器的参数列表的顺序一致
如果非要不一致的话,可以通过index的下标方式来标注,从0开始-->
<bean id = "person3" class="com.petrel.bean.Person">
    <constructor-arg value="3" index="0"></constructor-arg>
    <constructor-arg value="lisi"></constructor-arg>
    <constructor-arg value="22"></constructor-arg>
    <constructor-arg value="男"></constructor-arg>
</bean>
<!--当有多个相同参数的构造方法存在的时候,默认情况下是覆盖的过程,后面的构造方法会覆盖前面的构造方法
如果非要赋值给另外一个构造方法的话,可以使用type的参数来进行指定
/*    public Person(int id, String name, Integer age) {
    this.id = id;
    this.name = name;
    this.age = age;
    System.out.println("age......");
}
public Person(int id, String name, String gender) {
    this.id = id;
    this.name = name;
    this.gender = gender;
    System.out.println("gender.....");
}   就是这个地方 没加type="java.lang.Integer"会输出gender=22,加了type之后就输出age=22
还有还有一个注意的点是这里不会自动拆箱,所以定义的时候建议使用Integer类型
-->*/
<bean id = "person4" class="com.petrel.bean.Person">
    <constructor-arg value="3" index="0"></constructor-arg>
    <constructor-arg value="lisi"></constructor-arg>
    <constructor-arg value="22" type="java.lang.Integer"></constructor-arg>
</bean>

<!--    name:表示参数列表的名称
        value:表示实际的具体值
        type:表示值的类型
        index:表示值的下标,从0开始
        在日常工作中,一般都是用name,value的方式,很少有人去使用index或者type的方式,但是要注意各种情况出现的问题-->

4、通过命名空间为bean赋值,简化配置文件中属性声明的写法

1、导入命名空间

xmlns:p="http://www.springframework.org/schema/p"

2、添加配置

<!--使用p命名空间来给属性赋值-->
<bean id="person5" class="com.petrel.bean.Person" p:id="5" p:name="wangwu" p:age="25" p:gender="女">
</bean>
5、为复杂类型进行赋值操作

Person.java

    private String[] hobbies;
    private Address address;

    private List<Address> lists;
    private Set<String> sets;
    private Map<String,Object> maps;
    private Properties properties;

   public String[] getHobbies() {
        return hobbies;
    }

    public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public List<Address> getLists() {
        return lists;
    }

    public void setLists(List<Address> lists) {
        this.lists = lists;
    }

    public Set<String> getSets() {
        return sets;
    }

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

Address.java

package com.petrel.bean;

public class Address {
    private String province;
    private String city;
    private String town;

    public Address() {
        System.out.println("address被创建");
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getTown() {
        return town;
    }

    public void setTown(String town) {
        this.town = town;
    }

    @Override
    public String toString() {
        return "Address{" +
                "province='" + province + '\'' +
                ", city='" + city + '\'' +
                ", town='" + town + '\'' +
                '}';
    }
}


<!--给复杂类型进行赋值操作-->
<bean id="person6" class="com.petrel.bean.Person">
    <property name="id" value="6"></property>
    <property name="name" value="zhangsan6"></property>
    <property name="age" value="26"></property>
    <property name="gender" value="男6"></property>
    <!--给数组赋值,使用array标签-->
    <!-- 方法1 <property name="hobbies" value="book,girl,movie"></property>-->
    <property name="hobbies">
        <array>
            <value>book</value>
            <value>girl</value>
            <value>movie</value>
        </array>
    </property>
    <!--给引用类型赋值,可以使用ref引入外部bean-->
       <property name="address" ref="address"></property>

    <!--给list赋值-->
    <!--<property name="lists" value="1,2,3"></property>-->
    <property name="lists">
        <list>
            <!--使用内部bean,无法从IOC容器中直接获取对象的值-->
            <bean id="address2" class="com.petrel.bean.Address">
                <property name="province" value="北京"></property>
            </bean>
            <bean class="com.petrel.bean.Address">
                <property name="province" value="上海"></property>
            </bean>
            <!--使用外部bean,可以随意从IOC容器获取对象的值-->
            <ref bean="address"></ref>
        </list>
    </property>

    <!--给set属性赋值-->
    <property name="sets">
        <set>
            <value>zhangsan</value>
            <value>zhangsan</value>
            <value>lisi</value>
        </set>
    </property>

    <!--给map赋值-->
    <property name="maps">
        <map>
            <entry key="a" value="aaa"></entry>
            <entry key="address" value-ref="address"></entry>
            <entry key="address2">
                <bean class="com.petrel.bean.Address">
                    <property name="province" value="广东省"></property>
                </bean>
            </entry>
            <entry>
                <key>
                    <value>heihei</value>
                </key>
                <value>haha</value>
            </entry>
            <entry key="list">
                <list>
                    <value>11</value>
                    <value>22</value>
                </list>
            </entry>
        </map>
    </property>

    <!--给properties赋值-->
    <property name="properties">
        <props>
            <prop key="111">aaa</prop>
            <prop key="222">bbb</prop>
        </props>
    </property>

</bean>

    <bean id="address" class="com.petrel.bean.Address">
        <property name="province" value="河北省"></property>
        <property name="city" value="邯郸"></property>
        <property name="town" value="武安"></property>
    </bean>
6、继承关系bean的配置
    <!--bean之间的继承关系-->
    <!--可以使用abstract标签定义抽象bean,无法进行实例化  abstract="true"-->
    <bean id="parent" class="com.petrel.bean.Person" abstract="false">
        <property name="id" value="1"></property>
        <property name="name" value="zhangsan"></property>
        <property name="age" value="20"></property>
        <property name="gender" value="男"></property>
    </bean>

    <!--可以通过parent属性来获取父bean中的某些属性值-->
    <bean id="son" class="com.petrel.bean.Person" parent="parent">
        <property name="name" value="haha"></property>
    </bean>

7、bean对象创建的依赖关系
<!--创建bean的时候依赖关系
    当bean对象被创建的时候,是按照xml配置文件定义的顺序创建的,谁在前,谁就先被创建
        如果需要干扰创建的顺序,可以使用depends-on属性
    一般在实际工作中不必在意bean创建的顺序,无论谁先创建,需要依赖的对象在创建完成之后都会进行赋值操作
    <bean id="address" class="com.petrel.bean.Address" depends-on="person"></bean>
    <bean id="person" class="com.petrel.bean.Person"></bean>
    -->
8、bean的作用域控制,是否是单例
<!--
    bean的作用域:singleton、prototype、request、session
    默认情况下是单例的
    prototype:多实例的
        容器启动的时候不会创建多实例bean,只有在获取对象的时候才会创建该对象
        每次创建都是一个新的对象
    singleton:默认的单例对象
        在容器启动完成之前就已经创建好对象
        获取的所有对象都是同一个
    -->
    <bean id="person4" class="com.petrel.bean.Person" scope="prototype"></bean>
1~8

1-8所有bean对象的创建都是通过反射得到对应的bean实例,在spring中还可以通过工厂模式进行对象的创建

        /**
         * 当需要从容器中获取对象的时候,最好要保留无参构造方法,因为底层的实现是反射
         *
         *  Class clazz = Person.class;
         *  Object obj = clazz.newInstance();默认调用无参的构造方法,此方法已经被弃用
         *  Object obj = class.getDeclaredConstructor().newInstance()
         *
         */
9、利用工厂模式创建bean对象

工厂模式创建bean实例的时候有两种方式:静态工厂和实例工厂

  • 静态工厂:工厂本身不需要创建对象,但是可以通过静态方法调用,对象=工厂类.静态工厂方法名();
  • 实例工厂:工厂本身需要创建对象,工厂类 工厂对象=new 工厂类;工厂对象.get对象名();

静态工厂

//PersonStaticFactory.java
public class PersonStaticFactory {

    public static Person getPerson(String name){
        Person person = new Person();
        person.setId(1);
        person.setName(name);
        return person;
    }
}
//ioc.xml
<!--
静态工厂的使用:
class:指定静态工厂类
factory-method:指定哪个方法是工厂方法
-->
<bean id="person5" class="com.petrel.factory.PersonStaticFactory" factory-method="getPerson">
        <!--constructor-arg:可以为方法指定参数-->
        <constructor-arg value="lisi"></constructor-arg>
    </bean>

实例工厂(更容易扩展

//PersonInstanceFactory.java
public class PersonInstanceFactory {
    public Person getPerson(String name){
        Person person = new Person();
        person.setId(1);
        person.setName(name);
        return person;
    }
}
//ioc.xml
    <!--实例工厂使用-->
    <!--创建实例工厂类-->
    <bean id="personInstanceFactory" class="com.petrel.factory.PersonInstanceFactory"></bean>
    <!--
    factory-bean:指定使用哪个工厂实例
    factory-method:指定使用哪个工厂实例的方法
    -->
    <bean id="person6" class="com.petrel.bean.Person" factory-bean="personInstanceFactory" factory-method="getPerson">
        <constructor-arg value="wangwu"></constructor-arg>
    </bean>
10、继承FactoryBean来创建对象

FactoryBean是Spring规定的一个接口,当前接口的实现类,Spring都会将其作为一个工厂,但是在ioc容器启动的时候不会创建实例,只有在使用的时候才会创建对象

BeanFactory(接口):规范 有很多方法
FactoryBean(接口):获取唯一的一个对象

//MyFactoryBean.java
/**
 * 实现了FactoryBean接口的类是Spring中可以识别的工厂类,spring会自动调用工厂方法创建实例
 */
public class MyFactoryBean implements FactoryBean<Person> {

    /**
     * 工厂方法,返回需要创建的对象
     * @return
     * @throws Exception
     */
    @Override
    public Person getObject() throws Exception {
        Person person = new Person();
        person.setName("maliu");
        return person;
    }

    /**
     * 返回创建对象的类型,spring会自动调用该方法返回对象的类型
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }

    /**
     * 创建的对象是否是单例对象
     * @return
     */
    @Override
    public boolean isSingleton() {
        return false;
    }
}
//ioc.xml
<bean id="myfactorybean" class="com.petrel.factory.MyFactoryBean"></bean>
11、bean对象的初始化和销毁方法
//Address.java
    public void init(){
        System.out.println("对象被初始化");
    }
    
    public void destory(){
        System.out.println("对象被销毁");
    }
//ioc.xml
<!--bean生命周期表示bean的创建到销毁
        如果bean是单例singleton,容器在启动的时候会创建好,关闭的时候会销毁创建的bean
        如果bean是多例prototype,获取的时候创建对象,销毁的时候不会有任何的调用
        <bean id="address" class="com.petrel.bean.Address" init-method="init" destroy-method="destory" scope="singleton"></bean>
    -->
    
    <bean id="address" class="com.petrel.bean.Address" init-method="init" destroy-method="destory"></bean>
//MyTest.java
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
        Address address = context.getBean("address", Address.class);
        System.out.println(address);
        //applicationContext没有close方法,需要使用具体的子类
        ((ClassPathXmlApplicationContext)context).close();

    }
}
12、配置bean对象初始化方法的前后处理方法

spring中包含一个BeanPostProcessor(增强,在代码的前面和后面添加代码增加业务逻辑,提高扩展性)的接口,可以在bean的初始化方法的前后调用该方法,如果配置了初始化方法的前置和后置处理器,无论是否包含初始化方法,都会进行调用

//MyBeanPostProcessor.java
public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 在初始化方法调用之前执行
     * @param bean  初始化的bean对象
     * @param beanName  xml配置文件中的bean的id属性
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization:"+beanName+"调用初始化前置方法");
        return bean;
    }

    /**
     * 在初始化方法调用之后执行
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization:"+beanName+"调用初始化后缀方法");
        return bean;
    }
}
//ioc.xml
<bean id="myBeanPostProcessor" class="com.petrel.bean.MyBeanPostProcessor"></bean>

spring创建第三方bean对象

需要使用某些外部的单实例对象,例如数据库连接池

1、导入数据库连接池的pom文件
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
2、编写配置文件
//ioc.xml
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>
3、编写测试文件
public class MyTest {
    public static void main(String[] args) throws SQLException {
        ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
        
        DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
        System.out.println(dataSource);
        System.out.println(dataSource.getConnection());
    }
}

spring引用外部配置文件

1、在resource中添加dbconfig.properties
username=root
password=root
url=jdbc:mysql://localhost:3306/demo
driverClassName=com.mysql.jdbc.Driver
2、编写配置文件
//ioc.xml
<!--加载外部配置文件
		在加载外部依赖文件的时候需要context命名空间-->
<beans
	xmlns:context="http://www.springframework.org/schema/context"
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context.xsd"
>

    <context:property-placeholder location="classpath:dbconfig.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    	<!--spring容器在进行启动的时候会读取当前系统的某些环境变量的配置,
    	当前系统的用户名是用username来表示的,所以在配置文件编写属性的时候最好添加前缀来区分-->
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="url" value="${url}"></property>
        <property name="driverClassName" value="${driverClassName}"></property>
    </bean>

spring基于xml文件的自动装配

当一个对象中需要引用另外一个对象的时候,在之前的配置中我们都是通过property标签来进行手动配置的,其实在spring中还提供了一个非常强大的功能就是自动装配,可以按照我们指定的规则进行配置,配置的方式有以下几种:

​ default/no:不自动装配

​ byName:按照名字进行装配,以属性名作为id去容器中查找组件,进行赋值,如果找不到则装配null

​ byType:按照类型进行装配,以属性的类型作为查找依据去容器中找到这个组件,如果有多个类型相同的bean对象,那么会报异常,如果找不到则装配null

​ constructor:按照构造器进行装配,先按照有参构造器参数的类型进行装配,没有就直接装配null;如果按照类型找到了多个,那么就使用参数名作为id继续匹配,找到就装配,找不到就装配null

<?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="address" class="com.petrel.bean.Address">
        <property name="province" value="河北"></property>
        <property name="city" value="邯郸"></property>
        <property name="town" value="武安"></property>
    </bean>
        <!--在spring中,可以使用自动装配的功能,spring会把某些bean注入到另外bean中
        
        没有使用自动装配前,要这样设置:
	        <bean id="person" class="com.petrel.bean.Person">
	        	<property name="address" ref="address"></property>
	        </bean>
        	
    可以使用autowire属性来实现自动装配,有以下几种情况
    	default/no: 不装配
    	byName:按照id来进行装配,根据Person.java中set方法后面的名称首字母小写决定的,不是参数列表的名称
    	byType:按照bean的类型来进行装配,但是如果有多个类型,就会报错,不知道选择哪一个具体的类型
    	constructor:按照构造器进行装配,首先按照类型进行判断,如果有多个类型相同的bean,再按照id去进行判断
    -->
    <bean id="person" class="com.petrel.bean.Person" autowire="no"></bean> //运行结果:Person{id=0, name='null', age=null, gender='null', hobbies=null, adderss=null, lists=null, sets=null, maps=null, properties=null}
    <bean id="person" class="com.petrel.bean.Person" autowire="byName"></bean>//运行结果:Person{id=0, name='null', age=null, gender='null', hobbies=null, adderss=Address{province='河北',city='邯郸',town='武安'}, lists=null, sets=null, maps=null, properties=null}
    <bean id="person2" class="com.petrel.bean.Person" autowire="byType"></bean>//运行结果:Person{......,adderss=Address{province='河北',city='邯郸',town='武安'},sets=null,maps={adderss=Address{province='河北',city='邯郸',town='武安'},environment=StandardEnvironment......(环境变量)}
    <bean id="person3" class="com.petrel.bean.Person" autowire="constructor"></bean>/*运行结果:有    public Person(Address address) {
        this.address = address;
    }	这个的时候才装配上了*/
</beans>

SpEL的使用

SpEL:Spring Expression Language,spring的表达式语言,支持运行时查询操作对象

​ 使用#{…}作为语法规则,所有的大括号中的字符都认为是SpEL.

    <bean id="person4" class="com.petrel.bean.Person">
        <!--支持任何运算符-->
        <property name="age" value="#{12*2}"></property>
        <!--可以引用其他bean的某个属性值-->
        <property name="name" value="#{address.province}"></property>
        <!--引用其他bean-->
        <property name="address" value="#{address}"></property>
        <!--调用静态方法-->
        <property name="hobbies" value="#{T(java.util.UUID).randomUUID().toString().substring(0,4)}"></property>
        <!--调用非静态方法-->
        <property name="gender" value="#{address.getCity()}"></property>
    </bean>

注解

在bean上添加注解,快速将bean注册到ioc容器

1、使用注解的方式注册bean到IOC容器中
//applicationContext.xml
    <!--
    如果想要将自定义的bean对象添加到IOC容器中,需要在类上添加某些注解
    Spring中包含4个主要的组件添加注解:
    @Controller:控制器,推荐给controller层添加此注解
    @Service:业务逻辑,推荐给业务逻辑层添加此注解
    @Repository:仓库管理,推荐给数据访问层添加此注解
    @Component:给不属于以上基层的组件添加此注解
    注意:我们虽然人为的给不同的层添加不同的注解,但是在spring看来,可以在任意层添加任意注解
           spring底层是不会给具体的层次验证注解,这样写的目的只是为了提高可读性,最偷懒的方式
           就是给所有想交由IOC容器管理的bean对象添加component注解

    使用注解需要如下步骤:
    1、添加上述四个注解中的任意一个
    2、添加自动扫描注解的组件,此操作需要依赖context命名空间
    3、添加自动扫描的标签context:component-scan

	注意:当使用注解注册组件和使用配置文件注册组件是一样的,但是要注意:
		1、组件的id默认就是组件的类名首字符小写,如果非要改名字的话,直接在注解中添加即可
		2、组件默认情况下都是单例的,如果需要配置多例模式的话,可以在注解下添加@Scope注解
    -->
    <!--
    定义自动扫描的基础包:
    base-package:指定扫描的基础包,spring在启动的时候会将基础包及子包下所有加了注解的类都自动
                扫描进IOC容器
    -->
    <context:component-scan base-package="com.petrel"></context:component-scan>
</beans>

//PersonController.java
@Controller
public class PersonController {
    public PersonController() {
        System.out.println("创建对象");
    }
}

//PersonService.java
@Service
public class PersonService {
}

//PersonDao.java
@Repository("personDao")
@Scope(value="prototype")
public class PersonDao {
}
2、定义扫描包时要包含的类和不要包含的类

有选择性的配置是否要注册bean到IOC容器中

    <context:component-scan base-package="com.petrel" use-default-filters="false">
        <!--
        当定义好基础扫描的包之后,可以排除包中的某些类,使用如下的方式:
        type:表示指定过滤的规则
            annotation:按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解
            assignable:指定排除某个具体的类,按照类排除,expression表示不注册的具体类名
            aspectj:后面讲aop的时候说明要使用的aspectj表达式,不用
            custom:定义一个typeFilter,自己写代码决定哪些类被过滤掉,不用
            regex:使用正则表达式过滤,不用
        -->
<!--        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->

        <!--指定只扫描哪些组件,默认情况下是全部扫描的,所以此时要配置的话需要在component-scan标签中添加 use-default-filters="false"-->
        <context:include-filter type="assignable" expression="com.petrel.service.PersonService"/>
    </context:component-scan>
</beans>
3、使用@AutoWired进行自动注入

使用注解的方式实现自动注入

//PersonController.java
@Controller
public class PersonController {

    @Autowired
    private PersonService personService;

    public PersonController() {
        System.out.println("创建对象");
    }

    public void getPerson(){
        personService.getPerson();
    }
}

//PersonService.java
@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;

    public void getPerson(){
        personDao.getPerson();
    }
}
//PersonDao.java
@Repository
public class PersonDao {

    public void getPerson(){
        System.out.println("PersonDao:getPerson");
    }
}

/*当使用AutoWired注解的时候,自动装配的时候是根据类型实现的。
​		1、如果只找到一个,则直接进行赋值,
​		2、如果没有找到,则直接抛出异常,
​		3、如果找到多个,那么会按照变量名作为id继续匹配,
​				1、匹配上直接进行装配
​				2、如果匹配不上则直接报异常*/

//PersonServiceExt.java
@Service
public class PersonServiceExt extends PersonService{

    @Autowired
    private PersonDao personDao;

    public void getPerson(){
        System.out.println("PersonServiceExt......");
        personDao.getPerson();
    }
}

//PersonController.java
@Controller
public class PersonController {

    @Autowired
    private PersonService personServiceExt;

    public PersonController() {
        System.out.println("创建对象");
    }

    public void getPerson(){
        personServiceExt.getPerson();
    }
}

还可以使用@Qualifier注解来指定id的名称,让spring不要使用变量名,当使用@Qualifier注解的时候也会有两种情况:
​		1、找到,则直接装配
​		2、找不到,就会报错

//PersonController.java
@Controller
public class PersonController {

    @Autowired
    @Qualifier("personService")
    private PersonService personServiceExt2;

    public PersonController() {
        System.out.println("创建对象");
    }

    public void getPerson(){
        personServiceExt2.getPerson();
    }
}
4、@AutoWired可以进行定义在方法上
package com.petrel.controller;

import com.petrel.dao.PersonDao;
import com.petrel.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class PersonController {

    @Qualifier("personService")
    @Autowired
    private PersonService personServiceExt2;

    public PersonController() {
        System.out.println("创建对象");
    }

    public void getPerson(){
        System.out.println("personController..."+personServiceExt2);
//        personServiceExt2.getPerson();
    }

     /**
     * 当方法上有@AutoWired注解时:
     *  1、此方法在bean创建的时候会自动调用
     *  2、这个方法的每一个参数都会自动注入值
     * @param personDao
     */
    @Autowired
    public void test(PersonDao personDao){
        System.out.println("此方法被调用:"+personDao);
    }
    
    /**
     * @Qualifier注解也可以作用在属性上,用来被当作id去匹配容器中的对象,如果没有
     * 此注解,那么直接按照类型进行匹配
     * @param personService
     */
    @Autowired
    public void test2(@Qualifier("personServiceExt") PersonService personService){
        System.out.println("此方法被调用:"+personService);
    }
}
5、自动装配的注解@AutoWired,@Resource

在使用自动装配的时候,除了可以使用@AutoWired注解之外,还可以使用@Resource注解

  • 1、@AutoWired:是spring中提供的注解,@Resource:是jdk中定义的注解,依靠的是java的标准
  • 2、@AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在,@Resource默认是按照名字进行匹配的,同时可以指定name属性。
  • 3、@AutoWired只适合spring框架,而@Resource扩展性更好
@Controller
public class PersonController {

    @Qualifier("personService")
    @Resource
    private PersonService personServiceExt2;

    public PersonController() {
        System.out.println("创建对象");
    }

    public void getPerson(){
        System.out.println("personController..."+personServiceExt2);
        personServiceExt2.getPerson();
    }

    /**
     * 当方法上有@AutoWired注解时:
     *  1、此方法在bean创建的时候会自动调用
     *  2、这个方法的每一个参数都会自动注入值
     * @param personDao
     */
    @Autowired
    public void test(PersonDao personDao){
        System.out.println("此方法被调用:"+personDao);
    }

    /**
     * @Qualifier注解也可以作用在属性上,用来被当作id去匹配容器中的对象,如果没有
     * 此注解,那么直接按照类型进行匹配
     * @param personService
     */
    @Autowired
    public void test2(@Qualifier("personServiceExt") PersonService personService){
        System.out.println("此方法被调用:"+personService);
    }
}
6、泛型依赖注入
Student.java

```java
package com.mashibing.bean;

public class Student {
}

Teacher.java

package com.mashibing.bean;

public class Teacher {
}

BaseDao.java

package com.mashibing.dao;

import org.springframework.stereotype.Repository;

@Repository
public abstract class BaseDao<T> {

    public abstract void save();
}

StudentDao.java

package com.mashibing.dao;

import com.mashibing.bean.Student;
import org.springframework.stereotype.Repository;

@Repository
public class StudentDao extends BaseDao<Student>{
    public void save() {
        System.out.println("保存学生");
    }
}

TeacherDao.java

package com.mashibing.dao;

import com.mashibing.bean.Teacher;
import org.springframework.stereotype.Repository;

@Repository
public class TeacherDao extends BaseDao<Teacher> {
    public void save() {
        System.out.println("保存老师");
    }
}

StudentService.java

package com.mashibing.service;

import com.mashibing.dao.StudentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StudentService {

    @Autowired
    private StudentDao studentDao;

    public void save(){
        studentDao.save();
    }
}

TeacherService.java

package com.mashibing.service;

import com.mashibing.dao.TeacherDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TeacherService {
    @Autowired
    private TeacherDao teacherDao;

    public void save(){
        teacherDao.save();
    }
}

MyTest.java

import com.mashibing.service.StudentService;
import com.mashibing.service.TeacherService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;
import java.sql.SQLException;

public class MyTest {
    public static void main(String[] args) throws SQLException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService studentService = context.getBean("studentService",StudentService.class);
        studentService.save();

        TeacherService teacherService = context.getBean("teacherService",TeacherService.class);
        teacherService.save();
    }
}

​ 上述代码是我们之前的可以完成的功能,但是可以思考,Service层的代码是否能够改写:

BaseService.java

package com.mashibing.service;

import com.mashibing.dao.BaseDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

public class BaseService<T> {
    
    @Autowired
    BaseDao<T> baseDao;
    
    public void save(){
        System.out.println("自动注入的对象:"+baseDao);
        baseDao.save();
    }
}

StudentService.java

package com.mashibing.service;

import com.mashibing.bean.Student;
import com.mashibing.dao.StudentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StudentService extends BaseService<Student> {

}

TeacherService.java

package com.mashibing.service;

import com.mashibing.bean.Teacher;
import com.mashibing.dao.TeacherDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TeacherService extends BaseService<Teacher>{

}
代码

在这里插入图片描述

//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>petrel</groupId>
    <artifactId>spring_annotation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.3.RELEASE</version>
    </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.ibeetl</groupId>
            <artifactId>beetl</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>

    </dependencies>

</project>


//applicationContext.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: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标签的方式向IOC容器中注册bean
    <bean id="PersonController" class="com.petrel.controller.PersonController"></bean>-->

<!--    xml完整,注解便捷-->

    <!--在之前的操作中我们都是使用bean标签的方式向IOC容器中注册bean,下面使用注解的方式
        当使用注解的时候,可以在当前类的上面添加某些注解标识
        @Component:组件,理论上可以在任意的类上进行添加,在扫描的时候都会完成bean的注册
        @Controller:放置在控制层,用来接受用户的请求
        @Service:放置在业务逻辑层
        @Repository:放置在数据访问层 比如dao中

        这四个注解写在类上面的时候都可以完成注册bean的功能,但是这些规定并不是spring识别的标识
        在spring程序运行过程中,不会对这四个注解做任何区分,看起来是一样的,都会完成bean的注册功能
        在实际的开发过程中,最好能分清楚,提高代码的可读性。
        所以,最偷懒的方式是,给所有需要注册的bean类上添加@Component注解

        在使用注解的时候,还需要告诉spring应该从哪个包开始扫描,一般在定义的时候都写上相同包的路径
        需要导入context命名空间

        在使用注解的时候没有定义id和class,那么如何根据id来进行识别
        默认是把当前类的名称的首字母小写之后作为id
        如果需要改变名称,那么需要在注解添加参数值value来完成修改名字的目的(    @Controller(value = "personController02"   ),但是一般不改。

        @Scope注解可以声明当前类是单例还是多例
    -->

    <!--先导入context命名空间
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
        -->

    <!--告诉spring应该从哪个包开始扫描  -->
    <context:component-scan base-package="com.petrel"></context:component-scan>

    <!--当定义好注解的扫描路径之后,可以做更细粒度的控制,可以选择扫描哪个注解,也可以选择不扫描哪个注解

        include-filter:表示要包含扫描的注解,一般不会定义此规则(因为上面的那行代码扫描了整个包),但是如果引入的第三方包中包含注解,此时就需要使用此标签来进行标识。
        exclude-filter:表示要排除扫描的注解,使用较多

        type:规则的类型
            assignable:可以指定对应的类的名称。但是表达式必须是完全限定名
            annotation:按照注解来进行排除,但是表达式中必须是注解的完全限定名
            regex:使用正则表达式的方式,一般不用
            aspectj:使用切面的方式(AOP中),一般不用
            custom:使用自定义的方式,可以自己定义自己的筛选规则,一般不用

        expression:表达式
    -->
    <context:component-scan base-package="com.petrel">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/><!--根据注解排除-->
        <!--<context:include-filter type="assignable" expression="com.petrel.controller.PersonController"/>根据类的名称包含-->
    </context:component-scan>

<!--    <bean id="personController" class="com.petrel.controller.PersonController" autowire="byName......"></bean>
        注入方式可以用这种 也可以换一种 (就是private PersonService personService;要对象创建的过程)
        -->


</beans>

//PersonDao.java
package com.petrel.dao;

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

/**
 * @author Petrel
 * @data 2020/8/5 9:48
 */

@Repository
public class PersonDao {

    public void save(){
        System.out.println("保存成功");
    }

    public void updata(){
        System.out.println("更新成功");
    }
}


//PersonController.java
package com.petrel.controller;

import com.petrel.dao.PersonDao;
import com.petrel.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;

/**
 * @author Petrel
 * @data 2020/8/5 9:48
 */

@Controller
//@Scope(value = "singleton")
public class PersonController {

    /**
     * 通过@AutoWired注解能够完成自动注入的功能
     *      是按照什么方式进行自动注入的呢?
     *          默认情况下是按照ByType(就是private PersonService personService;中的PersonService这个)来进行装配的,如果找到直接赋值,找不到报错
     *          如果有多个类型一样的bean对象,此时会按照id(就是private PersonService personService;中的personService这个)来进行查找,默认的id是类名首字符小写
     *          如果找到了直接注入,如果找不到那么就报错
     *
     *          如果你想通过名字进行查找,可以自己规定名称,使用注解@Qualifier
     *
     *          当@AutoWired添加到方法上的时候,此方法在创建对象的时候会默认调用,同时方法中的参数会进行自动装配
     *
     * @Qualifier注解也可以定义在方法的参数列表中,可以指定当前属性的id名称
     *
     * 使用@Resource可以完成跟@AutoWired相同的功能,但是要注意他们之间的区别
     *      1、@Resource是jdk提供的功能,@AutoWired是spring提供的功能
     *      2、@Resource可以在其他框架中使用,而@AutoWired只能在spring中使用
     *           换句话说:@Resource扩展性好,而@AutoWired支持的框架比较单一
     *      3、@Resource是按照名称进行装配的,如果名字找不到,那么就使用类型
     *         而@AutoWired是按照类型进行装配,如果类型找不到那么就使用名字进行查找
     */
    //@Autowired//这个的运行结果是PersonService
    @Resource//这个的运行结果是PersonService2  刚开始报错了,要import javax.annotation.Resource; 我也不知道为什么没有自动导入
    @Qualifier("personService")
    private PersonService personService2;

    public void save(){
        personService2.save();
    }

    /*当@AutoWired添加到方法上的时候,此方法在创建对象的时候会默认调用,同时方法中的参数会进行自动装配*/
    @Autowired
    public void test(@Qualifier("personDao")PersonDao personDao123){  //@Qualifier注解也可以定义在方法的参数列表中,可以指定当前属性的id名称
        System.out.println("test");  //MyTest中会运行这个
        personDao123.updata();
    }

}


//PersonService.java
package com.petrel.service;

import com.petrel.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author Petrel
 * @data 2020/8/5 9:49
 */

@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;//这个对象没有进行对象创建的过程,此时就需要自动注入功能

    public void save(){
        System.out.println("PersonService");
        personDao.save();
    }

}


//PersonService2
package com.petrel.service;

import com.petrel.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author Petrel
 * @data 2020/8/15 11:06
 */

@Service
public class PersonService2 extends PersonService{
    /**
     * 加了继承,控制层中private PersonService personService2;运行结果是personService2
     * 如果没有加,就是personService
     * 如果此时想调用的是personService 就在控制层用@Qualifier注解
     */

    @Autowired
    private PersonDao personDao;

    public void save(){
        System.out.println("PersonService2");
        personDao.save();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值