AI 摘要

文章系统梳理 Spring 轻量级企业框架的诞生背景、模块体系与两大核心技术:IoC 通过依赖注入与工厂解耦,实现 Bean 生命周期托管;AOP 借切面、切入点、通知等概念,把日志、事务等横切逻辑动态织入业务对象,全程以 XML 配置示例演示从 HelloWorld 到打印机装配再到日志增强的完整流程,为后续深入 SSM 开发奠定理论与实战基础。

Spring 概述

Spring 历史由来

Spring 是一个轻量级的企业级应用框架,兴起于 2003 年,当时流行的传统 JavaEE 框架均为过于臃肿的重量级架构体系,其开发效率、开发难度和实际性能都不能满足人们的需求。

Spring 中文含义为 “春天”,其诞生给人一种格外清新的感觉,蕴含着勃勃生机。

Spring 最初由 Rod Johnson 在《Expert One-on-One Java EE 设计与开发》一书中的部分理念衍生而来的,在书中,他对 Java EE 技术的日益臃肿和低效提出了质疑,并提出了 Interface 21,即 Spring 的雏形。

2003 年 2 月,Spring 正式成为一个开源项目,并发布于 SourceForge 中。

Spring 文档:https://www.spring.io/

Spring 是企业应用开发的一站式选择,贯穿于表现层、业务层、持久层。

Spring 的优点:

  • 低侵入式设计:Spring 通过依赖注入、面向接口编程等方式管理对象关系,业务代码无需大量耦合特定 Spring 框架代码,改动实现细节时对依赖它的其他组件影响小。
  • 独立于各种应用服务器:Spring 基于自身的轻量级容器和组件化架构,能在多种应用服务器(如 Tomcat、Jetty 等)上运行,无需依赖特定应用服务器的独有特性来实现核心功能。
  • 依赖注入特性将组件关系透明化,降低耦合度。
  • 面向切面编程特性允许将通用任务进行集中式处理。
  • 与第三方框架的良好整合:Spring 像是一个超级黏合平台,能方便且灵活地与各类如 MyBatis、Hibernate 等第三方框架协同工作,实现功能互补、优势叠加。

Spring 体系结构

Spring 由大约 20 个功能模块组成。

Spring 核心容器 Core Container:

  • Beans 模块:负责创建和管理对象(即 Java Bean)。
  • Core 模块:Spring 的核心基础,提供了一些最基本的功能和工具,如对资源的访问、读取配置文件、处理一些基础的类型转换、事件传播等功能。
  • Context 模块:Spring 的大管家,提供提供应用上下文环境、负责管理 Bean 的生命周期等。
  • SpEL 模块(Spring Expression Language):专门用来在 Spring 里写表达式的语言。

数据访问与集成 Data Access and Integration:

  • JDBC 模块:简化了使用 JDBC 进行数据库访问的操作,让开发人员能更便捷地执行 SQL 语句、处理结果集以及管理数据库连接等相关事宜。
  • ORM 模块:用于无缝衔接 Spring 框架与各类 ORM 工具(如 MyBatis 等),使在 Spring 应用中利用 ORM 技术进行对象关系映射及数据操作更加方便、高效。
  • OXM 模块:用于在 Spring 框架中方便地实现对象与 XML 数据之间的相互转换以及对 XML 相关处理进行简化和规范化操作。
  • JMS 模块:便于在 Spring 应用中实现与消息队列系统进行高效的消息发送、接收以及对 JMS(Java Message System)相关操作的管理和集成。
  • Transactions:用于在 Spring 应用中方便地管理和控制数据库事务,提供了声明式和编程式两种方式来确保数据操作的一致性、隔离性等事务特性。

Spring Web:

  • Web 模块:为 Spring 应用提供了处理 Web 请求与响应、实现 Web 应用相关功能(如路由、视图处理等)的能力,以便构建功能完备的 Web 应用程序。
  • Servlet 模块:用于让 Spring 应用更好地与 Servlet 容器(如 Tomcat 等)协同工作,方便在基于 Servlet 技术的 Web 应用中实现请求处理、资源调度等功能。
  • WebSocket 模块:让 Spring 应用便捷地实现与浏览器等客户端间实时、双向的 WebSocket 通信功能,用于高效的数据交互与实时信息传递。
  • Portlet 模块:助力在 Spring 应用中方便地开发、部署和管理基于 Portlet 规范的 Web 组件,以实现可嵌入到门户页面(Portal)中的特定功能。

Spring 其他:

  • AOP:用于在不修改原始代码的基础上,通过定义切面实现对业务逻辑中横切关注点(如日志记录、事务管理等)的统一处理,增强代码的可维护性和功能扩展性。
  • Aspects:在 Spring 框架中用于方便地实现基于 AspectJ 的 AOP(面向切面编程)功能,辅助定义切面、切点及增强处理等以处理横切关注点。
  • Instrumentation:用于在 Spring 应用中实现对 Java 字节码的动态修改、监测等操作,以便更好地进行性能分析、运行时监控等。
  • Messaging:用于让 Spring 应用方便地进行消息的发送、接收以及对消息传递相关流程(如队列管理等)进行有效管理与集成。
  • Test:用于在 Spring 应用中便捷地开展各类测试工作,包括对 Bean、业务逻辑及 Web 请求处理等方面的测试,简化测试流程并提供相关测试工具。

Spring 核心

Spring 设计理念:面向 Bean 的编程,Bean 即被 Spring 容器所管理的 Java 对象。

Spring 两大核心技术:

  • 控制反转 IoC 和依赖注入 DI。
  • 面向切面编程 AOP。

Spring IoC

IoC 概述

IoC 全称 Inversion of Control,即控制反转,意为把对资源的控制权反转过来。IoC 不是一项开发技术,也不是具体功能,而是面向对象编程中的一种设计理念。

DI 全称 Dependency Injection,即依赖注入,表示将依赖的对象注入需要的类中,是 IoC 设计思想的一种具体实现方式。

控制反转初体验

用户模块业务逻辑层调用数据访问层,在用户模块业务逻辑层中要定义一个数据访问层对象,模块之间的耦合度较高,可以使用工厂家族(简单工厂或工厂方法)解决业务逻辑层和数据访问层之间的耦合性问题。


示例:控制反转初体验

用户模块数据访问接口(cn.duozai.dao.UserDao):

public interface UserDao {

    /**
     * 保存用户信息
     *
     * @return SQL语句影响行数
     */
    int save();

}

用户模块数据访问接口实现类(cn.duozai.dao.UserDaoImpl):

public class UserDaoImpl implements UserDao {

    /**
     * 保存用户信息
     *
     * @return SQL语句影响行数
     */
    @Override
    public int save() {
        System.out.println("保存用户信息到数据库");
        return 1;
    }

}

用户模块数据访问层工厂(cn.duozai.factory.UserDaoFactory):

public class UserDaoFactory {
  
    /**
     * 负责创建用户DAO实例
     *
     * @return 用户模块数据访问层实现类
     */
    public static UserDao getInstance() {
        return new UserDaoImpl();
    }

}

用户模块业务逻辑接口(cn.duozai.service.UserService):

public interface UserService {

    /**
     * 保存用户信息
     *
     * @return 保存结果
     */
    boolean save();

}

用户模块业务逻辑接口实现类(cn.duozai.service.UserServiceImpl):

public class UserServiceImpl implements UserService {

    // 实例化依赖的UserDao(自己实例化)
    // UserDao userDao = new UserDaoImpl();

    // 实例化依赖的UserDao(通过工厂获取,实例化UserDao的权限交给工厂)
    UserDao userDao = UserDaoFactory.getInstance();

    /**
     * 保存用户信息
     *
     * @return 保存结果
     */
    @Override
    public boolean save() {
        // 调用userDao的方法保存用户信息
        int result = userDao.save();
        return result > 0;
    }

}

用户模块业务逻辑层调用数据访问层,原本需要在业务逻辑层中实例化数据访问层对象,现在对于数据访问层实例化的工作,交给了工厂,即业务逻辑层获取依赖对象时的 “控制权/实例化权” 发生了反转,这就是所谓的控制反转。

第一个 Spring IoC 程序

实现 Spring IoC 的基本步骤:

  • 导入 JAR 依赖包:Spring 相关 JAR 文件。
  • 创建项目,并将所需的 JAR 文件添加为库。
  • 编写相关类。
  • 编写 Spring 核心配置文件 applicationContext.xml。
  • 编写测试类、测试方法。

示例:第一个 Spring IoC 程序

导入相关依赖 JAR 文件,并添加为库。

示例效果:

Log4J 配置文件(classpath:log4j.properties):

log4j.rootLogger=debug,CONSOLE

##################
# Console Appender
##################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=debug
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= - (%r ms) - %d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n

HelloSpring 实体类(cn.duozai.HelloSpring):

public class HelloSpring {

    private static final Logger logger= Logger.getLogger(HelloSpring.class);

    /**
     * hello属性
     */
    private String hello;

    /**
     * 输出方法
     */
    public void print() {
        logger.debug("Spring说:" + hello);
    }

    public String getHello() {
        return hello;
    }

    public void setHello(String hello) {
        this.hello = hello;
    }

}

Spring 核心配置文件(classpath: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">
    <!--
        一般情况下:类型 对象名 = new 类型();
        控制反转:通过<bean>标签声明需要Spring创建的实例,让Spring实例化对象
        class属性:指定该对象实例的类型
        id属性:该对象实例的名称
    -->
    <bean id="helloSpring" class="cn.duozai.HelloSpring">
        <!--
            实例化的过程中,给helloSpring对象设置name属性,即注入
            <property>标签:调用set方法设置对象中的属性
            name属性:对应的set方法,setXxx
            value属性:要设置的值
        -->
        <property name="hello" value="反转的人生如此惊艳"/>
    </bean>
</beans>

测试类(test.SpringTest):

public class SpringTest {

    /**
     * Spring测试
     *
     * @return void
     */
    @Test
    publicvoidtest() {
        // 1、读取Spring配置文件,实例化Spring上下文对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2、根据bean-id获取Bean对象实例
        HelloSpring helloSpring = (HelloSpring) applicationContext.getBean("helloSpring");
        // 3、执行print方法
        helloSpring.print();
    }

}

示例效果:


Spring 会根据配置文件中的信息,自动创建对应的实例对象,并通过 setter 方法为其属性进行赋值,实例的属性值将不再由程序中的代码来主动创建和管理,改为被动接收 Spring 的注入,这就是 IoC/DI 的一种具体实现。

使用 Spring IoC 实现 Java Bean 注入

使用 Spring IoC,可以实现 Java Bean 的注入。


示例:使用 Spring IoC 实现 Java Bean 注入

纸张接口(cn.duozai.Paper):

public interface Paper {

    /**
     * 获取纸张类型
     *
     * @return 纸张类型
     */
    String getPaper();

}

A4 纸张实现类(cn.duozai.A4Paper):

public class A4Paper implements Paper{

    /**
     * 获取纸张类型
     *
     * @return 纸张类型
     */
    @Override
    public String getPaper() {
        return "A4纸";
    }

}

B5 纸张实现类(cn.duozai.B5Paper):

public class B5Paper implements Paper {

    /**
     * 获取纸张类型
     *
     * @return 纸张类型
     */
    @Override
    public String getPaper() {
        return "B5纸";
    }

}

墨盒接口(cn.duozai.Ink):

public interface Ink {

    /**
     * 获取墨盒颜色
     *
     * @return 墨盒颜色
     */
    String getColor();

}

彩色墨盒实现类(cn.duozai.ColorInk):

public class ColorInk implements Ink {

    /**
     * 获取墨盒颜色
     *
     * @return 墨盒颜色
     */
    @Override
    public String getColor() {
        return "彩色";
    }

}

灰色墨盒实现类(cn.duozai.GrayInk):

public class GrayInk implements Ink {

    /**
     * 获取墨盒颜色
     *
     * @return 墨盒颜色
     */
    @Override
    public String getColor() {
        return "灰色";
    }

}

打印机类(cn.duozai.Printer):

public class Printer {

    private static final Logger logger = Logger.getLogger(Printer.class);

    /**
     * 墨盒对象
     */
    private Ink ink;

    /**
     * 设置打印机使用的墨盒
     *
     * @param ink 具体的墨盒实现类对象
     * @return void
     */
    public void setInk(Ink ink) {
        this.ink = ink;
    }

    /**
     * 纸张对象
     */
    private Paper paper;

    /**
     * 设置打印机使用的纸张
     *
     * @param paper 具体的纸张实现类对象
     * @return void
     */
    public void setPaper(Paper paper) {
        this.paper = paper;
    }

    /**
     * 打印所使用的墨盒和纸张信息
     */
    public void print() {
        logger.debug("墨盒:" + ink.getColor());
        logger.debug("纸张:" + paper.getPaper());
    }

}

Spring 核心配置文件(classpath: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">
    <!-- 定义A4纸的规格(bean对象) -->
    <bean id="a4Paper" class="cn.duozai.A4Paper"/>

    <!-- 定义B5纸的规格(bean对象) -->
    <bean id="b5Paper" class="cn.duozai.B5Paper"/>

    <!-- 定义彩色墨盒(bean对象) -->
    <bean id="colorInk" class="cn.duozai.ColorInk"/>

    <!-- 定义灰色墨盒(bean对象) -->
    <bean id="grayInk" class="cn.duozai.GrayInk"/>

    <!-- 组装打印机 -->
    <bean id="printer" class="cn.duozai.Printer">
        <!--
            name属性:对应setXxx方法
            ref属性:引用/注入已经定义好的Bean对象

            注入墨盒和纸张
        -->
        <property name="ink" ref="colorInk"/>
        <property name="paper" ref="a4Paper"/>
    </bean>
</beans>

测试类(test.SpringTest):

public class SpringTest {

    /**
     * Spring测试
     *
     * @return void
     */
    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Printer printer = (Printer) applicationContext.getBean("printer");
        printer.print();
    }

}

示例效果:


Spring 使用 DI 的方式实现了 Java 类之间的解耦合,使 Bean 与 Bean 之间不再产生直接的依赖关系,而是通过 Spring 统一建立桥梁,Bean 中只声明需要依赖的接口类型,最终注入的具体实现类则由 Spring 灵活配置,这也就是 “面向接口编程”。

Spring AOP

AOP 概述

面向切面编程(AOP,Aspect Oriented Programming)是软件编程思想发展到一定阶段的产物,是对面向对象编程(OOP,Object Oriented Programming)的有益补充。

AOP 一般适用于具有横切逻辑的场合,如访问控制、事务管理、日志记录、性能监测等。

public class UserServiceImpl implements UserService {

    private static final Logger log = Logger.getLogger(UserServiceImpl.class);

    public boolean save(User user){

        log.info("添加用户" + user.getUsername());    //记录日志

        SqlSession sqlSession = null;
        boolean flag = false;
        try {    //异常处理
            sqlSession = MyBatisUtil.createSqlSession();
            if(sqlSession.getMapper(userMapper.class).add(user) > 0) {
                flag = true;
            }
        
            sqlSession.commit();    //事务控制
            log.info("成功添加用户" + user.getUsername());    //记录日志
        } catch (Exception e){
            log.error("添加用户" + user.getUsername() + " 失败", e);    //记录日志
            sqlSession.rollback();    //事务控制
            flag = false;
        } finally {
            MyBatisUtil.closeSqlSession(sqlSession);
        }

        return flag;
    }
}

在业务系统中,总有一些散落、渗透到系统各处且不得不处理等事情,如日志、异常处理、事务控制等,这些穿插在既定业务中的操作就是所谓的“横切逻辑”,也就是所谓的切面。

这一些“横切逻辑”操作,一般会抽取出来,放在一个公共的类或方法中去处理,以实现业务系统和横切逻辑的解耦。

面向切面编程的目标就是在不改变原有的程序的基础上为代码片段增加新的功能,对其进行增强处理,其设计思想来源于代理模式。

AOP 中的基本概念:

  • 面向切面编程(AOP):给程序添加一些额外功能的特殊方式,不用改动原来主要业务代码的结构,就能在合适的地方悄悄 “插入” 像日志记录、权限验证、性能统计之类的通用功能。
  • 切面(Aspect):把一些分散在程序各处、但功能类似的通用操作(比如多处的日志记录代码)集中起来管理,规定好在哪些业务流程节点(比如方法调用前、调用后等)要做这些通用操作,就好像是一个关于这些通用功能怎么执行的整体规划。切面包含增强处理和切入点。
  • 连接点(Join Point):程序运行过程中,那些可以插入切面相关逻辑的具体时刻或动作,比如一个方法被调用、一个对象被创建、属性被访问等,就像是一些可供 “下手” 添加额外功能的具体时机。
  • 切入点(Pointcut):从众多连接点里选出来的特定部分,明确指出到底在哪些具体的连接点上要应用切面里的逻辑,就好比是从很多可添加额外功能的时机里,挑出符合特定条件的那些时机。切入点一定是连接点,但连接点不一定是切入点。
  • 通知/增强处理(Advice):切面里实际要执行的代码内容,也就是具体要做的通用功能操作,比如在方法调用前后记录日志等。
  • 目标对象(Target Object):即原本的业务对象,切面相关逻辑要作用在它们上面,就像是要接受这些额外功能 “装饰” 的原始对象。
  • 织入(Weaving):把切面里的增强处理按照切入点规定的地方,插入到目标对象的执行流程中去的过程,就像是把额外功能的操作按照选定的时机,准确 “嵌入” 到原始业务对象的运行过程中。
  • AOP 代理(AOP Proxy):由 AOP 框架所创建的对象,实现执行增强处理方法等功能,就像是接受了这些额外功能 “装饰” 之后的对象。

第一个 Spring AOP 程序

实现 Spring AOP 的基本步骤:

  • 导入 JAR 依赖包:Spring 相关 JAR 文件、Spring AOP 相关 JAR 文件。
  • 编写增强处理类。
  • 编写 Spring 核心配置文件 applicationContext.xml。
  • 编写测试类、测试方法。

示例:第一个 Spring AOP 程序

导入相关依赖 JAR 文件,并添加为库。

示例效果:

用户模块数据访问接口(cn.duozai.dao.UserDao):

public interface UserDao {

    /**
     * 保存用户信息
     *
     * @return SQL语句影响行数
     */
    int save();

}

用户模块数据访问接口实现类(cn.duozai.dao.UserDaoImpl):

public class UserDaoImpl implements UserDao {

    private static final Logger logger = Logger.getLogger(UserDaoImpl.class);

    /**
     * 保存用户信息
     *
     * @return SQL语句影响行数
     */
    @Override
    public int save() {
        logger.debug("保存用户信息到数据库");
        return 1;
    }

}

用户模块业务逻辑接口(cn.duozai.service.UserService):

public interface UserService {

    /**
     * 保存用户信息
     *
     * @return 保存结果
     */
    boolean save();

}

用户模块业务逻辑接口实现类(cn.duozai.service.UserServiceImpl):

public class UserServiceImpl implements UserService {

    /**
     * 用户模块数据访问层对象,通过Spring进行注入
     */
    UserDao userDao;

    /**
     * 设置用户模块数据访问层对象
     *
     * @param userDao 用户模块数据访问层对象
     * @return void
     */
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    /**
     * 保存用户信息
     *
     * @return 保存结果
     */
    @Override
    public boolean save() {
        // 调用userDao的方法保存用户信息
        intresult = userDao.save();
        return result > 0;
    }

}

Spring 核心配置文件(classpath: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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userDao" class="cn.duozai.dao.UserDaoImpl"/>
    <bean id="userService" class="cn.duozai.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>

测试类(test.SpringTest):

public class SpringTest {

    /**
     * Spring测试
     *
     * @return void
     */
    @Test
    public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.save();
    }

}

示例效果:

日志增强处理类(cn.duozai.aop.UserServiceLogger):

/**
 * 在增强处理类中主要定义增强方法,即要额外做的事情
 */
public class UserServiceLogger {

    private static final Logger logger = Logger.getLogger(UserServiceLogger.class);

    /**
     * 前置增强:在方法执行之前要做的事情
     *
     * @param jp 连接点方法对象
     * @return void
     */
    public void before(JoinPoint jp) {
        logger.debug("开始调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法");
    }

    /**
     * 后置增强:方法执行之后做增强
     *
     * @param jp 连接点方法对象
     * @param result 原方法的返回值
     * @return void
     */
    public void afterReturning(JoinPoint jp, Object result) {
        logger.debug("方法执行后,返回:" + result);
    }

}

Spring 核心配置文件(classpath:applicationContext.xml):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- ... -->

    <!-- 声明增强方法所在的Bean,让Spring创建增强处理类对象,才能调用增强方法 -->
    <bean id="userServiceLogger" class="cn.duozai.aop.UserServiceLogger"/>

    <!-- <aop:config>标签:配置Spring AOP -->
    <aop:config>
        <!--
            <aop:pointcut>标签:定义切入点,指定让哪些方法做增强操作
            expression:使用execution指定切入点表达式
        -->
        <aop:pointcut id="pointcut" expression="execution(boolean save())"/>

        <!--
            <aop:aspect>标签:配置切面
            ref属性:引用包含增强方法的Bean,即增强处理类对象
            切面 = 切入点 + 增强处理
        -->
        <aop:aspect ref="userServiceLogger">
            <!--
                <aop:before>标签:定义前置增强,在方法开始执行前做额外操作
                method属性:指定增强方法
                pointcut-ref属性:引用切入点
                告知程序pointcut-ref方法在执行之前先执行userServiceLogger中的before方法
            -->
            <aop:before method="before" pointcut-ref="pointcut"/>

            <!--
                <aop:after-returning>标签:定义后置增强,在方法正常执行结束时做额外操作
                returning属性:指定为获取原有方法的返回值并传递给增强方法中的result参数
            -->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
        </aop:aspect>
    </aop:config>

</beans>

示例效果: