使用spring的IOC解决程序耦合的方法

 更新时间:2019年05月20日 15:32:26   作者:南方菇凉  
这篇文章主要介绍了使用spring的IOC解决程序耦合的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。 那么,这个读取配置文件,创建和获取三层对象的类就是工厂。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。简单点说就是用来创建具有相同基类的对象

简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,但是其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码将会非常复杂。 简单工厂模式适用情况包括:客户端只知道传入工厂类的参数,对于如何创建对象不关心;工厂类负责创建的对象比较少。

1、程序的耦合

(划分模块的一个准则就是高内聚低耦合) 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。

模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。

耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

<?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-3.1.xsd ">
   
   <!-- 定义userDaoImpl对象,并指定id为userDaoImpl -->
   <bean id="userDaoImpl" class="cn.bdqn.biz.dao.impl.UserDaoImpl" />
   <!-- 定义User对象,并指定id为user -->
   <bean id="user" class="cn.bdqn.biz.pojo.User" />
   
   <!-- 定义UserServiceImpl对象,并指定id为userServiceImpl -->
   <bean id="userServiceImpl" class="cn.bdqn.biz.service.impl.UserServiceImpl">
     <!--为userServiceImpl的userDao属性赋值,需要注意的是,这里要调用setUserDao()方法 -->
     <property name="userDao">
       <!-- 引用id为userDao的对象为userServiceImpl的userDao属性赋值 -->
       <ref bean="userDaoImpl" />
     </property>
   </bean>
   
 </beans>

2、解决程序耦合的思路

1、当是我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串

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

3、代码实现:持久层,业务层,变现层

当我们讲解jdbc时,是通过反射来注册驱动的,代码如下: Class.forName("com.mysql.jdbc.Driver"); 这时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译。但是因为没有驱动类,所以不能运行。

不过,此处也有个问题,就是我们反射类对象的全限定类名字符串是在java类中写死的,一旦要改还是要修改源码。 解决这个问题也很简单,使用配置文件配置。

在实际开发中我们可以把所有的dao和service和action对象使用配置文件配置起来,当启动服务器应用加载的时候,通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。

package com.ioc.spring.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 获取某个包下面的所有类信息
 */
public class Util {

  /**
  * 取得某个接口下所有实现这个接口的类
  */
  public static List<Class> getAllClassByInterface(Class c) {
   List<Class> returnClassList = null;

   if (c.isInterface()) {
     // 获取当前的包名
     String packageName = c.getPackage().getName();
     // 获取当前包下以及子包下所以的类
     List<Class<?>> allClass = getClasses(packageName);
     if (allClass != null) {
      returnClassList = new ArrayList<Class>();
      for (Class classes : allClass) {
        // 判断是否是同一个接口
        if (c.isAssignableFrom(classes)) {
         // 本身不加入进去
         if (!c.equals(classes)) {
           returnClassList.add(classes);
         }
        }
      }
     }
   }

   return ClassList;
  }

  /*
  * 取得某一类所在包的所有类名 不含迭代
  */
  public static String[] getPackageAllClassName(String classLocation, String packageName) {
   // 将packageName分解
   String[] packagePathSplit = packageName.split("[.]");
   String realClassLocation = classLocation;
   int packageLength = packagePathSplit.length;
   for (int i = 0; i < packageLength; i++) {
     realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
   }
   File packeageDir = new File(realClassLocation);
   if (packeageDir.isDirectory()) {
     String[] allClassName = packeageDir.list();
     return allClassName;
   }
   return null;
  }

  /**
  * 从包package中获取所有的Class
  * @param packageName
  * @return
  */
  public static List<Class<?>> getClasses(String packageName) {

   // 第一个class类的集合
   List<Class<?>> classes = new ArrayList<Class<?>>();
   // 是否循环迭代
   boolean recursive = true;
   // 获取包的名字 并进行替换
   String packageDirName = packageName.replace('.', '/');
   // 定义一个枚举的集合 并进行循环来处理这个目录下的things
   Enumeration<URL> dirs;
   try {
     dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
     // 循环迭代下去
     while (dirs.hasMoreElements()) {
      // 获取下一个元素
      URL url = dirs.nextElement();
      // 得到协议的名称
      String protocol = url.getProtocol();
      // 如果是以文件的形式保存在服务器上
      if ("file".equals(protocol)) {
        // 获取包的物理路径
        String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
        // 以文件的方式扫描整个包下的文件 并添加到集合中
        findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
      } else if ("jar".equals(protocol)) {
        // 如果是jar包文件
        // 定义一个JarFile
        JarFile jar;
        try {
         // 获取jar
         jar = ((JarURLConnection) url.openConnection()).getJarFile();
         // 从此jar包 得到一个枚举类
         Enumeration<JarEntry> entries = jar.entries();
         // 同样的进行循环迭代
         while (entries.hasMoreElements()) {
           // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
           JarEntry entry = entries.nextElement();
           String name = entry.getName();
           // 如果是以/开头的
           if (name.charAt(0) == '/') {
            // 获取后面的字符串
            name = name.substring(1);
           }
           // 如果前半部分和定义的包名相同
           if (name.startsWith(packageDirName)) {
            int idx = name.lastIndexOf('/');
            // 如果以"/"结尾 是一个包
            if (idx != -1) {
              // 获取包名 把"/"替换成"."
              packageName = name.substring(0, idx).replace('/', '.');
            }
            // 如果可以迭代下去 并且是一个包
            if ((idx != -1) || recursive) {
              // 如果是一个.class文件 而且不是目录
              if (name.endsWith(".class") && !entry.isDirectory()) {
               // 去掉后面的".class" 获取真正的类名
               String className = name.substring(packageName.length() + 1, name.length() - 6);
               try {
                 // 添加到classes
                 classes.add(Class.forName(packageName + '.' + className));
               } catch (ClassNotFoundException e) {
                 e.printStackTrace();
               }
              }
            }
           }
         }
        } catch (IOException e) {
         e.printStackTrace();
        }
      }
     }
   } catch (IOException e) {
     e.printStackTrace();
   }

   return classes;
  }

  /**
  * 以文件的形式来获取包下的所有Class
  * 
  * @param packageName
  * @param packagePath
  * @param recursive
  * @param classes
  */
  public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
     List<Class<?>> classes) {
   // 获取此包的目录 建立一个File
   File dir = new File(packagePath);
   // 如果不存在或者 也不是目录就直接返回
   if (!dir.exists() || !dir.isDirectory()) {
     return;
   }
   // 如果存在 就获取包下的所有文件 包括目录
   File[] dirfiles = dir.listFiles(new FileFilter() {
     // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
     public boolean accept(File file) {
      return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
     }
   });
   // 循环所有文件
   for (File file : dirfiles) {
     // 如果是目录 则继续扫描
     if (file.isDirectory()) {
      findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
         classes);
     } else {
      // 如果是java类文件 去掉后面的.class 只留下类名
      String className = file.getName().substring(0, file.getName().length() - 6);
      try {
        // 添加到集合中去
        classes.add(Class.forName(packageName + '.' + className));
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
     }
   }
  }
}

上面解耦的思路有2个问题:

1、存哪去? 分析:由于我们是很多对象,肯定要找个集合来存。这时候有Map和List供选择。到底选Map还是List就看我们有没有查找需求。有查找需求,选Map。 所以我们的答案就是: 在应用加载时,创建一个Map,用于存放action,Service和dao对象。我们把这个map称之为容器。

2、还是没解释什么是工厂? 工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。 原来,我们在获取对象时,都是采用new的方式。是主动的。现在:我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象是被动的。

这种被动接收的方式获取对象的思想就是控制反转,它是spring框架的核心之一。它的作用只有一个:削减计算机程序的耦合。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:

相关文章

  • Struts1教程之ActionMapping_动力节点Java学院整理

    Struts1教程之ActionMapping_动力节点Java学院整理

    这篇文章主要介绍了Struts1教程之ActionMapping,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Java数据结构之实现跳表

    Java数据结构之实现跳表

    今天带大家来学习Java数据结构的相关知识,文中对用Java实现跳表作了非常详细的图文解说及代码示例,对正在学习java的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-05-05
  • Java强制类型转换原理详解(父类转子类、子类转父类)

    Java强制类型转换原理详解(父类转子类、子类转父类)

    这篇文章主要给大家介绍了关于Java强制类型转换原理(父类转子类、子类转父类)的相关资料,所谓的强制类型转换,其实是自动类型转换的逆过程,在数据类型兼容的情况下,将容量大的数据类型转换为容量小的数据类型,需要的朋友可以参考下
    2023-12-12
  • mybatis的insert插入后获取自增id的方法详解(从controller到mapper)

    mybatis的insert插入后获取自增id的方法详解(从controller到mapper)

    这篇文章主要介绍了mybatis的insert插入后获取自增id的示例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-10-10
  • logback过滤部分日志输出的操作

    logback过滤部分日志输出的操作

    这篇文章主要介绍了logback过滤部分日志输出的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • springboot整合redis进行数据操作(推荐)

    springboot整合redis进行数据操作(推荐)

    springboot整合redis比较简单,并且使用redistemplate可以让我们更加方便的对数据进行操作。下面通过本文给大家分享springboot整合redis进行数据操作的相关知识,感兴趣的朋友一起看看吧
    2017-10-10
  • jmeter正则表达式提取器的用法与正则详解

    jmeter正则表达式提取器的用法与正则详解

    在使用Jmeter过程中,会经常使用到正则表达式提取器提取器,下面这篇文章主要给大家介绍了关于jmeter正则表达式提取器的用法与正则的相关资料,需要的朋友可以参考下
    2022-07-07
  • 浅谈普通for循环遍历LinkedList弊端

    浅谈普通for循环遍历LinkedList弊端

    下面小编就为大家带来一篇浅谈普通for循环遍历LinkedList弊端。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • 基于IntelliJ IDEA的类注释和方法注释操作

    基于IntelliJ IDEA的类注释和方法注释操作

    这篇文章主要介绍了基于IntelliJ IDEA的类注释和方法注释操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • JAVA容器集合全面解析(Collection和Map)

    JAVA容器集合全面解析(Collection和Map)

    这篇文章主要介绍了JAVA容器集合全面解析(Collection和Map)本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08

最新评论