【绝对经典】Spring中的IOC(控制反转)

目录

 

一、Spring框架概念

二、IOC容器XML方式实现

1、IOC的底层原理

2、IOC的接口

3、IOC操作的bean管理

  <1>基于XML配置文件方式的实现(创建对象和注入属性)

  <2>通过有参构造方法完成属性的注入,创建类,属性,创建属性对应的有参数的构造方法。在spring的配置文件xml中进行配置创建对象,属性注入。

  <3>注入属性-注入的是外部类的bean,可以是对象类型,集合,字符串等

  <4>注入属性-内部bean和级联赋值

  <5>XML注入数组、集合的属性方式

  <6>IOC另一种方式操作Bean,FactoryBean

<7> IOC中Bean的作用域和生命周期

(9)IOC中Bean的xml的自动装配

(10)IOC中Bean的xml的引入外部的属性文件

Spring通过XML配置文件方式总结:

三、IOC容器注解方式实现(重点掌握)

  1、什么是注解?

  2、Spring 针对Bean的管理中创建对象提供注解

 3、基于注解方式实现对象的创建

 4、基于注解方式实现属性的注入

① @Autowired:根据属性的类型进行装配

② @Qualifier:根据属性的名称进行装配。就是多个实现类对象,通过value值引入哪一个实现类对象。

③  @Resource 可以根据类型注入,可以根据名称注入

④@Value 注入普通类型属性

 5、完全注解开发

总结:


一、Spring框架概念

     Spring是轻量级的开源的JavaEE框架,轻量级指的是很少使用jar包。Spring可以解决企业应用开发的复杂性。Spring有两个核心部分:IOC和AOP。

   (1)IOC:控制反转把创建对象的过程交给Spring进行管理。之前创建对象的过程是通过new关键字在堆内存中进程创建,而现在通过Spring进行对象的创建和管理。

   (2)AOP:面向切面编程,在不修改源代码的前提下进行功能的增强,之前增加功能是修改源代码,现在使用AOP更加的增强了程序高内聚低耦合的特点。

     Spring的特点:方便解耦,简化开发。AOP编程的支持,方便程序的测试。方便和其他框架进行整合,方便事务的操作,降低API开发难度。

     IOC需要的jar:core,context,bean,expression.

     Spring创建对象的方式有两种,第一种是采用xml配置文件的方式创建对象,这种方式在实际开发中基本上被弃用,但是作为勤奋好学的我们,这种方式也要学习的啦, 要不然学到后面注解开发等会一头雾水。第二种是通过注解进行对象的创建,这种方式在实际开发过程中应用比较广泛,正如现在的主流框架SpringBoot就是使用注解创建对象进行开发。

     先举个例子,让大家认识认识Spring怎么创建的对象,使用xml配置方式创建对象。

① 创建xml文件,配置<bean>标签,其中,<bean>标签中的id是起的是spring所创建对象的别名,class是创建对象类的全路径

<!-- 

下面是创建类User的对象

id是起的是spring所创建对象的别名,class是创建对象类的全路径 -->

<bean id="user" class="com.spring.cn.User"></bean>

② 测试:

   测试分为两步:<1>加载xml配置文件。<2>获取配置文件中所创建类的对象 

测试类中加载配置文件
 // 加载配置文件,括号内是加载的bean1.xml,上下文类
ApplicationContext context =  new ClassPathXmlApplicationContext("bean1.xml");
// 获取配置文件的对象,上下文对象获得Spring创建的对象,
//第一个参数spring是创建对象的别名,第二个参数是得到编译后的类对象,这个class包含类的所有属性
User user =  context.getBean("user", User.class);
User.class会得到一个Class(字节码对象)类型的对象,这个对象包含这个类的所有属性

二、IOC容器XML方式实现

从4个方面来说IOC容器:

    (1)IOC容器的底层原理

    (2)IOC接口(BeanFactory)

    (3)IOC基于XML配置文件操作Bean的管理

    (4)IOC基于注解操作Bean的管理

1、IOC的底层原理

     什么是IOC呢?英文是Inversion of Control,也就是控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理。使用IOC的目的是为了降低程序模块间的耦合度。所谓耦合度,就是程序模块间的紧密连接的程度,模块之间连接的越紧密,耦合度越高,当然程序模块之间的独立性就越低。内聚性是只模块模块内部的聚集性,当然是模块内部的连续越紧密越好。

     IOC的底层原理基于三个部分:①XML文件的解析,②工厂模式,③反射。

     ①XML文件的解析:对XML配置文件里面的内容进行获取值或者是操作值

     ②工厂模式创建对象。

       首先看下图,创建的是两个类:UserService和UseDao类,之前在UserService调用UserDao的时候是通过UserDao userDao = new UserDao(),是在堆内存中new出来一个对象,当然这儿没有使用接口。而现在的方式就不一样了。IOC是将创建对象交给一个工厂类类进行创建外部对象,外面需要什么对象,工厂创建什么对象。在修改创建的对象只需要修改工厂类UserFactory里面的内容即可,不需要在UserService类和UserDao类两边来回修改代码,进一步降低了耦合度。Spring通过xml中的bean标签创建对象,将创建对象这个事情交给spring来完成。

       

     ③反射:通过得到类的字节码文件class,操作类的所有内容。java文件经过编译后变成class文件,得到字节码文件,类的属性和方法等内容都可以得到。

      下面是IOC的执行过程:id是所创建对象的别名,class是类所在包路径。首先通过反射技术获取xml里面的class类,然后将class进行解析成字节码,获取字节码也就获取了 类的属性和方法,再通过调用newInstance()进行创建对象。

     

2、IOC的接口

      IOC容器,本质上是一个工厂容器,IOC思想基于IOC容器完成,IOC的底层就是对象工厂。工厂类创建对象需要实例化,Spring对IOC的实现有两种方式,IOC对象工厂创建存储对象的方式,也就是对象工厂实现的两种方式:

      <1>BeanFactory,IOC最基本是实现方式,是Spring里面内置的实现方式,不提供开发人员使用,在加载配置文件的过程中不会创建对象。在获取对象(getBean)的时候才会去创建对象。

      <2>ApplicationContext:BeanFactory的一个子接口,提供了更多更强大的功能。在加载配置文件的时候就会创建对象:ClassPathXmlApplicationContext(xml),下面是跟进ApplicaitonContext一层的底层代码:

这两个接口的作用都可以加载配置文件,通过工厂过程创建对象。

下面是ApplicationContext的结构:

FileSystemXmlApplicationContext:表示xml文件在本地磁盘中,没有在项目中创建xml文件,从本地磁盘进行引入

ClassPathXmlApplicationContext:表示src里面的配置文件内容

IOC是基于容器,底层是一个对象工厂,工厂也相当于一个容器。

3、IOC操作的bean管理

  IOC对bean的管理指的是以下两个操作:

  ①Spring创建对象

  ②Spring注入类的属性,在Spring创建对象的过程中向类的属性注入属性的值

  Spring对对象Bean管理操作分为两种形式:

  ①基于XML的配置文件方式的实现

  ②基于注解方式的体现

  <1>基于XML配置文件方式的实现(创建对象和注入属性)

    在spring配置文件标签中,使用bean标签,标签里添加对应的属性,spring在配置文件中自动帮我们进行创建对象,至于创建对象的过程,由对象工厂创建。

    基于XML方式创建对象:

    XML配置文件中bean标签由多个属性:

    <1>id属性:xml文件中bean创建对象,id进行标识这个创建的对象,以便后端代码调用。

    <2>class属性:所需要创建对象的类的全路径,包+类

    <3>创建对象的时候,默认执行的是无参构造方法完成对象的创建。

    基于XML方式注入属性:

    DI:依赖注入,往类对象中注入属性

    XML方式输入属性的方式:①Set注入 ②带参的构造函数的注入

    <1>Set方式注入属性:①创建类,定义属性和对应的Set方法 ②在spring配置文件中配置对象的创建,配置属性注入的标签是<property name=""  value = “”>,name是属性的名字,value是设定的属性的值

  <2>通过有参构造方法完成属性的注入,创建类,属性,创建属性对应的有参数的构造方法。在spring的配置文件xml中进行配置创建对象,属性注入。

  XML文件中使用的是  <constructor-arg> 标签

<!-- 创建对象 -->
<bean id="book" class="com.spring.cn.Book">
    <!-- 设置类中属性内容 -->
    <!-- name里面是属性名,value是设置属性的具体值 -->
    <property name="name" value="sun"></property>

    <property name="description" value="这是一本好书"></property>
</bean>

<bean id="order" class="com.spring.cn.Orders">
    <constructor-arg name="oname" value="鞋子"></constructor-arg>
    <constructor-arg name="oaddress" value="China"></constructor-arg>
</bean>

  <3>注入属性-注入的是外部类的bean,可以是对象类型,集合,字符串等

dao层,service层,通过service调用dao层,这就叫引入外部bean,下面是通过xml的配置方式

①创建两个类dao,service

②在service里面调用dao里面的方法

在UserService里面创建UserDao类型属性,生成set方法,把之前的基本类型换成类类型而已。

<!-- 接口创建对象,找它的实现类才能创建对象,所以class里面是接口的实现类路径 -->
<bean id="userDao" class="com.spring.cn.dao.UserDaoImpl"></bean>

// 外部类的注入
<bean id="userService" class="com.spring.cn.service.UserService">
    <!-- Spring创建对象方式:
     service类里面引入dao类的时候,属性名是userDao,这个userDao依赖的是下面UserDaoImpl的id,
     之前的是基本数据类型,直接赋值value="",现在是引入别的类
     ref:创建userDao对象的bean标签的id 
     -->
    <property name="userDao" ref="userdao"></property>
    
</bean>

<!-- 接口创建对象,找它的实现类才能创建对象,所以class里面是接口的实现类路径 -->
<bean id="userdao" class="com.spring.cn.dao.UserDaoImpl"></bean>
//加载配置文件,Application,是一个容器,将加载后的xml里面的内容放到Application里面
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
// 然后获取容器里面的对象,调用的是哪一个方法呢,第一个参数是xml中bean标签中的id标识符,第二个是获取类的字节码文件
UserService userService = context.getBean("userService", UserService.class);
userService.add();

ApplicationContext本身就是一个容器,<bean>标签已经将对象创建好,ApplicationContext存储创建的对象。getBean(A,B)第一个参数是<bean>标签的id,标识获取创建的对象,第二个参数就是获取类的字节码文件,获取字节码文件,获取了类的属性和方法等内容。

  <4>注入属性-内部bean和级联赋值

①一对多的关系为例:部门和员工

一个部门有多个员工,一个员工属于一个部门,部门是一,员工是多

②实体类之间表示一对多的关系

③在spring配置文件中进行配置

④级联赋值的先把get方法写出来,获取对象

<bean id="emp" class="com.spring.cn.bean.Emp">
    <property name="ename" value="zhangsan"/>
    <property name="gender" value="男"/>
    <!-- 在属性的内部使用bean -->
    <property name="dept">
        <bean id="dept" class="com.spring.cn.bean.Dept">
            <property name="dname" value="计算机部门"></property>
        </bean>
    </property>
</bean>

#级联赋值
<bean id="emp" class="com.spring.cn.bean.Emp">
    <property name="ename" value="zhangsan"/>
    <property name="gender" value="男"/>
    <!--<property name="dept" ref="dept"></property>-->
    <property name="dept" ref="dept"></property>
    <!--
    前提需要设置依附的类
     <property name="dept" ref="dept"></property>
    Emp先获取Dept的对象,设置对象属性的值 dept是Emp里面的Dept dept属性,dname是类Dept中的属性值dname,-->
    <property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="com.spring.cn.bean.Dept">
    <property name="dname" value="安保部"></property>
</bean>

  <5>XML注入数组、集合的属性方式

    ①注入数组类型的属性

    ②注入List集合类型的属性

    ③注入Map集合类型的属性

<1>创建类,定义数组,list,map

<2>在spring的配置文件中,<bean>标签完成了对象的创建,applicationContext其实就是将创建好的对象保存,然后通过对象获取容器

集合类型的注入实现:

  使用的标签:

    <1>数组使用 
    <array> 
        <value> as</value>
    </array>
    
    <2>List使用 
    <list>
          <value> asda</value> 
     </list>
     
    <3>Map集合:
    <map>
        <entry key="" value=""></entry>
        <entry key="" value=""></entry>
    </map>
    
    <4>set集合:
     <set>
         <value>aaa</value>
     </set>

④注入集合属性中的细节问题,假如在List<User>放User对象

 比如属性: private List<Course> list;每一个Course类,Course类里面的也有属性,另外创建<bean>进行属性的注入

⑤把集合注入的公共的属性怎么提取出啦,重复的属性,提取出来

     <1>在spring配置文件中先引入名称空间

    <2>使用util标签提取公共属性的注入

  

  <6>IOC另一种方式操作Bean,FactoryBean

IOC有两种创建Bean管理Bean的方法:第一种就是以xml的形式:

第二种就是创建工厂对象的方法创建和操作bean,FactoryBean。

1、Spring 有两种类型bean,一种是普通bean,一种是工厂bean(FactoryBean) 2、普通bean,就是在配置文件中定义bean类型,也就是返回类型。 3、工厂bean:在配置文件中定义bean类型可以和返回类型不一样。

Spring有两种类型的Bean,一种是普通类型的bean,另一种工厂bean(FactoryBean)

①普通类型的bean:<bean>标签中的class里面定义什么类型,就返回什么类型

②工厂类型返回bean:配置文件定义bean类型,可以和返回类型不一样。 定义的当前类型,可能返回的是其他类型,分为以下两步骤:

<1>创建一个类,让这个类作为工厂bean,需要实现该类的对象,在xml创建对象,实现接口就行了,实现FactoryBean

<2>实现接口中的方法,在实现的方法中定义返回的bean类型

FactoryBean接口里面的方法:

xml的<bean>标签中定义的类型和返回的类型不一样,由FactoryBean<T>实现类方法中getObject()来决定。

<7> IOC中Bean的作用域和生命周期

①Bean中的作用域:

   <1>xml中Bean可以是单例的也可以是多例的

   <2>默认情况下是单实例对象。

   <3>可以在bean中设置bean是单实例对象还是多实例对象

   <4>如何设置是单实例还是多实例

   在Spring配置文件中bean标签中里面有属性(scope)用于设置单例还是多实例

②Bean的生命周期

     <1>生命周期

     对象的创建到对象的销毁的过程叫做生命周期。

     <2>Bean的生命周期

<bean id="orders" class="com.test.Orders" init-method="initMethod" destroy-method="initDestroy">
    <property name="oname" value="鞋子"></property>
</bean>
public Orders(){

    System.out.println("第一步:构造无参方法");
}
public void setOname(String oname) {
    this.oname = oname;
    System.out.println("第二步,设置set方法的注入");
}

public void initMethod(){
    System.out.println("第三步,初始化方法");
}

public void initDestroy(){
    System.out.println("第五步,bean的销毁方法");
}

在手动销毁的时候注意强制转换,因为是实现类中的方法
public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml");
    Orders orders = context.getBean("orders", Orders.class);
    System.out.println("第四步,获取bean的实例");
    System.out.println(orders);
    
    ((ClassPathXmlApplicationContext)context).close();
}

③Bean的后置处理器

  以上五步骤的bean声明周期需要加上两步,在bean初始化之前调用的方法和第三步之后调用的方法

    <1>创建类,实现接口BeanPostProcessor,创建后置处理器。创建一个类实现BeanPostProcessor接口,接口中有bean初始化之前和初始化之后的处理方法。

public class MyBeanPost implements BeanPostProcessor {
// 初始化之前的方法
        @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

// 初始化之后的方法 
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

怎么知道有后置处理器加载了呢

<1> 首先创建后置处理器的对象

<2> 类实现接口,Spring把它当做后置处理器执行。后置处理器会把当前所有bean都添加到后置处理器中进行执行。共2个方法,初始化之前的方法,初始化之后的方法

第一步:构造无参方法

第二步,设置set方法的注入 初始化之前执行的方法

第三步,初始化方法 初始化之后执行的方法

第四步,获取bean的实例 Orders{oname='鞋子'}

第五步,bean的销毁方法

<8>IOC中Bean的xml的自动装配

    ①什么是自动装配呢?

    向一个类中注入属性,通过<property>标签设置属性值,这种方式叫做手动装配。不需要写<property>标签中的内容叫做自动装配,属性名称、属性值,spring可以帮我们自动注入到对应的属性中。

    ②自动装配的过程

    第一种是根据属性名称,第二种是根据属性类型。实现自动装配:在<bean>标签中的autowire,配置自动装配,autowire有两个值:byName;byType

注意:byType对于相同类型的bean不能定义多个,byName中对相同类型的bean可以定义多个。

<9>IOC中Bean的xml的引入外部的属性文件

将固定的值放大property文件中,在xml文件中读取配置文件内容

① 直接配置数据库的信息

配置德鲁伊的连接池,Druid,引入jar包

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3006/userDb"></property>
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
</bean>

可以把value中的值是固定的,可以放在property里面

②引入外部属性文件配置数据库库连接池

<1>可以把value中的值是固定的,可以放在property里面

<2>把外部properties属性文件引入到spring配置文件中

*先引入context命名空间

在spring的配置文件中使用标签引入外部文件

Spring通过XML配置文件方式总结:

IOC中基于xml的的Bean的管理方式,主要包含两大类:

    ①通过XML配置文件方式创建对象

    ②通过XML配置文件方式注入属性

Spring通过xml配置的方式创建对象或者是注入属性是最原始的开发方式,在现在开发方式中,注解开发方式是最主流的,XML方式几乎面临淘汰。

但是,通过XML方式更容易学习注解配置是怎么来的,XML和注解方式我们要重点掌握注解开发方式。

三、IOC容器注解方式实现(重点掌握)

    Spring中有两种对Bean注解管理的方式:

    ①一种是基于XML配置文件对Bean的管理

    ②第二种是基于注解的方式对Bean管理

  1、什么是注解?

     注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值....)。

     使用注解,注解作用在类上,方法上,属性上面。

     注解的目的:为了简化配置

  2、Spring 针对Bean的管理中创建对象提供注解

@Component:一种普通的注解,用它都可以创建对象

@Service:一般用在业务逻辑层service上

@Controller:一般用在web层控制层上

@Repository:一般用在dao层上 以上注解的功能是一样的,都可以用来创建bean的实例。只是对于不同的模块不同的使用, 其实功能都相同

 3、基于注解方式实现对象的创建

① 引入依赖:spring-aop的jar

② 开启组件的扫描:告诉spring容器现在要在哪一个类中加上注解,扫描这些类

扫描包中的这些类,比如包下的很多类,看类上有没有注解,有注解就创建对象,指定扫描的位置,有注解创建对象。开启组件扫描,看扫描注解中的哪一个类。

怎么开启注解扫描呢?

*首先在xml文件中引入一个context名称空间

*开启组件扫描:<context:component-scan>

* 先扫描哪一个包需要扫描,然后扫描包里面的内容,当扫描到类上面有创建对象的注解的时候,就创建类的对象。

注意:类上面的注解属性值默认是所注解类的名字,首字母小写,比如:@Component(value = "userService"),或者直接不写value值,@Component

这个属性值,就是所创建对象的标记,就像XML方式里面的<bean id = "">  id属性,是标记的

总结注解创建对象:

①加载xml配置文件

②开启注解扫描,配置文件只配置扫描组件,以及扫描的base-package

③在配置的包中,找到配置的所有类,如果类上面有相关注解,那么根据注解就把类的对象创建

开启注解扫描:可以选择那些包被扫描,哪些包不被扫描

    <!-- 开启组件扫描  -->
    <context:component-scan base-package="comm.itcast"></context:component-scan>

    <!-- 组件扫描的细节问题
        use-default-filters默认值为true,为false表示不使用默认的filter,需要自己配置filter
         <context:include-filter 只扫描带Controller注解的类,比如:
         扫描Controller注解的User类
         @Controller
         public class User{}

    -->
    <context:component-scan base-package="comm.itcast" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

<!--
    扫描包里面的所有内容,
    context:exclude-filter:设置哪些内容不进行扫描,只要是注解是Controller的类,那么这个类就不用扫描
 -->
<context:component-scan base-package="comm.itcast">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>

</context:component-scan>

 4、基于注解方式实现属性的注入

     基于注解方式完成属性的注入,以service和dao层为例,通过注解完成自动装配。首先查看注入属性的注解。

     @Autowired,@Qualifier,@Resource,@Value

① @Autowired:根据属性的类型进行装配

第一步:通过注解创建Service类和Dao类的对象,在Service和Dao类上添加注解

第二步:在Service类里注入Dao类的对象,private Dao dao;不需要写Set方法,在属性上面直接使用@Autowired

在service层里面引入dao层,属性是其实就是引入一个外部类对象作为属性类型,spring注解自己就扫描了,自己引入类型。

也就是说,我想在service层调用dao的对象,DaoImpl类对象已经通过注解@Repository(value = "userDaoImpl")进行创建,现在service层里面的属性类型是Dao类型,原来调用dao层的是private UserDao userDao = new UserDaoImpl(); 现在是通过注解@Autowired进行注入属性的类型注入,引入的属性类型是UserDao类型,单个的属性类型比如 :UserDao只有一个接口UserDaoImpl,那默认的就是UserDaoImol,但是如果是多个实现类实现UserDao接口,那么需要辨别下是哪一个实现类,需要引入@Qualifier(value = "")进行注入,value指的是DaoImpl中注释@Repository(value = "userDaoImpl")的value内容。

② @Qualifier:根据属性的名称进行装配。就是多个实现类对象,通过value值引入哪一个实现类对象。

        这个@Qualifier注解要和@AutoWired一起使用。@AutoWired说明注入的属性的类型,默认的是一个,如果一个接口(UserDao)有多个实现类,根据类型,这个类型是接口UserDao,接口UserDao有多个实现类,要实现UserDao userDao = new UserDaoImpl(),注入不知道注入哪一个UserDao的实现类,现在根据名称注入,可以使用@Qualifier指定是哪一个实现类名称。指定实现类后,创建的dao对象

③  @Resource 可以根据类型注入,可以根据名称注入

@Resource //这样是根据类型进行注入

private UserDao userDao;

@Resource(name="..."),根据名称进行注入,

private UserDao usrDao;

注意:

Resource是Java扩展包中的内容!import javax.annotation.Resource;

④@Value 注入普通类型属性

 5、完全注解开发

    纯注解开发,去除xml里面的配置,

    也就是将xml配置文件里面的内容放到配置类中,创建一个配置类config,在配置类上面加上。@Configuration注解,告诉Spring这个类作为配置类,可以替代xml里面的内容。

总结:

三个重点,重点掌握注解开发!!

① IOC底层原理基于xml配置文件、工厂模式和反射

② Spring管理对象IOC基于XML配置的方式

③ Spring管理对象IOC基于注解配置的方式,配置类config替代我们的配置文件

猜你喜欢

转载自blog.csdn.net/Sunshineoe/article/details/112383676