Spring工厂模式解耦和控制反转IOC/DI注入介绍



一、Spring概述


Spring 是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架,以IoC(Inversion of Control控制反转,目的是解耦)和AOP(面向切面编程,本质是动态代理,目的是增强)为内核

  • Spring家族有很多的框架,涉及到所有层(web |service |dao)

  • 今天学的Spring仅仅是Spring家族里面的其中一个框架 Spring Framework (IOC + AOP)

提供了:

  • 表现层(web层): Spring MVC

  • 业务层(service层) : Spring

  • 持久层(Dao层):Spring JDBCTemplate, Spring Data JPA

  • 能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。


2. Spring的优势


方便解耦,简化开发

通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。 创建对象更简单!


AOP编程的支持

通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。

比如:要求面面项目里,每个方法被调用时,都输出日志到控制台“2020-03-20 11:20:31执行了xxx.xx方法”


声明式事务的支持(第3天) 在xml里面配置事务,在方法上或者类上 打一个注解即可。

可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。@Transactional


方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

例如:Spring整合了Junit


方便集成各种优秀框架

Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。


降低JavaEE API的使用难度

Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。


Java源码是经典学习范例

Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。


3. Spring的体系结构


请添加图片描述


二、工厂模式解耦


1. 耦合性问题


耦合性:程序之间(代码间)的依赖性。代码与代码之间的联系。

编译期依赖:编译时必须提供依赖的类,否则编译不通过

  • UserService us = new UserService();

    • UserService us02 = new UserServiceImpl();
  • 运行期依赖:运行时必须提供依赖的类,否则不能运行。

    • 接口和实现的写法:

      UserService us02 = Class.forName(“com.execise.service.impl.UserServiceImpl”).newInstance();

  • 应当减少编译期依赖,使用运行期依赖

耦合性越强,维护成本(时间成本&精力成本)就越高

开发时要求:高内聚,低耦合

  • 低耦合 : 耦合度很低,代码与代码之间耦合度很低。

  • 高内聚: 把具有一样功能的代码,尽可能靠拢起来。 一个业务有很多的方法,这些方法要尽可能靠在一块。方便管理,维护。

    类与类之间的内聚 模块化servlet

    • 注册 ---- RegisterServlet

    • 登录 — LoginServlet

    • 更新用户 — UpdateUserServlet

    • 用户 ----- UserServlet

方法与方法之间的内聚

  • 每个方法里面都有乱码解决…两句话 ---- 过滤器

  • req.setCharacterEncoding();

  • resp.setContentType();


耦合性问题现象


在web开发中,服务端通常分为三层:web层、service层、dao层

  • web层调用service层完成功能:需要new一个Service对象

  • 以前的写法,直接new对象

      UserService  userService = new UserService();
    
  • 真正开发的时候是面向接口编程。

      UserService userService = new UserServiceImpl();
    
  • service层调用dao层操作数据库:需要new一个dao对象

  • 以前的写法, 直接new对象

      UserDao userDao = new UserDao();
    
  • 真正开发的时候是面向接口编程。

      UerDao userDao = new UserDaoImpl();
    

三层之间的耦合性比较强:存在编译期依赖

  • service里写死了创建某一个dao对象:一旦dao对象换了,就需要修改service的源码

  • web里写死了创建某一个service对象:一旦service对象换了,就需要修改web的源码


解耦的思路


可以使用反射技术,代替 new创建对象,避免编译期依赖

bject obj = Class.forName("全限定类名").newInstance();

再把全限定类名提取到配置文件中(xml或properties),读取配置文件信息来反射创建对象

//读取配置文件,得到要创建对象的全限定类名;再通过反射技术创建对象
String className = ...;
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();

2. 使用工厂模式解耦


在这里插入图片描述


1. 创建项目,导入依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

2. dao层代码


接口

package com.execise.dao;

public interface UserDao {
    void add();
}


实现类

package com.execise.dao.impl;

import com.execise.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public void add() {
        System.out.println("调用了UserDaoImpl的add方法~!");
    }
}


3. service层代码


接口

package com.execise.service;

public interface UserService {
    void add() throws Exception;
}


实现类

package com.execise.service.impl;

import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;
import com.execise.factory.BeanFactory;
import com.execise.service.UserService;

public class UserServiceImpl implements UserService {
    public void add() throws Exception {
        System.out.println("调用了UserServiceImpl的add方法~!");

        //以前的写法:
        /*UserDao userDao = new UserDao();
        UserDao userDao = new UserDaoImpl();
        userDao.add();*/

        //现在的写法:
        UserDao userDao = (UserDao) BeanFactory.getBean("ud");
        userDao.add();
    }
}


4. 配置文件 beans.properties


在resource下面创建 beans.properties

us=com.execise.service.impl.UserServiceImpl
ud=com.execise.dao.impl.UserDaoImpl

5. 工厂类 BeanFactory

package com.execise.factory;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

/*
    这是专门用来创建对象的工厂类
        1. 使用静态代码块来读取beans.properties

        2. 提供一个静态方法,供外面的人调用获取对象
 */
public class BeanFactory {
    static Map<String , String> map = new HashMap<String , String>();

    //1. 在这里读取beans.properties文件
    static{

        //1. 读取外部的properties文件,只要写名字即可
        ResourceBundle resourceBundle = ResourceBundle.getBundle("beans");

        //2. 读取里面的内容
        Enumeration<String> keys = resourceBundle.getKeys();

        //3. 遍历每一个key value
        while(keys.hasMoreElements()){

            //4. 取出每一个key
            String key = keys.nextElement();

            //5. 得到每一个value
            String value = resourceBundle.getString(key);

            //6. 把key和value保存到map集合
            map.put(key , value );
        }

    }


    public static Object getBean(String name) throws Exception {

        //1. 从map集合里面获取全路径
        String className = map.get(name);

        //2. 判定
        if(className != null){
            return Class.forName(className).newInstance();
        }

        return null;
    }
}


测试

package com.itexeciseeima.test;

import com.execise.factory.BeanFactory;
import com.execise.service.UserService;
import org.junit.Test;

public class TestUserServiceImpl {

    @Test
    public void testAdd() throws Exception {

        //1. 问工厂要对象
       UserService us = (UserService) BeanFactory.getBean("us");
       us.add();
    }
}

小结


首先得有接口和实现类 : UserDao 和 UserDaoImpl , UserService 和 UserServiceImpl

使用properties配置文件来记录 ,别名和实现类的全路径

定义一个工厂类

  1. 在静态代码块里面读取配置文件,使用map集合来存映射关系

  2. 定义一个静态方法,只要有人来获取实例,那么就从map集合里面取出来全路径

  3. 使用反射技术来构建实例返回。


三、控制反转IOC


什么是IOC inversion of control


控制反转,把对象的创建工作交给框架(工厂 Spring),我们不需要自己去new这个对象,只管问工厂要。由原来的主动创建对象,变成自己被动接收 框架创建的对象。


IOC的作用


IOC是Spring的核心之一,作用就是为了解耦,降低程序,代码间的耦合度。


1. 创建Maven项目,导入依赖坐标

 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

2. 编写dao接口 UserDao及实现 UserDaoImpl


接口 UserDao

package com.execise.dao;

public interface UserDao {
    void add();
}


实现类 UserDaoImpl

package com.execise.dao.impl;

import com.execise.dao.UserDao;

public class UserDaoImpl implements UserDao {
  
   
    public  void add() {
        System.out.println("调用了UserDaoImpl的add方法~!~");
    }
}

3. 创建Spring核心配置文件,并配置 UserDaoImpl


这个步骤的作用就是告诉spring,要创建哪个类的对象,并且给这个类起一个别名,方便以后我们问spring要对象。它的作用等于我们前面写的 beans.properties


配置文件名称,通常叫 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--
        在这里告诉spring要创建哪个类的对象,并且给这个对象起一个别名

        bean标签:
            作用: 用来托管(类)对象
            属性:
                id: 唯一标识,不能出现重复!
                class: 托管类的全路径
     -->
    <bean id="ud"  class="com.execise.dao.impl.UserDaoImpl" />
</beans>

4. 使用Spring的API,获取Bean实例对象


编写测试类

package com.execise.test;

import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUserDao {


    @Test
    public void testAdd(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 问工厂要对象
        UserDao userDao = (UserDao) context.getBean("ud");

        //3. 调用方法
        userDao.add();
    }

}


小结

  1. 首先编写UserDao 和 UserDaoImpl

  2. 在pom.xml里面添加依赖

  3. 在resources下面,创建一个xml文件,名字随意。不要手动创建文件的方式。要选择xml配置文件的方式

  4. 在xml文件里面登记|注册|托管实现类

  5. 问工厂要实例


配置文件详解


1. bean标签的基本配置

<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"></bean>

介绍

  • 用于配置:把对象交给Spring进行控制 , spring会帮助我们创建对象。

  • 默认情况下,Spring是调用类的无参构造来创建对象的;如果没有无参构造,则不能创建成功


基本属性

  • id:唯一标识

  • class:bean的全限定类名


了解:bean的id和name的区别

  1. 一个bean只能有一个id;一个bean可以有多个name
  2. bean的name值:多个name之间以 , ; 空格 隔开,第1个name作为id,其它作为别名

2. bean标签的作用范围配置

<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl" scope="singleton"></bean>

scope属性取值如下:

取值说明
singleton默认,表示单例的,一个Spring容器里,只有一个该bean对象
prototype多例的,一个Spring容器里,有多个该bean对象
requestweb项目里,Spring创建的bean对象将放到 request域中:一次请求期间有效
sessionweb项目里,Spring创建的bean对象将放到 session域中:一次会话期间有效
globalSessionweb项目里,应用在Portlet环境/集群环境;如果没有Portlet/集群环境,那么globalSession相当于session(新版本中已删除)

不同scope的bean,生命周期:

  • singleton:bean的生命周期和Spring容器的生命周期相同

    • 整个Spring容器中,只有一个bean对象
    • 何时创建:加载Spring配置文件,初始化Spring容器时,bean对象创建
    • 何时销毁:Spring容器销毁时,bean对象销毁
  • prototype:bean的生命周期和Spring容器无关。Spring创建bean对象之后,交给JVM管理了

    • 整个Spring容器中,会创建多个bean对象,创建之后由JVM管理
    • 何时创建:调用 getBean方法获取bean对象时,bean对象创建
    • 何时销毁:对象长时间不用时,垃圾回收

3. bean生命周期相关方法的配置


<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl" 
      init-method="" destroy-method=""></bean>

init-method:指定类中初始化方法名称,该方法将在bean对象被创建时执行

destroy-method:指定类中销毁方法名称,该方法将在bean对象被销毁时执行


注意:

  • prototype类型的bean:Spring容器销毁时,也不会执行销毁方法,因为Spring不负责它的销毁
  • singleton类型的bean:在Spring容器显式关闭时,会执行destroy-method指定的方法

dao

package com.execise.dao.impl;

import com.execise.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public UserDaoImpl(){
        System.out.println("创建对象了额~!!~");
    }

    public void add() {
        System.out.println("调用了UserDaoImpl的add方法~!");
    }

    //创建好对象的时候调用这个方法
    public  void init(){
        System.out.println("调用了UserDaoImpl的init方法~!");

    }

    //销毁对象的时候调用这个方法
    public  void destroy(){
        System.out.println("调用了UserDaoImpl的destroy方法~!");

    }
}


单元测试

package com.execise.test;

import com.execise.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUserDaoImpl {

    //测试单例多例
    @Test
    public void testAdd2(){

        //1. 创建工厂
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 问工厂要对象
        UserDao userDao = (UserDao) context.getBean("ud");

        //3. 关闭工厂!
        context.close();
    }
}

xml配置

    <!--
        在这里告诉spring要创建哪个类的对象,并且给这个对象起一个别名

        bean标签:
            作用: 用来托管(类)对象
            属性:
                id: 唯一标识,不能出现重复!
                class: 托管类的全路径
                name : 用于给对象起别名,标识以后可以用这个别名来找到对象
                     可以写多个值,使用 分号 , 逗号, 空格 ,tab 来间隔!【一般不用它!】

                scope: 用来设置创建的对象是单例的还是多例
                    singleton: 单例,默认是单例! 【大多数情况下,使用的都是单例对象】
                        何时创建:创建工厂的时候创建了对象
                        何时销毁:销毁工厂的时候,销毁对象
                    prototype: 多例
                        何时创建:问工厂要对象的时候,才创建对象
                        何时销毁:长时间不用之后,就由GC回收对象!
                init-method:
                    当对象初始化的时候,调用这个方法
                destroy-method:
                    当对象销毁的时候,调用这个方法
            spring创建类的对象,默认会执行该类的无参构造方法!

     -->
    <bean id="ud"  class="com.execise.dao.impl.UserDaoImpl" scope="prototype" init-method="init" destroy-method="destroy"/>



4. bean实例化的三种方式


我们通常都是问Spring要对象,那么Spring怎么整出来对象的呢?有三种方式。算起来就只有两种办法创建对象:

  1. 由Spring来创建对象
  2. 由我们自己来创建对象,然后spring来拿我们的对象给需要的人。

无参构造方法实例化,默认的:让Spring调用bean的无参构造,生成bean实例对象给我们 【由Spring创建】

工厂静态方法实例化:让Spring调用一个我们自己写好的工厂类的静态方法,得到一个bean实例对象 【由咱们自己创建】

工厂非静态方法实例化(实例化方法):让Spring调用一个工厂对象的非静态方法,得到一个bean实例对象 【由咱们自己创建】


无参构造方法实例化 【spring创建对象】


UserDaoImpl 是由Spring创建的。

<bean id="ud" class="com.execise.dao.impl.UserDaoImpl"></bean>

工厂静态方法实例化


UserDaoImpl的由我们写好的StaticFactory的类来创建 , Spring工厂没干活,只是问我们的工厂要对象而已。


工厂类如下:com.execise.factory.StaticFactory

package com.execise.factory;

import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;

/*
    使用工厂的静态方法来创建对象
 */
public class StaticFactory {

    /**
     * 创建UserDaoImpl的对象
     * @return
     */
    public static UserDao getBean(){
        System.out.println("来问StaticFactory要对象了~");
        return new UserDaoImpl();
    }
}

配置如下:

    <!--
        使用工厂的静态方法来创建对象
            1. spring工厂并不会去创建UserDaoImpl的对象。
            2. 当有方法拿着ud02来问spring的工厂要对象的时候,spring的工厂会
                找StaticFactory的getBean方法得到对象
            3. 然后把这个对象返回给我们的方法
     -->
   <bean id="ud02" class="com.execise.factory.StaticFactory" factory-method="getBean"/>


工厂非静态方法实例化


UserDaoImpl的由我们写好的InstanceFactory的类来创建 ,, Spring工厂没干活,只是问我们的工厂要对象而已。


工厂类如下:com.execise.factory.InstanceFactory

package com.execise.factory;

import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;

public class InstanceFactory {
    /**
     * 创建UserDaoImpl的对象
     * @return
     */
    public  UserDao getBean(){
        System.out.println("来问InstanceFactory要对象了~");
        return new UserDaoImpl();
    }

}

配置如下:

    <!--
        使用工厂的非静态方法来创建对象:
            1. spring的工厂并不会去创建UserDaoImpl的对象。
            2. 它创建了我们工厂的对象
            3. 当有方法拿着ud03来问spring的工厂要对象的时候,spring会拿着instanceFactory去找到工厂对象
                (这个工厂对象是由spring创建出来的),然后调用getBean方法,返回我们创建好的UserDaoImpl对象
      -->
    <bean id="instanceFactory" class="com.execise.factory.InstanceFactory"/>
    <bean id="ud03" factory-bean="instanceFactory" factory-method="getBean"/>


实现FactoryBean<T>方式


定义UserDaoFactoryBean实现FactoryBean<UserDao>


UserDaoFactoryBean中实例化什么类型的对象泛型就是该类型。

//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

applicationContext.xml配置

<!--方式四:使用FactoryBean实例化bean-->
<bean id="userDao" class="com.execise.factory.UserDaoFactoryBean"/>

注意配置文件中id="userDao"是否重复。


小结

  1. Spring工厂创建实例有三种方式: 默认的无参构造方式 | 静态工厂方式 | 实例工厂方式

  2. 只有无参构造的那种方式是spring创建对象,其他两种都是由我们自己来创建对象

  3. 我们使用spring的IOC ,目的就是为了把对象的创建工作交给Spring,后面这种工厂的方式,反而是我们来创建对象,所以一般不用这两种。

  4. 既然如此,都不怎么用后面得的两种方式了,为什么spring还要提供这两种入口呢?

    这个其实就是为了兼容, 就是为了兼容以前的旧项目,有的旧项目50年前的旧项目,那个没有spring,但是那个时候已经使用了工厂来创建实例了。


IOC小结

  1. IOC是什么? 控制反转,把对象的创建工作交给spring的工厂完成,只管问spring要对象即可

  2. 在applicationContext.xml里面注册。

  3. 默认创建的实例是单例,如果想要多例,需要配合scope属性,设置成prototype


四、依赖注入DI


托管类里面有什么属性需要完成赋值工作,把这个赋值的工作交给spring来做。由spring把属性需要用到的值赋值(注入)进来就称之为依赖注入。


我们通过Ioc把bean对象交给了Spring容器进行管理,降低了耦合性。

但是耦合性不能彻底消除,bean之间还是有一些依赖关系。比如:业务层userService要依赖于持久层userDao。

这样的依赖关系,可以交给Spring帮我们进行依赖的注入,而不用我们自己注入依赖


1. 创建Maven项目,导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

2. 编写dao层和service层代码


dao层接口 UserDao


package com.execise.dao;

public interface UserDao {
    void add();
}


dao层实现类 UserDaoImpl


package com.execise.dao.impl;

import com.execise.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public void add() {
        System.out.println("调用了UserDaoImpl的add方法~!");
    }
}


service层接口 UserService


package com.execise.service;

public interface UserService {

    void add();
}

service层实现类 UserServiceImpl


package com.execise.service.impl;

import com.execise.dao.UserDao;
import com.execise.service.UserService;


/*
    需求: service里面的add方法要调用dao的add方法
    分析:
        1. 要想调用dao的add方法,必须持有UserDaoImpl的对象。
        2. 要想拥有UserDaoImpl的对象有两种办法:
            2.1 自己创建 ,自己new   【以前的做法】
                UserDao userDao  = new UserDaoImpl();
                userDao.add();
            2.2 让spring把userDaoImpl的对象给注入进来,注入给UserServiceImpl!【采用这种方式】
     步骤:
        1. 在UserServiceImpl里面定义属性 :  private UserDao userDao;
        2. 提供这个属性的set方法!
        3. 把UserServiceImpl和UserDaoImpl这两个类都交给spring托管。
        4. 在xml里面配置,告诉spring要把UserDao的对象注入到UserServiceImpl里面的这个属性 userDao身上!

 */

public class UserServiceImpl implements UserService {

    //1. 定义属性
    private UserDao userDao;

    //2. 提供set方法
    public void setUserDao(UserDao userDao) {
        System.out.println("来调用Set方法了·~");
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl的add方法~!");
        userDao.add();
    }
}


3. 创建Spring核心配置文件,并配置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">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>

    <!--2. 把UserServiceImpl交给spring管理-->
    <bean id="us" class="com.execise.service.impl.UserServiceImpl">
        <!--告诉spring,把id名字叫做ud的对象,赋值给UserServiceImpl里面的userDao属性-->
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>

4. 使用Spring的API,测试

package com.execise.test;

import com.execise.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUserServiceImpl {

    @Test
    public void testAdd(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 问工厂要对象
        UserService us = (UserService) context.getBean("us");

        //3. 调用方法
        us.add();

    }

}


小结

  1. 有接口,有实现类

  2. 给属性提供set方法

  3. 在托管|登记类的时候,要添加property标签。如果注入的是对象,那么要使用ref属性,如果注入的是普通的数据,那么要使用value属性。


三种常见注入方式


1. set方法注入(最常用)


1) 介绍


在类中提供需要注入的成员(依赖项)的set方法,在配置文件中注入属性的值


<bean id="" class="">
	<property name="属性名" value="属性值"></property>
    <property name="属性名" ref="bean的id"></property>
</bean>

property标签:用在bean标签内部,表示要给某一属性注入数据

  • name:属性名称

  • value:要注入的属性值,注入简单类型值

  • ref:要注入的属性值,注入其它bean对象


2) 示例


service

package com.execise.service.impl;

import com.execise.dao.UserDao;
import com.execise.service.UserService;


/*
    三种注入方式之一:  set方法
    要求:一定要提供属性的set方法!

 */

public class UserServiceImpl01 implements UserService {

    private String address;
    private UserDao userDao;

    public void setAddress(String address) {
        this.address = address;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl01的add方法~!"+address);
        userDao.add();
    }
}


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

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl01交给spring管理
            2.1 采用set方法完成属性的注入工作。
                property :  用来告诉spring,有哪些属性要赋值,代码里面一定要提供set方法
                    name :属性名
                    ref : 注入值,针对的是对象类型属性
                    value : 注入值,针对的是普通的属性(基本数据类型 &  字符串)
     -->
    <bean id="us" class="com.execise.service.impl.UserServiceImpl01">
        <property name="userDao" ref="userDao"/>
        <property name="address" value="深圳"/>
    </bean>

</beans>

2. 构造方法注入


1) 介绍


在类中提供构造方法,构造方法的每个参数就是一个依赖项,通过构造方法给依赖项注入值。


<bean id="" class="">
	<constructor-arg name="构造参数名称" value="构造参数的值"></constructor-arg>
    <constructor-arg name="构造参数名称" ref="bean的id"></constructor-arg>
</bean>
  • name:构造参数的名称

  • type:构造参数的类型

  • index:构造参数的索引

  • value:要注入的值,注入简单类型值

  • ref:要注入的值,注入其它bean对象


2) 示例


service

package com.execise.service.impl;

import com.execise.dao.UserDao;
import com.execise.service.UserService;


/*
    三种注入方式之一:  有参构造
    要求:一定要提供有参构造方法!

 */

public class UserServiceImpl02 implements UserService {

    private String address;
    private UserDao userDao;

    public UserServiceImpl02(String address, UserDao userDao) {
        this.address = address;
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl02的add方法~!"+address);
        userDao.add();
    }
}

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

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl02交给spring管理
            constructor-arg : 用于匹配有参构造函数,
                name: 参数名
                value : 给参数赋值,针对的是普通的参数(基本类型 &  字符串)
                ref : 给参数赋值, 针对的是对象的参数
     -->
    <bean id="us" class="com.execise.service.impl.UserServiceImpl02">
        <constructor-arg name="address" value="深圳" />
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>

</beans>

3. p名称空间注入


1) 介绍


p名称空间注入,本质仍然是set方法注入


在xml中引入p名称空间的约束

然后通过 p:属性名称=""来注入简单数据、使用 p:属性名称-ref=""注入其它bean对象,它的本质仍然是set方法注入


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
  
    <bean id="" class="" p:属性名="简单值" p:属性名-ref="bean的id"></bean>
  
</beans>

2) 示例


service

package com.execise.service.impl;

import com.execise.dao.UserDao;
import com.execise.service.UserService;


/*
    三种注入方式之一:  p名称空间
    要求:一定要提供属性的set方法!

 */

public class UserServiceImpl03 implements UserService {

    private String address;
    private UserDao userDao;

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

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("调用了UserServiceImpl03的add方法~!"+address);
        userDao.add();
    }
}

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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl03交给spring管理

     -->
    <bean id="us" class="com.execise.service.impl.UserServiceImpl03" p:address="中粮商务公园" p:userDao-ref="userDao"/>

</beans>

依赖注入方式选择


  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现

  2. 可选依赖使用setter注入进行,灵活性强

  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨

  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入

  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入

  6. 自己开发的模块推荐使用setter注入


小结

  1. 注入方式有三种,setter注入 ,构造方法注入,p名称空间

  2. 最常用的是setter注入。

  3. 以后如果使用注解了,方法也不需要写了。


依赖自动装配


如何配置按照类型自动装配?


自动装配概念

IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配

自动装配方式

按类型(常用)

按名称

按构造方法

不启用自动装配


自动装配类型


依赖自动装配


配置中使用bean标签autowire属性设置自动装配的类型

<bean id="bookDao" class="com.execise.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.execise.service.impl.BookServiceImpl" autowire="byType"/>

依赖自动装配特征

  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作

  2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用

  3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用

  4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

注入集合数据


前边我们介绍了如何注入简单数据类型和bean对象,但是在实际开发中,可能会需要给集合属性注入数据,比如:给数组、List、Set、Map等注入数据


示例代码

package com.execise.service.impl;

import com.execise.dao.UserDao;
import com.execise.service.UserService;

import java.util.*;


/*
    注入集合数据: 数组、list、set、map、properties

 */

public class UserServiceImpl04 implements UserService {

    private String [] array;
    private List<String> list;
    private Set<String> set;
    private Map<String , String> map;
    private Properties properties;

    public void setArray(String[] array) {
        this.array = array;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

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

    public void add() {
        System.out.println("调用了UserServiceImpl04的add方法~!");
        System.out.println("array="+Arrays.toString(array));
        System.out.println("list = " + list);
        System.out.println("set = " + set);
        System.out.println("map = " + map);
        System.out.println("properties = " + properties);
    }
}


配置注入数据

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1. 把UserDaoImpl交给spring管理-->
    <bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>

    <!--
        2. 把UserServiceImpl04交给spring管理
     -->
    <bean id="userviceImpl" class="com.execise.service.impl.UserServiceImpl"/>

    <bean id="us" class="com.execise.service.impl.UserServiceImpl04">

        <!--1. 数组-->
        <property name="array">
            <array>
                <value>array01</value>
                <value>array02</value>
                <value>array03</value>
            </array>
        </property>

        <!--2. list-->
        <property name="list">
            <list>
                <value>list01</value>
                <value>list02</value>
                <value>list03</value>
            </list>
        </property>

        <!--3. set-->
        <property name="set">
            <set>
                <value>set01</value>
                <value>set02</value>
                <value>set03</value>
            </set>
        </property>

        <!--4. map-->
        <property name="map">
            <map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
                <entry key="key3" value="value3"/>
            </map>
        </property>

        <!--5. properties-->
        <property name="properties">
            <props>
                <prop key="username">张三</prop>
                <prop key="password">123456</prop>
            </props>
        </property>

    </bean>

</beans>

所有单列结构的数据集合,标签可以互换使用。例如:List、Set、数组等

所有键值对结构的数据集合,标签可以互换使用。例如:Map、Properties等


小结

  1. 所有的DI数据类型里面,最常用的是对象数据。

  2. 最常用的方式 set方法。

  3. 数组 、 list 、set写法基本一样, map 和 properties基本一样。


引入properties文件


如果需要在 applicationContext.xml中引入properties文件:

  • 准备一个properties文件放在resources目录里:db.properties
db.driverClass=com.mysql.jdbc.Driver
db.jdbcUrl=jdbc:mysql://localhost:3306/spring01
db.user=root
db.password=root

applicationContext.xml中引入并使用 db.properties

  • Spring的名称空间(建议使用idea自动生成的,如果idea抽风了,就自己手写)
<beans
       xmlns:名称空间="http://www.springframework.org/schema/名称空间"
       xsi:scehmaLocation="
          http://www.springframework.org/schema/名称空间
         http://www.springframework.org/schema/名称空间/spring-名称空间.xsd">
</beans>
  • 使用context名称空间提供的标签,引入外部的properties文件

context的标签,硬着头皮写出来就可以了,不要害怕!


   <!--导入进来db.properties-->
    <context:property-placeholder location="db.properties"/>

    <!--在下面使用 ${}来取值-->
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${driverClass}"/>
        <property name="jdbcUrl" value="${jdbcUrl}"/>
        <property name="user" value="${user}"/>
        <property name="password" value="${password}"/>
    </bean>

分模块提供配置文件


在大型项目开发中,如果把所有的配置都写在一个配置文件 applicationContext.xml中,会导致:

  • 配置文件过于臃肿

  • 不利于分模块开发,不利于模块之间的解耦

Spring提供了分模块配置的方式,即:每个模块|层提供一个配置文件,在核心配置文件中引入模块配置:

  • dao模块有一个配置文件:applicationContext-dao.xml 只配置dao相关的对象

  • service模块有一个配置文件:applicationContext-service.xml 只配置service相关的对象

  • 有一个总的核心配置文件:applicationContext-all.xml如下

<import resource="classpath:applicationContext-service.xml"/>
<import resource="classpath:applicationContext-dao.xml"/>

五、相关API介绍


1. ApplicationContext的继承体系


ApplicationContext:接口,代表应用上下文,可以通过其实例对象获取Spring容器中的bean对象


在这里插入图片描述


2. ApplicationContext


XmlBeanFactory 和 ApplicationContext的区别

  • ApplicationContext 是现在使用的工厂

    ApplicationContext context = 
        new ClassPathXmlApplicationContext("applicationContext.xml");
    
  • XmlBeanFactory 是老版本使用的工厂,目前已经被废弃【了解】

    BeanFactory beanFactory = 
        new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    

两者的区别:

  • ApplicationContext加载方式是框架启动时就开始创建所有单例的bean,存到了容器里面

  • XmlBeanFactory加载方式是用到bean时再加载(目前已经被废弃)


ApplicationContext的实现类


  1. ClassPathXmlApplicationContext

    • 是从类加载路径里,加载xml配置文件的

    • 什么是类加载路径:代码编译之后的那个classes文件夹,

      • 开发中可以认为Maven项目的:Java文件夹、resources文件夹,都是类加载路径

  1. FileSystemXmlApplicationContext

    • 从磁盘路径里,加载xml配置文件的

  1. AnnotationConfigApplicationContext

    • 用注解配置Spring时,通过此类加载配置类创建Spring容器,它用于读取类上的注解配置

getBean()方法


ApplicationContext提供了多种getBean方法的重载,常用的如下:

方法参数返回值
getBean(String beanId)bean的idObject,bean对象
getBean(String beanId,Class beanType)bean的Class类型bean对象
getBean(Class beanType)bean对象
getBeanDefinitionNamesString[] 获取工厂管理的对象的名字

package com.execise.test;

import com.execise.service.UserService;
import com.execise.service.impl.UserServiceImpl04;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestApplicationContext {

    @Test
    public void testGetBean(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext04.xml");

        //2. 问工厂要对象=getBean()

       /**/ //1. 按照id来找对象,没有问题的!
        UserService us = (UserService)context.getBean("us");
        us.add();

        //2. 按照真实的自己的类型来找对象!
        UserService us2 = context.getBean(UserServiceImpl04.class);
        us2.add();

        //3. 按照接口的类型来找对象!
        UserService us3 = context.getBean(UserService.class);
        us3.add();

    }

    // 得到所有对象的id值
    @Test
    public void testGetBeanNames(){

        //1. 创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext04.xml");


        //2. 获取spring工厂里面的所有对象的id值。
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println("name = " + name);
        }

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请叫我阿杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值