Spring(1) Spring配置开发以及IOC的基本原理

一.Spring的基本概念

  Spring是一个轻量级的开源框架,它以IOC(控制反转)AOP(面向切面编程)为核心,Spring诞生的根本目的是解决软件应用开发的复杂性。其本质是一个帮助我们管理对象及其依赖关系的容器,除此之外它还提供了事务控制,动态监督,异常处理等能力,方便解耦,便于简化开发。我们这里只要先知道Spring是干什么的就可以了,接下来我们会详细介绍它的IOC和AOP原理以及基本使用。

二.糟糕的原始代码开发

  现在我们想编写一个用户服务系统,提供一个用于保存账户的方法,用户在UI类中调用相应的方法来实现保存账户的操作。

1.系统初始编码实现

(1)Dao层IUserDao接口及其实现类IUserDaoImpl

public interface IUserDao {
    public void saveAccount();
}

public class IUserDaoImpl implements IUserDao {
    @Override
    public void saveAccount() {
        System.out.println("账户保存了...");
    }
}

(2)Service层IUserService接口及其实现类IUserServiceImpl

public interface IUserService {
    public void saveAccount();
}

public class IUserServiceImpl implements IUserService {
    private IUserDao iUserDao = new IUserDaoImpl();
    @Override
    public void saveAccount() {
        iUserDao.saveAccount();
    }
}

(3)用户交互UI类IUserUi

public class IUserUi {
    public static void main(String[] args) {
        IUserService service = new IUserServiceImpl();
        service.saveAccount();
    }
}

2.这样编码的弊端

(1)程序之间存在高耦合,系统不同的模块之间通过大量的new等关键字建立了复杂的关系,这使得我们在修改或者拓展系统业务时,特别是代码量大的项目中,需要不断修改源码,常常面临着令人头皮发麻的连锁反应。

(2)存在严重的编译期依赖。如果我们缺少某些模块,则整个项目将会在编译期就无法通过,另一个侧面也反映了项目中存在高耦合,因此我们应该实现运行期依赖,即在运行期间动态地建立模块之间的关系,而不是写死的。

3.解决思路

(1)降低模块之间的耦合度,减少new关键字的使用。这里我们自然而然的想到了工厂模式,可以通过工厂来生产我们所需要的类,从而避免了new关键字的频繁使用。

(2)减少编译期依赖,不把代码写死。我们这里想一下在jdbc中,我们是通过如下方式去注册驱动的:

Class.forName("com.mysql.jdbc.Driver");//此处传入的只是一个字符串而已

此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。同时,也产生了一个新的问题,mysql驱动的全限定类名字符串是在java类中写死的,一旦要改还是要修改源码。所以解决这个问题也很简单,那就是使用配置文件配置。所以综上,我们可以通过解析配置文件,利用反射去动态地维护对象。

三.基于IOC的程序优化

1.优化思路分析

(1)基于上述的思路分析,首先我们创建工厂类BeanFactory来根据我们传入的对象名生产我们所需要的各种对象,从而消除new关键字的使用。

(2)那么工厂类怎么通过对象名来动态地创建对象呢?当然是通过反射机制来动态地创建对象,进一步降低耦合度

(3)为了不频繁的创建对象降低系统效率,同时也为了不把代码写死,我们把需要由工厂生产的类都写入配置文件中,让工厂类中的方法通过读取配置文件,把这些对象都创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。

(4)那么存哪去呢?我们选择Map来存储创建出来的对象,因为我们不仅需要存,还需要取。因此,我们这里就把这个存放所有对象的Map集合称为我们的容器。这时工厂就变成了专门初始化容器,并负责给我们从容器中获取指定对象的类。

2.代码实现

(1)编写配置文件bean.properties (key=value)

userService=com.sdust.service.impl.IUserServiceImpl
userDao=com.sdust.dao.impl.IUserDaoImpl

(2)编写工厂类BeanFactory

public class BeanFactory {
    //定义一个Properties对象
    private static Properties props;

    //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
    private static Map container;

    //使用静态代码块解析配置文件,初始化容器
    static {
        try{
            //实例化对象
            props = new Properties();
            //获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            //实例化容器
            container = new HashMap();
            //取出配置文件中所有的Key
            Enumeration keys = props.keys();
            //遍历枚举
            while (keys.hasMoreElements()){
                //取出每个Key
                String key = keys.nextElement().toString();
                //根据key获取value
                String beanPath = props.getProperty(key);
                //反射创建对象
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入容器中
                container.put(key,value);
            }
        }catch(Exception e){
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    /**
     * 根据bean的名称获取对象(单例对象)
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return container.get(beanName);
    }

    /**
     * 根据Bean的名称获取bean对象(多例对象)
     * @param beanName
     * @return
    public static Object getBean(String beanName){
        Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
            bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
        }catch (Exception e){
            e.printStackTrace();
        }
        return bean;
    }*/

}

(3)优化原始代码

public class IUserUi {
    public static void main(String[] args) {
        //IUserService service = new IUserServiceImpl();
        IUserService service = (IUserService) BeanFactory.getBean("userService");
        service.saveAccount();
    }
}

运行结果无误

3.总结

(1)原始代码设计中,我们是直接的主动的去获取我们所需要的对象,对象如何产生使用是直接在我们的具体控制范围内的,代码是僵硬的,写死的。

Spring(1) Spring配置开发以及IOC的基本原理_第1张图片

(2)优化后的代码设计中,我们获取对象时,需要跟工厂要,有工厂为我们查找或者创建对象。是被动的。对象的控制权从我们这里转移到了工厂和容器中,这种思想被称为控制反转,这也是Spring的重要核心之一。

Spring(1) Spring配置开发以及IOC的基本原理_第2张图片

四.使用Spring的IOC配置开发

1.导入Spring的依赖坐标spring-context

2.编写Spring的配置文件spring-beans.xml




    
    

    

3.获取Spring核心容器对象,解析配置文件,获取bean对象

public class IUserUi {
    public static void main(String[] args) {
        //1.获取spring核心容器对象,解析配置文件
        /**
         * spring核心容器ApplicationContext有三个实现类:
         *      1.ClassPathXmlApplicationContext : 他负责加载本项目 类路径 下的配置文件(推荐)
         *      2.FileSystemXmlApplicationContext : 他负责加载任意 磁盘路径 下的配置文件(必须有访问权限)
         *      3.AnnotationConfigApplicationContext : 他用于加载注解配置
         */
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-beans.xml");
        //2.根据key获取bean对象
        IUserDao dao = (IUserDao)ac.getBean("userDao");
        IUserService service = ac.getBean("userService",IUserService.class);

        System.out.println(dao);
        System.out.println(service);
    }
}

五.Spring的配置细节与拓展

1.bean对象的三种创建方式

(1)使用默认构造函数创建

在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。


      

(2)使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

/**
 * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
 */
public class InstanceFactory {

    public IUserService getAccountService(){
        return new IUserServiceImpl();
    }
}
 
    
    

(3)使用工厂中的 静态方法 创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

**
 * 模拟一个静态工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
 */
public class StaticFactory {

    public static IUserService getAccountService(){

        return new IUserServiceImpl();
    }
}

    

2.bean对象的作用范围与生命周期

(1)作用范围

 bean标签的scope属性用于指定bean的作用范围。取值如下( 常用的就是单例的和多例的):

 singleton:单例的(默认值)
 prototype:多例的
 request:作用于web应用的请求范围
 session:作用于web应用的会话范围
 global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session



    

(2)生命周期 

 单例对象:
                出生:当容器创建时对象出生
                活着:只要容器还在,对象一直活着
                死亡:容器销毁,对象消亡
                总结:单例对象的生命周期和容器相同
多例对象;
                出生:当我们使用对象时spring框架为我们创建
                活着:对象只要是在使用过程中就一直活着。
                死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收

    

3.Spring中的依赖注入(DI)

 IOC的主要作用是降低程序间的耦合(依赖关系),但不能完全消除依赖关系,所以我们将依赖关系以后都交给spring来维护和管理, 在当前类需要用到其他类的对象时,由spring为我们提供注入,我们只需要在配置文件中说明即可。这种依赖关系的维护就称之为依赖注入(DI)。

能注入的数据:有三类
                基本类型和String
                其他bean类型(在配置文件中或者注解配置过的bean)
                复杂类型/集合类型
注入的方式:有三种
                第一种:使用构造函数提供
                第二种:使用set方法提供
                第三种:使用注解提供(后面讲)

(1) 构造函数注入

<1>修改IUserServiceImpl代码,编写构造函数

public class IUserServiceImpl implements IUserService {
    private String serviceName;
    private Integer serviceId;
    private IUserDao userDao;

    public IUserServiceImpl(String serviceName,Integer serviceId,IUserDao userDao){
        this.serviceName = serviceName;
        this.serviceId = serviceId;
        this.userDao = userDao;
    }

    @Override
    public void saveAccount() {
        userDao.saveAccount();
        System.out.println(serviceId + "----" + serviceName);
    }
}

<2>修改配置文件,配置构造函数注入

构造函数注入使用的标签是 < constructor-arg > ,标签出现的位置在bean标签的内部,其中标签中的属性如下所示:

type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:用于指定给构造函数中指定名称的参数赋值(常用的)
=============以上三个用于指定给构造函数中哪个参数赋值===============================
value:直接赋值。用于提供基本类型和String类型的数据
ref:引用对象赋值。用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象

    
    
    
        
        
        
    

(2)Set方法注入

<1>修改IUserServiceImpl代码,生成set方法

public class IUserServiceImpl implements IUserService {
    private String serviceName;
    private Integer serviceId;
    private IUserDao userDao;
    

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }


    public void setServiceId(Integer serviceId) {
        this.serviceId = serviceId;
    }
    
    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void saveAccount() {
        userDao.saveAccount();
        System.out.println(serviceId + "----" + serviceName);
    }
}

<2>修改配置文件,配置set方法注入

set方法注入(更常用的方式)涉及的标签为 < property > ,出现的位置在bean标签的内部 ,其标签的属性如下所示:

name:用于指定注入时所调用的set方法名称
value:直接赋值.用于提供基本类型和String类型的数据
ref:引用对象赋值。用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象

    

    
        
        
        
    

 

你可能感兴趣的:(-----SSM框架-----)