Spring框架:简述Spring及基于Xml的IOC配置

本人小白一枚,欢迎大家一起讨论学习,如有错误,还望大家指教。

Spring概述

什么是spring
Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programmaing:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
spring的优点

  • 方便解耦,简化开发
    通过Spring提供的IOC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的耦合。用户也不再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
  • AOP编程的支持
    通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
  • 声明式事务的支持
    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
  • 方便程序的测试
    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可以做的事情。
  • 方便集成各种优秀框架
    Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
  • 降低JavaEE API的使用难度
    Spring对JavaEE API(如JDBC、JavaMail、远程调用等等)进行了薄薄的封装层,是这些API的使用难度大大降低。
  • Java源码是经典学习的典范
    Spring的源代码设计精妙、结构清晰、匠心独用,处处体现这大师对Java设计模式灵活运行以及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实现的范例。

spring的体系结构

在这里插入图片描述

IoC的概念和作用

简述程序的耦合和解耦
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立 性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个 准则就是高内聚低耦合。 它有如下分类:

  1. 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另 一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
  2. 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大 量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
  3. 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传 递该全局变量的信息,则称之为外部耦合。
  4. 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进 行适当的动作,这种耦合被称为控制耦合。
  5. 标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间 存在一个标记耦合。
  6. 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形 式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
  7. 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实 现的。

总结: 耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须 存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。 内聚与耦合 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从 功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之 间的相互依存度却要不那么紧密。 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。

演示案例: 在我们进行JDBC操作时,当注册驱动时,为什么要用反射的方式即(Class.forName)进行呢,为什么不用DriverManager 的register 方法?

public static void main(String[] args) throws Exception {
        // 注册驱动
        //DriverManager.registerDriver(new Driver());
        Class.forName("com.mysql.jdbc.Driver");
        // 获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///frame?characterEncoding=utf-8", "root", "root");
        // 获取预处理对象
        PreparedStatement statement = connection.prepareStatement("SELECT * FROM account");
        // 执行SQL,并得到结果集
        ResultSet resultSet = statement.executeQuery();
        // 遍历结果集
        while (resultSet.next()) {
            System.out.println(resultSet.getString("name"));
        }
    }

原因及解决办法:
如果我们使用DriverManager 的register 方法,这里依赖的是MySQL的驱动类,如果我们将数据库换成Oracle或者其他,那么我们需要修改源代码来重新加载驱动,这肯定不是我们想要的。在原先JDBC的学习中,我们通常会将驱动的全限定类名以及url和数据库的用户名和密码配置在文件中,我们通过读取配置文件来进行操作。同样在我们实际开发中,是不是也可以把三层的对象都用配置文件配置起来,当启动服务应用加载的时候,读取配置文件并将对象创建并保存起来,在我们使用时,就可以直接拿过来用了,那么在读取配置文件,创建和获取三层对象的类就是工厂,也就是使用工厂模式来解耦。

  • 创建工厂类,用工厂类通过读取配置文件来反射创建对象,并将对象放在一个map容器中,当我们使用对象时,通过map的key来获取对应的对象,并在resources目录添加bean.properties配置文件。
public class BeanFactory {
    // 用来装载对象的容器
    private static Map<String, Object> beansMap;

    /**
     * 使用静态代码块来实现当类加载时,通过读取配置文件反射创建对象,并将对象存放在map容器内,并且对象都是单例的。
     */
    static {
        InputStream in;
        Properties properties;
        try {
            beansMap = new HashMap<String, Object>();
            in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties = new Properties();
            properties.load(in);
            Enumeration<Object> keys = properties.keys();
            while (keys.hasMoreElements()) {
                String key = (String) keys.nextElement();
                String value = properties.getProperty(key);
                Object bean = Class.forName(value).newInstance();
                beansMap.put(key, bean);
            }
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    /**
     * 通过key来获取对应的对象
     */
    public static Object getBean(String key) {
        return beansMap.get(key);
    }
}

accountService=service.impl.AccountServiceImpl
accountDao=dao.impl.AccountDaoImpl
  • 创建持久层接口并添加sava方法同时实现该接口,这里只是简单模拟下三层模式的开发。
public interface AccountDao {
    void saveAccount();
}
public class AccountDaoImpl implements AccountDao {
    public void saveAccount() {
        System.out.println("保存成功!");
    }
}
  • 创建service层接口并添加sava方法同时实现该接口,在实现类中调用持久层的save方式,这里使用工厂创建持久层对象。
public interface AccountService {
    void saveAccount();
}
public class AccountServiceImpl implements AccountService {
    private AccountDao dao = (AccountDao) BeanFactory.getBean("accountDao");
    
    @Override
    public void saveAccount() {
       dao.saveAccount();
    }
}
  • 在controller层创建测试方法并查看测试结果,这里使用的service方法也是用工厂进行创建的。
public class AccountDemo {
    public static void main(String[] args) {
        AccountService service = (AccountService) BeanFactory.getBean("accountService");
        service.saveAccount();
    }
}

打印结果
在这里插入图片描述
项目结构
在这里插入图片描述
在这里插入图片描述
疑问: 在编写以上的代码时,我遇到一个问题,就是在我的bean.properties配置文件中,我先写的accountDao,后写的accountService如上图所示,在运行测试代码时,提示我空指针异常,在service实现类中,dao变量没有在工厂中获取到对应的值为null,所以在dao调用saveAccount方法时会报出空指针异常。通过断点测试我发现,在读取配置文件时,它会从下到上进行读取,也就是说,它会先初始化service对象,在service对象中的dao成员变量会在工厂的容器中获取dao对象,但此时容器中并没有该对象,所以service中的dao成员变量就为null,之后我就将配置文件中的dao和service位置对换了一下,再次运行没有问题,在这里记录一下这个问题,对于这个问题为什么是这样,我也不是很清楚,大概猜测在执行Enumeration<Object> keys = properties.keys();这段代码时,它会从下到上获取到key值。
总结: 这里使用了工厂为我们创建对象,这时候我们获取对象的方式发生了变化,原先我们在获取对象时,都是采用new的方式,是主动的。现在我们获取对象时,是工厂在为我们创建对象,是被动的。这种被动接收的方式获取对象的思想就是控制反转,它是spring框架的核心之一。IOC的作用:帮我们消减计算机程序的耦合(解除我们代码中的依赖关系)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用springIOC解决程序的耦合

同样使用上面的例子,模拟三层结构的开发。使用springIOC为我们创建对象。因为和上述的功能大体相同,我这里就简单概述一下了。

  • 在pom文件中添加spring的坐标,注意这里使用的版本是spring5.0.2,因为spring5版本使用jdk1.8编写的,所以要求我们jdk版本是8以上,同时tomcat的版本要求8.5以上。
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
</dependencies>
  • 创建spring配置文件,先添加约束,在配置要注入的对象。
<?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="accountDao" class="dao.impl.AccountDaoImpl"/>
    <bean id="accountService" class="service.impl.AccountServiceImpl"/>
</beans>
  • 创建持久层接口并创建实现该接口的实现类。
public interface AccountDao {
    void saveAccount();
}
public class AccountDaoImpl implements AccountDao {
    @Override
    public void saveAccount() {
        System.out.println("保存成功!");
    }
}
  • 创建服务层接口并创建实现该接口的实现类。
public interface AccountService {
    void saveAccount();
}
public class AccountServiceImpl implements AccountService {
    private AccountDao dao;

    @Override
    public void saveAccount() {
       dao.saveAccount();
    }
}
  • 创建表现层,并添加测试类。
public class AccountDemo {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountService service = ac.getBean("accountService", AccountServiceImpl.class);
        AccountDao dao = (AccountDao) ac.getBean("accountDao");
        System.out.println(service);
        System.out.println(dao);
    }
}

在这里插入图片描述
在这里插入图片描述
Spring基于xml的IOC的细节
通过上述能够发现,我们使用xml方式能够让spring为我们创建对象。对于在测试类中我们使用的接口ApplicationContext实现类ClassPathXmlApplicationContext以及以下其他细节我们阐述一下,通过下图可以看下sping中工厂的类结构图。
在这里插入图片描述
在这里插入图片描述
通过上图我们就会知道BeanFactory 才是spring容器的顶层接口,ApplicationContext是它的子接口,那么二者又有什么区别吗?

  • ApplicationContext:它在创建核心容器时,创建对象采取的策略是采用立即加载的方式,也就是说,只要一读取完配置文件立马就会创建在配置文件中配置的对象,为了减少反复创建对象,这里使用单例模式,也就是说每个被配置的对象只存在一个在容器中,就像上述我们自己实现工厂类的思路一样。
  • BeanFactory:它在创建核心容器时,创建对象采取的策略是延迟加载的方式,也就是什么时候获取对象时,什么时候真正创建对象,这里采用多例模式。

简述ApplicationContext 接口常用的实现类:

  • ClassPathXmlApplicationContext:从类的根路径下加载配置文件,推荐使用这种。
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置(必须要有访问权限)。
  • AnnotationConfigApplicationContext:当我们使用功能注解配置容器对象时,需要使用该类创建spring容器。

bean标签

  • bean标签:该标签的作用用于配置对象让spring帮我们创建,默认情况下它调用的是类中无参构造函数,如果没有无参构造函数菜不能创建成功。该标签有以下属性:
    • id:给对象在容器中提供一个唯一标识,用于获取对象,因为容器是个map所以该属性就是map的key。
    • class:指定类的全限定类名,用于反射对象,默认情况下调用无参构造函数。
    • scope:指定对象的作用范围,有以下五种属性值。
      • singleton:单例,也就是当容器创建时对象出生,只要容器还在则对象就在,当容器销毁时对象也随之销毁,默认值。
      • prototype:多例,当我们使用对象时spring才帮我们创建,对象被使用时,对象则一直存在,当对象长时间不用,且没有别的对象引用时,就会被Java的垃圾回收器回收。
      • request:作用于web应用的请求范围。
      • session:作用于web应用的会话范围。
      • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session。
    • init-method:指定类中的初始化方法的名称。
    • destory-method:指定类中销毁方法的名称。

实例化Bean的三种方式

  • 使用无参构造函数,默认就是使用无参构造函数创建对象,如果对象中没有无参构造,则会创建失败。
<bean id="accountService" class="service.impl.AccountServiceImpl"/>
  • 使用静态工厂的方法创建对象。
public class StaticFactory {
    public static AccountDao creatAccountDao() {
        return new AccountDaoImpl();
    }
}
<bean id="accountDao" class="factory.StaticFactory" factory-method="creatAccountDao"/>
  • 使用实例工厂的方法创建对象。
public class InstanceFactory {
    public AccountDao createAccountDao() {
        return new AccountDaoImpl();
    }
}
<bean id="instanceFactory" class="factory.InstanceFactory"/>
<bean id="accountDao" factory-bean="instanceFactory" factory-method="createAccountDao"/>

spring的依赖注入
依赖注入,Dependency Injection,它是spring框架核心ioc的具体体现。在我们程序编写时,,通过控制反转把对象创建并注入到容器中,ioc解耦只是降低程序间的耦合,并不会消除,如果代码中出现依赖关系,那我们怎么解决呢?例如我们的业务层调用持久层。简单地说,就是坐等框架把持久层对象传入到业务层,而不是用我们自己去获取持久层对象。

  • 构造函数注入:就是使用类中的构造函数给成员变量赋值,注意:赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架为我们注入。
    • constructor-arg:构造函数注入,在bean标签内部,它有以下属性:
      • type:用于指定要注入数据的数据类型,该类型也是构造函数中某个或某些参数的类型。
      • index:用于给构造函数中指定的索引位置的参数进行赋值,所以从0开始。
      • name:用于给构造函数中指定名称的参数赋值,这个也是最常用的。
      • value:用于给基本数据类型和String类型进行赋值。
      • ref:用于指定其他bean类型的数据,指的是在spring中IOC核心容器中出现过的对象。
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="刘灰灰"/>
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="birthday" ref="now"/>
    </bean>
    <bean id="now" class="java.util.Date"/>
public class AccountServiceImpl implements AccountService {

    private String name;
    private int age;
    private Date birthday;

    public AccountServiceImpl(String name, int age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    @Override
    public void saveAccount() {
        System.out.println(name + ":" + age + ":" + birthday);
    }
}

测试并查看结果

public class AccountDemo {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountService service = ac.getBean("accountService", AccountServiceImpl.class);
        service.saveAccount();
}

在这里插入图片描述

  • property:使用set方法注入,注意使用该方法时,要注入的类中要含有无参构造器,因为spring是通过无参构造器反射出对象,之后使用set方法进行赋值。
    • name:找的是类中set方法后面的部分,并且首字母小写,例如:setUsername,则写username即可。
    • ref:给其他引用类型赋值。
    • value:给基本数据类型和String类型进行赋值。
<bean id="accountService" class="service.impl.AccountServiceImpl">
    <property name="name" value="刘灰灰"/>
    <property name="age" value="18"/>
    <property name="birthday" ref="now"/>
</bean>
<bean id="now" class="java.util.Date"/>

在这里插入图片描述

  • p:propertyName:使用p名称空间注入数据(本质还是调用set方法),首先我们得在约束文件中加入部分约束。
    在这里插入图片描述
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       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="accountService" class="service.impl.AccountServiceImpl" p:name="刘灰灰" p:age="18" p:birthday-ref="now"/>
    <bean id="now" class="java.util.Date"/>
</beans>

注入集合属性
对类中的集合成员进行赋值,这里同样使用set方法注入。

public class AccountServiceImpl implements AccountService {

   private String[] myStrs;
   private List<String> myList;
   private Set<String> mySet;
   private Map<String, String> myMap;
   private Properties myProps;

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }

    @Override
    public void saveAccount() {
        System.out.println("myStrs:" + Arrays.toString(myStrs));
        System.out.println("myList:" + myList);
        System.out.println("mySet:" + mySet);
        System.out.println("myMap:" + myMap);
        System.out.println("myProps:" + myProps);
    }
}
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
        <property name="myStrs">
            <set>
                <value>myStrsA</value>
                <value>myStrsB</value>
                <value>myStrsC</value>
            </set>
        </property>
        <property name="myList">
            <array>
                <value>myListA</value>
                <value>myListB</value>
                <value>myListC</value>
            </array>
        </property>
        <property name="mySet">
            <list>
                <value>mySetA</value>
                <value>mySetB</value>
                <value>mySetC</value>
            </list>
        </property>
        <property name="myMap">
            <props>
                <prop key="myMapA">AAA</prop>
                <prop key="myMapB">BBB</prop>
            </props>
        </property>
        <property name="myProps">
            <map>
                <entry key="myPropsA" value="AAAA"/>
                <entry key="myPropsB" value="BBBB"/>
            </map>
        </property>
    </bean>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值