Loading...

框架知识:Spring

Spring 框架是 Java 开发必备的基础框架,也是每个 Java 开发者必须掌握的,同时,也是面试较为聚集的知识区域。要在 Java 领域更加深入,也必须拥有 Spring 基础,毕竟开发 Springboot、Springcloud 也都是基于 Spring 进行实现的。

概述

Spring 最认同的技术是控制反转的**依赖注入(DI)**模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。

Spring 框架的一个关键组件是面向切面的程序设计(AOP)框架。一个程序中跨越多个点的功能被称为 横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。在 OOP 中模块化的关键单元是类,而在 AOP 中模块化的关键单元是切面。AOP 帮助你将横切关注点从它们所影响的对象中分离出来,然而依赖注入帮助你将你的应用程序对象从彼此中分离出来。Spring 框架的 AOP 模块提供了面向切面的程序设计实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦出来。

体系结构

Spring框架结构

核心容器

核心容器由 spring-corespring-beansspring-contextspring-context-supportspring-expression (SpEL,Spring表达式语言,Spring Expression Language)等模块组成。

  • [spring-core]{.pink} 模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
  • [spring-beans]{.pink} 模块提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。
  • [spring-context]{.pink} 模块建立在由 core 和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能。Context模块也支持Java EE的功能,比如EJB、JMX和远程调用等。 ApplicationContext 接口是Context模块的焦点。 spring-context-support 提供了对第三方库集成到Spring上下文的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。
  • [spring-expression]{.pink} 模块提供了强大的表达式语言,用于在运行时查询和操作对象图,它支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。

数据访问/集成

数据访问/集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块。

  • [JDBC]{.pink} 模块提供了JDBC抽象层,它消除了冗长的JDBC编码和对数据库供应商特定错误代码的解析。
  • [ORM]{.pink} 模块提供了对流行的对象关系映射API的集成,包括JPA、JDO和Hibernate等。通过此模块可以让这些ORM框架和spring的其它功能整合,比如前面提及的事务管理。
  • [OXM]{.pink} (Object XML Mapping)模块提供了对OXM实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。
  • [JMS]{.pink} (Java Message Service)模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了spring-messaging模块。
  • [事务模块]{.pink}为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。

Web

Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成。

  • [Web 模块]{.pink}提供面向web的基本功能和面向web的应用上下文,比如多部分(multipart)文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分。
  • [Web-MVC 模块]{.pink}为web应用提供了模型视图控制(MVC)和 REST Web 服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成。
  • [Web-Socket 模块]{.pink}为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。
  • [Web-Portlet 模块]{.pink}提供了用于 Portlet 环境的 MVC 实现,并反映了 spring-webmvc 模块的功能。

其他

  • [AOP 模块]{.pink}提供了面向切面的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。
  • [Aspects 模块]{.pink}提供了与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  • [Instrumentation 模块]{.pink}在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。
  • [Messaging 模块]{.pink}为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。
  • [测试模块]{.pink}支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。

Hello World

配置环境说明:

本文相关示例环境为:JDK1.8,Tomcat,IDEA。后续示例均假定该环境已成功配置。

  1. 创建项目

    创建maven项目

    下一步,输入项目名 spring-demo 并完成。

  2. 添加 Spring 依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.4</version>
    </dependency>
  3. 在src目录下创建包 com.xfc 及文件 HelloWorld.javaMainApp.java

    HelloWorld.java

    package com.xfc;
    
    public class HelloWorld {
        private String message;
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public void getMessage() {
            System.out.println("Your Message : " + message);
        }
    }

    MainApp.java

    package com.xfc;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp {
    
        public static void main(String[] args) {
            // 加载引用上下文环境
            ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
            // 获取bean实例
            HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
            obj.getMessage();
        }
    }
  4. 创建 Beans.xml 文件。

    在 main 目录下创建 resources 包,并新建 Beans.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-3.0.xsd">
        <!--注册bean并设置属性值-->
        <bean id="helloWorld" class="com.xfc.HelloWorld">
            <property name="message" value="Hello World!"/>
        </bean>
    </beans>
  5. 测试

    运行 MainApp.java > main 方法,得到如下结果:

    Your Message : Hello World!

IoC 容器(控制反转)

Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans,Spring IoC 容器利用 Java 的 POJO 类和配置元数据来生成完全配置和可执行的系统或应用程序。

IoC 容器具有依赖注入功能的容器,它可以创建对象,IoC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。

Spring 提供了 BeanFactoryApplicationContext 这两种不同类型的容器。

ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常建议超过 BeanFactory。BeanFactory 仍然可以用于轻量级的应用程序,如移动设备或基于 applet 的应用程序,其中它的数据量和速度是显著。

BeanFactory 容器

BeanFactory 容器主要的功能是为依赖注入(DI)提供支持。在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。

修改 Hello World 示例 中的 MainApp 类。

package com.xfc;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class MainApp {

    public static void main(String[] args) {
        XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("Beans.xml"));
        HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
        obj.getMessage();
    }
}

这里使用了框架提供的XmlBeanFactory() API生成工厂 bean 以及利用 ClassPathResource() API 去加载在路径 CLASSPATH 下可用的 bean 配置文件。

测试

运行 MainApp.java > main 方法,得到如下结果:

Your Message : Hello World!

ApplicationContext 容器

ApplicationContext 是 BeanFactory 的子接口,也被成为 Spring 上下文。 和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。

最常被使用的 ApplicationContext 接口实现:

  • FileSystemXmlApplicationContext

    该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。

    示例:

    ApplicationContext context = new FileSystemXmlApplicationContext("E:\project\spring-demo\src\Beans.xml");
  • ClassPathXmlApplicationContext

    该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。

  • WebXmlApplicationContext

    该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

Bean 定义

被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象,这些 bean 是由用容器提供的配置元数据创建的。

Bean 与 Spring 容器的关系

Bean与Spring容器

Spring 配置元数据

Spring IoC 容器完全由实际编写的配置元数据的格式解耦。以下是将配置元数据注册到Spring容器的三种方式:

  • 基于 XML 的配置文件
  • 基于注解的配置
  • 基于 Java 的配置

另外,在配置bean实例时,我们也可以指定延迟初始化,初始化方法和销毁方法。

<!-- A bean definition with lazy init set on -->
<bean id="..." class="..." lazy-init="true">
    <!-- collaborators and configuration for this bean go here -->
</bean>
<!-- A bean definition with initialization method -->
<bean id="..." class="..." init-method="...">
    <!-- collaborators and configuration for this bean go here -->
</bean>
<!-- A bean definition with destruction method -->
<bean id="..." class="..." destroy-method="...">
    <!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->

Bean 作用域

作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境

singleton作用域

singleton 是默认的作用域,当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。

Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。

配置示例:

<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="singleton">
    <!-- collaborators and configuration for this bean go here -->
</bean>

代码示例(略)

Bean 生命周期

对于 bean 的生命周期,我们可以通过 init-method 参数和 destroy-method 参数进行监听。并在对应实例中声明相应的方法。

<bean id="helloWorld" class="com.xfc.HelloWorld" init-method="init" destroy-method="destroy">
    <property name="message" value="Hello World!"/>
</bean>
// HelloWorld.java......
public void init(){
    
}
public void destroy(){
    
}

而对于多个 bean 实例,也可以进行统一配置:

<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.0.xsd"
    default-init-method="init" default-destroy-method="destroy">
   <bean id="..." class="...">
       <!-- collaborators and configuration for this bean go here -->
   </bean>
</beans>

Bean 后置处理器

Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。

代码示例:

本示例需要的文件:HelloWorld.java、InitHelloWorld.java、Beans.xml、MainApp.java。

修改 HelloWorld.java

package com.xfc;

public class HelloWorld {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public void getMessage() {
        System.out.println("Your Message : " + message);
    }

    public void init() {
        System.out.println("Bean is going through init.");
    }

    public void destroy() {
        System.out.println("Bean will destroy now.");
    }
}

添加 InitHelloWorld.java

package com.xfc;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class InitHelloWorld implements BeanPostProcessor {

    // 前置处理器
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeforeInitialization : " + beanName);
        return bean;
    }

    // 后置处理器
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("AfterInitialization : " + beanName);
        return bean;
    }
}

修改 Beans.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-3.0.xsd">
    <!--注册bean-->
    <bean id="helloWorld" class="com.xfc.HelloWorld" init-method="init" destroy-method="destroy">
        <property name="message" value="Hello World!"/>
    </bean>

    <bean class="com.xfc.InitHelloWorld" />

</beans>

修改 MainApp.java

package com.xfc;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
        obj.getMessage();
        // 关闭hook:确保正常关闭且调用destroy()
        context.registerShutdownHook();
    }
}

通过上述代码,我们知道了前后置处理器的调用方式。

运行 MainApp.java 得到如下结果,我们可以观察到前后置处理器的调用时机:

BeforeInitialization : helloWorld
Bean is going through init.
AfterInitialization : helloWorld
Your Message : Hello World!
Bean will destroy now.

Bean 定义继承

bean 定义可以包含很多的配置信息,包括构造函数的参数,属性值,容器的具体信息例如初始化方法,静态工厂方法名,等等。

Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。子 bean 的定义继承父定义的配置数据。子定义可以根据需要重写一些值,或者添加其他值。

代码示例:

本示例需要的文件:HelloWorld.java、HelloChina.java、Beans.xml、MainApp.java。

修改 HelloWorld.java

package com.xfc;

public class HelloWorld {
    private String message1;
    private String message2;

    public void setMessage1(String message) {
        this.message1 = message;
    }

    public void setMessage2(String message) {
        this.message2 = message;
    }

    public void getMessage1() {
        System.out.println("World Message1 : " + message1);
    }

    public void getMessage2() {
        System.out.println("World Message2 : " + message2);
    }
}

添加 HelloChina.java

package com.xfc;

public class HelloChina {
    private String message1;
    private String message2;
    private String message3;

    public void setMessage1(String message) {
        this.message1 = message;
    }

    public void setMessage2(String message) {
        this.message2 = message;
    }

    public void setMessage3(String message) {
        this.message3 = message;
    }

    public void getMessage1() {
        System.out.println("China Message1 : " + message1);
    }

    public void getMessage2() {
        System.out.println("China Message2 : " + message2);
    }

    public void getMessage3() {
        System.out.println("China Message3 : " + message3);
    }
}

修改 Beans.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-3.0.xsd">
    <bean id="helloWorld" class="com.xfc.HelloWorld">
        <property name="message1" value="Hello World!"/>
        <property name="message2" value="Hello Second World!"/>
    </bean>
    <bean id="helloChina" class="com.xfc.HelloChina" parent="helloWorld">
        <property name="message1" value="Hello China!"/>
        <property name="message3" value="你好,中国!"/>
    </bean>
</beans>

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");

        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.getMessage1();
        objA.getMessage2();

        HelloChina objB = (HelloChina) context.getBean("helloChina");
        objB.getMessage1();
        objB.getMessage2();
        objB.getMessage3();
    }
}

运行 MainApp.java 得到如下结果:

World Message1 : Hello World!
World Message2 : Hello Second World!
China Message1 : Hello China!
China Message2 : Hello Second World!
China Message3 : 你好,中国!

我们可以观察到子 bean 继承了父 bean 中的 message2 的属性配置,而重写了 message1 和 message3 的属性配置。

依赖注入(DI)

Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。

基于构造函数的依赖注入

当容器调用带有一组参数的类构造函数时,基于构造函数的 DI 就完成了,其中每个参数代表一个对其他类的依赖。

代码示例:

本示例需要的文件:Role.java、User.java、Beans.xml、MainApp.java。

创建 Role.java

package com.xfc;

public class Role {
    public void getUserRole() {
        System.out.println("Inside getUserRole.");
    }
}

创建 User.java

package com.xfc;

public class User {
    private Role role;

    public User(Role role) {
        System.out.println("Inside Role constructor.");
        this.role = role;
    }

    public void getUserRole() {
        role.getUserRole();
    }
}

修改 Beans.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-3.0.xsd">
    <!-- Definition for User bean -->
    <bean id="user" class="com.xfc.User">
        <constructor-arg ref="role"/>
    </bean>

    <!-- Definition for Role bean -->
    <bean id="role" class="com.xfc.Role"/>
</beans>

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        User user = (User) context.getBean("user");
        user.getUserRole();
    }
}

运行 MainApp.java 得到如下结果:

Inside User constructor.
Inside getUserRole.

通过上述代码及运行结果,我们观察在 Beans.xml 中,User实例通过 <constructor-arg ref="role"/> 方式注入了其依赖的 Role Bean 对象。因此,在 MainApp.java 中获取 User 实例时,即通过 User 类的构造函数获取到其依赖的 Role 实例,继而能够通过其依赖的实例执行 getUserRole() 方法。

当构造函数存在多个参数时,为了避免歧义,构造函数的参数在 bean 定义中的顺序也应当尽量与参数位置保持一致。但如果存在多个相同数据类型、不同参数顺序的构造函数时,默认会调用匹配类型中的第一个构造函数,如果多个构造函数的数据类型相同而顺序和参数名不同,则可以在 bean 定义中指定 name 或 index 属性来指定匹配的构造函数(这里的 name 指向对应参数的 name)。

示例:

public User(Integer i, Role role) {
    System.out.println("构造函数 1");
    this.role = role;
    this.i = i;
}
public User(Role role, Integer j) {
    System.out.println("构造函数 2");
    this.role = role;
    this.j = j;
}
<!-- 通过构造函数2注入(与参数顺序无关) -->
<bean id="user" class="com.xfc.User">
    <!--<constructor-arg type="java.lang.Integer" value="77" index="1"/>-->
    <constructor-arg type="java.lang.Integer" value="120" name="i"/>
    <constructor-arg ref="role"/>
</bean>

注:示例中的 type 属性用于显式指定构造函数的参数类型。

基于设值函数的依赖注入

当容器调用一个无参的构造函数或一个无参的静态 factory 方法来初始化你的 bean 后,通过容器在你的 bean 上调用设值函数,基于设值函数的 DI 就完成了。

代码示例:

本示例需要的文件:Role.java、User.java、Beans.xml、MainApp.java。其中 Role.java、MainApp.java 文件与上一示例一致,此处不再重述。

创建 Role.java

package com.xfc;

public class Role {
    public void getUserRole() {
        System.out.println("Inside getUserRole.");
    }
}

创建 User.java

package com.xfc;

public class User {
    private Role role;

    // a setter method to inject the dependency.
    public void setRole(Role role) {
        System.out.println("Inside setRole.");
        this.role = role;
    }

    // a getter method to return spellChecker
    public Role getRole() {
        return role;
    }

    public void getRoleDescription() {
        role.getRoleDescription();
    }
}

修改 Beans.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-3.0.xsd">
    <!-- Definition for User bean -->
    <bean id="user" class="com.xfc.User">
        <property name="role" ref="role"/>
    </bean>

    <!-- Definition for Role bean -->
    <bean id="role" class="com.xfc.Role"/>
</beans>

运行 MainApp.java 得到如下结果:

Inside setRole.
Inside getRoleDescription.

通过上述代码及运行结果,我们实现了通过 setRole 的方式向 User 实例中注入了其依赖的 Role 对象。

构造函数与设值注入的唯一区别,在于基于构造函数注入中使用的是 <constructor-arg> ,而在基于设值函数的注入中使用的是 <property>

注入内部 Beans

inner beans 是在其他 bean 的范围内定义的 bean。

修改 基于设值函数的依赖注入 演示代码中的 Beans.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-3.0.xsd">
    <!-- Definition for User bean -->
    <bean id="user" class="com.xfc.User">
        <property name="role">
            <bean id="role" class="com.xfc.Role"></bean>
        </property>
    </bean>
</beans>

同样能够实现对依赖对象的注入,这里这种方式被称为内部 bean 注入。

注入集合

Spring 提供了四种类型的集合的配置元素:

元素 描述
<list> 它有助于连线,如注入一列值,允许重复。
<set> 它有助于连线一组值,但不能重复。
<map> 它可以用来注入名称-值对的集合,其中名称和值可以是任何类型。
<props> 它可以用来注入名称-值对的集合,其中名称和值都是字符串类型。

示例:

<property name="addressList">
    <list>
        <value>INDIA</value>
        <value>Pakistan</value>
        <value>USA</value>
        <value>USA</value>
    </list>
</property>
<property name="addressSet">
    <set>
        <value>INDIA</value>
        <value>Pakistan</value>
        <value>USA</value>
        <value>USA</value>
    </set>
</property>
<property name="addressMap">
    <map>
        <entry key="1" value="INDIA"/>
        <entry key="2" value="Pakistan"/>
        <entry key="3" value="USA"/>
        <entry key="4" value="USA"/>
    </map>
</property>
<property name="addressProp">
    <props>
        <prop key="one">INDIA</prop>
        <prop key="two">Pakistan</prop>
        <prop key="three">USA</prop>
        <prop key="four">USA</prop>
    </props>
</property>

如何注入 null 和空字符串:

<property name="userName" value=""/>
<property name="obj"><null/></property>

Beans 自动装配

Spring 容器可以在不使用 <constructor-arg><property> 元素的情况下 自动装配 相互协作的 bean 之间的关系。

动装配模式可用于指示 Spring 容器为来使用自动装配进行依赖注入。你可以使用 <bean> 元素的 autowire 属性为一个 bean 定义指定自动装配模式。

模式 描述
no 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。在依赖注入章节你已经看到这个了。
byName 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。
byType 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。
constructor 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。
autodetect Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。

自动装配的局限性

  • 重写: 需用 <constructor-arg><property> 配置来定义依赖,意味着总要重写自动装配。
  • 基本数据类型: 不能自动装配简单的属性,如基本数据类型,String字符串,和类。
  • 模糊特性: 自动装配不如显式装配精确,如果有可能,尽量使用显式装配。

自动装配 byName

Spring DI 配置中,我们可以指定 autowire 属性为 byName 来指定自动装配,而不需每一次都显示声明其依赖的注入对象。此选项启用基于 bean 名称的依赖项注入。在 Bean 中自动装配属性时,属性名称用于在配置文件中搜索匹配的 Bean 定义。如果找到这样的 bean,则将其注入属性。如果找不到,则会引发错误。

代码示例:

本示例需要的文件:Role.java、User.java、Beans.xml、MainApp.java,其中 Role.java、MainApp.java 文件与上一示例一致,此处不再重述。

修改 User.java

package com.xfc;

public class User {
    private Role role;
    private String name;

    public void setRole(Role role) {
        System.out.println("Inside setRole.");
        this.role = role;
    }

    public Role getRole() {
        return role;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void getRoleDescription() {
        role.getRoleDescription();
    }
}

修改 Beans.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-3.0.xsd">
    <!-- Definition for textEditor bean -->
    <bean id="user" class="com.xfc.User" autowire="byName">
        <property name="name" value="Generic User" />
    </bean>

    <!-- Definition for Role bean -->
    <bean id="role" class="com.xfc.Role"></bean>
</beans>

运行 MainApp.java 得到如下结果:

Inside setRole.
Inside getRoleDescription.

通过上述代码及运行结果,我们发现添加 autowire="byName" 后, User 依赖的 Role 实例在未经过显示声明的情况下仍被自动注入到 Spring 容器中。此时,程序在装配 User 时,检测到 private Role role; 属性,并自动根据 role 名称查找配置文件,并完成自动注入。

自动装配 byType

Spring 容器也可以指定 autowire 属性为 byType 来指定自动装配,若指定的 type 与配置文件中 beans 名称中的一个匹配,则它将尝试匹配和连接它的属性。此选项支持基于 bean 类型的依赖项注入。在 Bean 中自动装配属性时,属性的类类型用于在配置文件中搜索匹配的 bean 定义。如果找到这样的 bean ,就在属性中注入它。如果找不到,则会引发错误。

代码示例:

本示例需要的文件:Role.java、User.java、Beans.xml、MainApp.java,其中 Role.java、User.java、MainApp.java 文件与上一示例一致,此处不再重述。

修改 Beans.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-3.0.xsd">
    <!-- Definition for textEditor bean -->
    <bean id="user" class="com.xfc.User" autowire="byType">
        <property name="name" value="Generic User" />
    </bean>

    <!-- Definition for Role bean -->
    <bean id="role" class="com.xfc.Role"></bean>
</beans>

运行 MainApp.java 得到如下结果:

Inside setRole.
Inside getRoleDescription.

通过上述代码及运行结果,我们发现添加 autowire="byType" 后, User 依赖的 Role 实例在未经过显示声明的情况下仍被自动注入到 Spring 容器中。此时,程序在装配 User 时,检测到 private Role role; 属性,并自动根据 Role 类型查找配置文件,并完成自动注入。

由构造函数自动装配

这种模式与 byType 非常相似,但它应用于构造器参数。

代码示例:

本示例需要的文件:Role.java、User.java、Beans.xml、MainApp.java,其中 Role.java、MainApp.java 文件与上一示例一致,此处不再重述。

修改 User.java

package com.xfc;

public class User {
    private Role role;
    private String name;

    public User(Role role, String name) {
        this.role = role;
        this.name = name;
    }

    public void getRoleDescription() {
        System.out.println("Current User: " + name);
        role.getRoleDescription();
    }
}

修改 Beans.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-3.0.xsd">
    <!-- Definition for User bean -->
    <bean id="user" class="com.xfc.User" autowire="constructor">
        <constructor-arg value="xfc_exclave"/>
    </bean>

    <!-- Definition for Role bean -->
    <bean id="role" class="com.xfc.Role"></bean>
</beans>

运行 MainApp.java 得到如下结果:

Current User: xfc_exclave
Inside getRoleDescription.

通过上述代码及运行结果,我们发现添加 autowire="constructor" 后, User 依赖的 Role 实例在未经过显示声明的情况下仍被自动注入到 Spring 容器中。此时,程序在装配 User 时,通过构造函数检测到 Role role 参数,并自动根据该参数完成自动注入。

基于注解的配置

从 Spring 2.5 开始就可以使用 注解 来配置依赖注入,而不是采用 XML 来描述一个 bean 连线。注解连线在默认情况下在 Spring 容器中不打开。因此,在可以使用基于注解的连线之前,我们需要在 Spring 配置文件中启用。

如果你想在 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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <context:annotation-config/>
   <!-- bean definitions go here -->

</beans>

相关注解

序号 注解 & 描述
1 @Required 应用于 bean 属性的 setter 方法。
2 @Autowired 可应用于 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。
3 @Qualifier 通过指定确切的将被连线的 bean,@Autowired 和 @Qualifier 注解可以用来删除混乱。
4 JSR-250 其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。

@Required

@Required 注解应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。

代码示例:

本示例需要的文件:Product.java、Beans.xml、MainApp.java。

新建 Product.java

package com.xfc;

import org.springframework.beans.factory.annotation.Required;

public class Product {
    private String name;
    private Integer count;

    public String getName() {
        return name;
    }

    @Required// 新版本显示此注解过时,暂时忽略
    public void setName(String name) {
        this.name = name;
    }

    public Integer getCount() {
        return count;
    }

    @Required
    public void setCount(Integer count) {
        this.count = count;
    }
}

修改 Beans.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config/>

    <!-- Definition for product bean -->
    <bean id="product" class="com.xfc.Product">
        <property name="name" value="toothbrush"/>
        <!--<property name="count" value="4"/>-->
    </bean>

</beans>

注:新版本使用 @Required 并配置上述配置文件时,代码编辑阶段即会提示错误。

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Product product = (Product) context.getBean("product");
        System.out.println("Name : " + product.getName());
        System.out.println("Count : " + product.getCount());
    }
}

运行 MainApp.java ,若 @Required 注解未过时,将抛出 BeanInitializationException 异常并输出如下信息:

Property ‘count’ is required for bean ‘product’

若 @Required 注解已过时,将得到如下结果:

Name : toothbrush
Count : null

通过上述代码及运行结果,我们得出结论:@Required 注解作用于 bean 属性的 setter 方法,且要求其对应的属性必须被注入到 bean 实例中。但对于较新版本而言, @Required 被标记为过时注解且不会生效,未被注入的属性将会被填充该类型对应的默认值。

@Autowired

@Autowired 注解对在哪里和如何完成自动连接提供了更多的细微的控制。

  • Setter 方法中的 @Autowired

    代码示例:

    本示例需要的文件:Role.java、User.java、Beans.xml、MainApp.java。

    Role.java

    package com.xfc;
    
    public class Role {
    
        private String description;
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public void getRoleDescription() {
            System.out.println("role description: " + description);
        }
    }
    

    修改 User.java

    package com.xfc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    public class User {
        private Role role;
    
        public Role getRole() {
            return role;
        }
    
        @Autowired
        public void setRole(Role role) {
            this.role = role;
        }
    
        public void getRoleDescription() {
            role.getRoleDescription();
        }
    }

    修改 Beans.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <context:annotation-config/>
    
        <!-- Definition for user bean -->
        <bean id="user" class="com.xfc.User"/>
    
        <!-- Definition for role bean -->
        <bean id="role" class="com.xfc.Role">
            <property name="description" value="this role is for admin" />
        </bean>
    
    </beans>

    修改 MainApp.java

    package com.xfc;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
            User user = (User) context.getBean("user");
            user.getRoleDescription();
        }
    }

    运行 MainApp.java 得到如下结果:

    role description: this role is for admin

    User 所依赖的 Role 对象通过 setRole() 方法上的 @Autowired 注解被自动注入到其 role 属性中。

  • 属性中的 @Autowired

    修改上一示例代码中的 User.java

    package com.xfc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    public class User {
    
        @Autowired
        private Role role;
    
        public void getRoleDescription() {
            role.getRoleDescription();
        }
    }

    再次运行 MainApp.java 仍得到相同返回结果。此时, User 所依赖的 Role 对象通过其对应属性上的 @Autowired 注解实现了依赖注入。

  • 构造函数中的 @Autowired

    再一次修改 User.java 文件。

    package com.xfc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    public class User {
        private Role role;
    
        @Autowired// 可以省略
        public User(Role role) {
            this.role = role;
        }
    
        public void getRoleDescription() {
            role.getRoleDescription();
        }
    }

    仍旧得到相同结果,此时 User 通过构造函数上的 @Autowired 注解实现了依赖注入。

  • @Autowired 的(required=false)选项

    默认情况下,@Autowired 注释意味着依赖是必须的,它类似于 @Required 注释,可以通过 @Autowired 的 (required=false) 选项关闭默认行为(此项不再进行代码演示)。

@Qualifier

当需要向 Spring 容器中注入多个同类型 bean 时,可以通过 @Qualifier 指定实例对象与注入配置之间的连线关系。

代码示例:

本示例需要的文件:Role.java、User.java、Beans.xml、MainApp.java,其中 Role.java、MainApp.java 文件与上一示例一致,此处不再重述。

修改 User.java

package com.xfc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class User {

    @Autowired
    @Qualifier("role1")
    private Role admin;

    @Autowired
    @Qualifier("role2")
    private Role customer;

    public void getRoleDescription() {
        admin.getRoleDescription();
        customer.getRoleDescription();
    }
}

修改 Beans.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config/>

    <bean id="user" class="com.xfc.User"/>

    <bean id="role1" class="com.xfc.Role">
        <property name="description" value="this role is for admin" />
    </bean>
    <bean id="role2" class="com.xfc.Role">
        <property name="description" value="this role is for customer" />
    </bean>

</beans>

运行 MainApp.java 得到如下结果:

role description: this role is for admin
role description: this role is for customer

JSR-250 注释

Spring还使用基于 JSR-250 注释,它包括 @PostConstruct, @PreDestroy 和 @Resource 注释。

@PostConstruct@PreDestroy 注解分别对应配置文件中的 init-methoddestroy-method 参数。

修改上一示例代码中的 User.java

package com.xfc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class User {

    @Autowired
    @Qualifier("role1")
    private Role admin;

    @Autowired
    @Qualifier("role2")
    private Role customer;

    public void getRoleDescription() {
        admin.getRoleDescription();
        customer.getRoleDescription();
    }

    @PostConstruct
    public void init(){
        System.out.println("--------------- Bean init ----------------");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("-------------- Bean destroy --------------");
    }
}

修改 MainApp.java

package com.xfc;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        User user = (User) context.getBean("user");
        user.getRoleDescription();
        context.registerShutdownHook();
    }
}

运行 MainApp.java 得到如下结果:

--------------- Bean init ----------------
role description: this role is for admin
role description: this role is for customer
-------------- Bean destroy --------------

@Resource 注解指定 name 属性,改属性会以一个 bean 名称的形式被注入,它遵循 by-name 自动连接语义。该注解作用于对象的属性或 setter 方法。

再次修改 User.java

package com.xfc;

import javax.annotation.Resource;

public class User {

    @Resource(name = "role1")
    private Role role;

    public void getRoleDescription() {
        role.getRoleDescription();
    }
}

或者修改为:

package com.xfc;

import javax.annotation.Resource;

public class User {
    private Role role;

    @Resource(name = "role1")
    public void setRole(Role role) {
        this.role = role;
    }

    public void getRoleDescription() {
        role.getRoleDescription();
    }
}

运行 MainApp.java 得到如下结果:

role description: this role is for admin

基于 Java 的配置

此前已经完成了基于 xml 的方式配置 Spring bean 。但 Spring 也提供了基于 java 配置 Spring bean 的方法。

@Configuration 和 @Bean 注解

带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。

代码示例:

本示例需要的文件:Product.java、BeanConfig.java、Beans.xml、MainApp.java,其中 Beans.xml 文件与上一示例一致,此处不再重述。

修改 Product.java

package com.xfc;

public class Product {
    private String name;
    private Integer count;

    public Product(String name, Integer count) {
        this.name = name;
        this.count = count;
    }

    public String getName() {
        return name;
    }

    public Integer getCount() {
        return count;
    }
}

创建 BeanConfig.java

package com.xfc;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {
    @Bean
    public Product product(){
        return new Product("washing machine", 3);
    }
}

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfig.class);
        Product product = (Product) ctx.getBean("product");
        System.out.println("product: " + product.getName());
        System.out.println("count: " + product.getCount());
    }
}

运行 MainApp.java ,得到如下结果:

product: washing machine
count: 3

对于使用 @Configuration 中注入多个实例,只需配置多个 @Bean 即可。

@Import 注解

@import 注解允许从另一个配置类中加载 @Bean 定义。

示例:

@Configuration
public class ConfigA {
   @Bean
   public A a() {
      return new A(); 
   }
}
@Configuration
@Import(ConfigA.class)// 从ConfigA中加载Bean
public class ConfigB {
   @Bean
   public B a() {
      return new A(); 
   }
}

生命周期回调

与 Spring 在 xml 中指定初始化和销毁方法一样,@Bean 注解也支持指定任意的初始化和销毁的回调方法,其语法如下:

@Bean(initMethod = "init", destroyMethod = "destroy")

指定 Bean 的范围:

@Bean 默认范围是单实例,但我们可以重写带有 @Scope 注解的该方法。示例如下:

@Configuration
public class AppConfig {
   @Bean
   @Scope("prototype")
   public Foo foo() {
      return new Foo();
   }
}

事件处理

Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期。

通过 ApplicationEvent 类和 ApplicationListener 接口来提供在 ApplicationContext 中处理事件。如果一个 bean 实现 ApplicationListener,那么每次 ApplicationEvent 被发布到 ApplicationContext 上,那个 bean 会被通知。

Spring 提供了以下的标准事件:

Spring 内置事件 描述
ContextRefreshedEvent ApplicationContext 被初始化或刷新时,该事件被触发。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来触发。
ContextStartedEvent 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被触发。
ContextStoppedEvent 当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,该事件被触发。
ContextClosedEvent 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被触发。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
RequestHandledEvent 是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。

在设计应用程序时应当注意,由于 Spring 的事件处理是单线程的,一个事件被发布,直到所有的接收者都处理完这个消息之前,该进程会被阻塞,流程不会继续。

监听上下文事件

为了监听上下文事件,一个 bean 应该实现 ApplicationListener 接口的 onApplicationEvent() 方法。

代码示例:

本示例需要的文件:Product.java、StartEventHandler.java、StopEventHandler.java、BeanConfig.java、MainApp.java。

修改 Product.java

package com.xfc;

public class Product {
    private String name;
    private Integer count;

    public Product(String name, Integer count) {
        this.name = name;
        this.count = count;
    }

    public String getName() {
        return name;
    }

    public Integer getCount() {
        return count;
    }
}

新建 StartEventHandler.java

package com.xfc;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;

public class StartEventHandler implements ApplicationListener<ContextStartedEvent> {

    @Override
    public void onApplicationEvent(ContextStartedEvent contextStartedEvent) {
        System.out.println("ContextStartedEvent Received");
    }
}

新建 StopEventHandler.java

package com.xfc;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent;

public class StopEventHandler implements ApplicationListener<ContextStoppedEvent> {

    @Override
    public void onApplicationEvent(ContextStoppedEvent contextStoppedEvent) {
        System.out.println("ContextStoppedEvent Received");
    }
}

修改 BeanConfig.java

package com.xfc;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {
    @Bean
    public Product product() {
        return new Product("washing machine", 3);
    }

    @Bean
    public StartEventHandler startEventHandler() {
        return new StartEventHandler();
    }

    @Bean
    public StopEventHandler stopEventHandler() {
        return new StopEventHandler();
    }
}

修改 MainApp.java

package com.xfc;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfig.class);
        ctx.start();
        Product product = (Product) ctx.getBean("product");
        System.out.println("Name : " + product.getName());
        System.out.println("Count : " + product.getCount());
        ctx.stop();
    }
}

当然,也可以通过 xml 方式配置对应的 bean。

运行 MainApp.java ,得到如下结果:

ContextStartedEvent Received
Name : washing machine
Count : 3
ContextStoppedEvent Received

自定义事件

代码示例:

本示例需要的文件:CustomEvent.java、CustomEventHandler.java、CustomEventPublisher.java、MainApp.java。

新建 CustomEvent.java

package com.xfc;

import org.springframework.context.ApplicationEvent;

public class CustomEvent extends ApplicationEvent {

    public CustomEvent(Object source) {
        super(source);
    }

    public String toString() {
        return "My Custom Event";
    }
}

新建 CustomEventHandler.java

package com.xfc;

import org.springframework.context.ApplicationListener;

public class CustomEventHandler implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        System.out.println(event.toString());
    }
}

新建 CustomEventPublisher.java

package com.xfc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;

public class CustomEventPublisher implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void publish() {
        CustomEvent ce = new CustomEvent(this);
        publisher.publishEvent(ce);
    }
}

注:CustomEventPublisher 也可以直接实现 Aware 接口,然后通过构造函数或 @Autowired 注入 ApplicationEventPublisher 依赖。

修改 MainApp.java

package com.xfc;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfig.class);
        CustomEventPublisher cvp = (CustomEventPublisher) ctx.getBean("customEventPublisher");
        cvp.publish();
    }
}

运行 MainApp.java ,得到如下结果:

My Custom Event

Spring 框架的 AOP

面向切面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为 横切关注点 ,这些横切关注点在概念上独立于应用程序的业务逻辑。

Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。

AOP 术语

术语 描述
Aspect 一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 切面调用。应用程序可以拥有任意数量的切面,这取决于需求。
Join point 在你的应用程序中它代表一个点,你可以在插件 AOP 切面。你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。
Advice 这是实际行动之前或之后执行的方法。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。
Pointcut 这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。
Introduction 引用允许你添加新方法或属性到现有的类中。
Target object 被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。
Weaving Weaving 把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。

通知的类型

  • 前置通知:在一个方法执行之前,执行通知。
  • 后置通知:在一个方法执行之后,不考虑其结果,执行通知。
  • 返回后通知:在一个方法执行之后,只有在方法成功完成时,才能执行通知。
  • 抛出异常后通知:在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
  • 环绕通知:在建议方法调用之前和之后,执行通知。

实现自定义切面

Spring 支持 @AspectJ annotation style 的方法和基于模式的方法来实现自定义切面。

  • XML Schema based 使用常规类以及基于配置的 XML 来实现。
  • @AspectJ based 引用一种声明切面的风格作为带有 Java 5 注释的常规 Java 类注释。

基于 AOP 的 XML架构

配置示例:

<aop:config>
   <aop:aspect id="myAspect" ref="aBean">
      <aop:pointcut id="businessService"
         expression="execution(* com.xfc.service.*.*(..))"/>
      <!-- a before advice definition -->
      <aop:before pointcut-ref="businessService" 
         method="doRequiredTask"/>
      <!-- an after advice definition -->
      <aop:after pointcut-ref="businessService" 
         method="doRequiredTask"/>
      <!-- an after-returning advice definition -->
      <!--The doRequiredTask method must have parameter named retVal -->
      <aop:after-returning pointcut-ref="businessService"
         returning="retVal"
         method="doRequiredTask"/>
      <!-- an after-throwing advice definition -->
      <!--The doRequiredTask method must have parameter named ex -->
      <aop:after-throwing pointcut-ref="businessService"
         throwing="ex"
         method="doRequiredTask"/>
      <!-- an around advice definition -->
      <aop:around pointcut-ref="businessService" 
         method="doRequiredTask"/>
   ...
   </aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

代码示例:

此前的代码示例已演示了较多内容,为了避免混淆,我们可以选择删除以前的文件或创建一个新的项目后,再继续后续代码演示。

本示例需要的文件:aspectjrt.jaraspectjweaver.jar、Logging.java、Book.java、Beans.xml、MainApp.java。

下载 aspectjrt.jaraspectjweaver.jar 并添加到项目 lib 中。

这些文件你可以前往 https://mvnrepository.com/ 或其他网站下载。

新建 Logging.java

package com.xfc;

public class Logging {

    public void beforeAdvice() {
        System.out.println("Going to setup book profile.");
    }

    public void afterAdvice() {
        System.out.println("Book profile has been setup.");
    }

    public void afterReturningAdvice(Object retVal) {
        System.out.println("Returning:" + retVal.toString());
    }

    public void AfterThrowingAdvice(IllegalArgumentException ex) {
        System.out.println("There has been an exception: " + ex.toString());
    }
}

新建 Book.java

package com.xfc;

public class Book {

    private Integer count;
    private String name;

    public Integer getCount() {
        System.out.println("Count : " + count );
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public String getName() {
        System.out.println("Name : " + name );
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public void printThrowException(){
        System.out.println("Exception raised");
        throw new IllegalArgumentException();
    }
}

修改 Beans.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-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

    <aop:config>
        <aop:aspect id="log" ref="logging">
            <aop:pointcut id="selectAll" expression="execution(* com.xfc.*.*(..))"/>
            <aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
            <aop:after pointcut-ref="selectAll" method="afterAdvice"/>
            <aop:after-returning pointcut-ref="selectAll" returning="retVal" method="afterReturningAdvice"/>
            <aop:after-throwing pointcut-ref="selectAll" throwing="ex" method="AfterThrowingAdvice"/>
        </aop:aspect>
    </aop:config>

    <!-- Definition for book bean -->
    <bean id="book" class="com.xfc.Book">
        <property name="name" value="Effective Java"/>
        <property name="count" value="11"/>
    </bean>

    <!-- Definition for logging aspect -->
    <bean id="logging" class="com.xfc.Logging"/>

</beans>

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Book book = (Book) context.getBean("book");
        book.getCount();
        book.getName();
        book.printThrowException();
    }
}

运行 MainApp.java ,得到如下结果:

Going to setup book profile.
Count : 11
Book profile has been setup.
Returning:11
Going to setup book profile.
Name : Effective Java
Book profile has been setup.
Returning:Effective Java
Going to setup book profile.
Exception raised
Book profile has been setup.
There has been an exception: java.lang.IllegalArgumentException

Exception in thread “main” java.lang.IllegalArgumentException
at com.xfc.Book.printThrowException(Book.java:27)

基于 AOP 的 @AspectJ

@AspectJ 作为通过 Java 5 注解注释的普通的 Java 类,它指的是声明 aspects 的一种风格。通过配置 <aop:aspectj-autoproxy/> 后可以配置 @AspectJ

代码示例:

本示例需要的文件:Logging.java、Book.java、Beans.xml、MainApp.java,其中 Book.java、MainApp.java 文件与上一示例一致,此处不再重述。

修改 Logging.java

package com.xfc;

import org.aspectj.lang.annotation.*;

@Aspect
public class Logging {

    @Pointcut("execution(* com.xfc.*.*(..))")
    private void selectAll() {
    }

    @Before("selectAll()")
    public void beforeAdvice() {
        System.out.println("Going to setup book profile.");
    }

    @After("selectAll()")
    public void afterAdvice() {
        System.out.println("Book profile has been setup.");
    }

    @AfterReturning(pointcut = "selectAll()", returning = "retVal")
    public void afterReturningAdvice(Object retVal) {
        System.out.println("Returning:" + retVal.toString());
    }

    @AfterThrowing(pointcut = "selectAll()", throwing = "ex")
    public void AfterThrowingAdvice(IllegalArgumentException ex) {
        System.out.println("There has been an exception: " + ex.toString());
    }
}

修改 Beans.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-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <aop:aspectj-autoproxy/>

    <!-- Definition for book bean -->
    <bean id="book" class="com.xfc.Book">
        <property name="name" value="Effective Java"/>
        <property name="count" value="11"/>
    </bean>

    <!-- Definition for logging aspect -->
    <bean id="logging" class="com.xfc.Logging"/>

</beans>

运行 MainApp.java ,得到如下结果:

Going to setup book profile.
Count : 11
Book profile has been setup.
Returning:11
Going to setup book profile.
Name : Effective Java
Book profile has been setup.
Returning:Effective Java
Going to setup book profile.
Exception raised
Book profile has been setup.
There has been an exception: java.lang.IllegalArgumentException

Exception in thread “main” java.lang.IllegalArgumentException
at com.xfc.Book.printThrowException(Book.java:27)

Spring JDBC 框架

使用 Spring JDBC 框架进行数据库连接,它会负责所有的底层细节,从开始打开连接,准备和执行 SQL 语句,处理异常,处理事务,到最后关闭连接。

Spring JDBC 提供了几种方法和数据库中相应的不同的类与接口,如 JdbcTemplate。

JdbcTemplate 类

JdbcTemplate 类执行 SQL 查询、更新语句和存储过程调用,执行迭代结果集和提取返回参数值。它也捕获 JDBC 异常并转换它们到 org.springframework.dao 包中定义的通用类、更多的信息、异常层次结构。JdbcTemplate 类的实例是线程安全配置的。

JDBC 示例

注:与数据库相关的代码片段或演示,均优先选用 MySQL 为例。

创建数据库 spring-demo 并创建表 book

CREATE TABLE `book`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `count` int(11) NOT NULL,
  PRIMARY KEY (`id`)
)

代码示例:

本示例需要的文件:mysql-connector-java.jar、Book.java、BookDao.java、BookJDBCTemplate.java、BookMapper.java、Beans.xml、MainApp.java。

修改 Book.java

package com.xfc;

public class Book {

    private Integer id;
    private Integer count;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

添加 BookDao.java

package com.xfc;

import java.util.List;
import javax.sql.DataSource;

public interface BookDao {

    void setDataSource(DataSource dataSource);

    void create(String name, Integer count);

    Book getBook(Integer id);

    List<Book> listBooks();

    void delete(Integer id);

    void update(Integer id, Integer count);
}

添加 BookJDBCTemplate.java

package com.xfc;

import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.List;

public class BookJDBCTemplate implements BookDao {

    private DataSource dataSource;

    private JdbcTemplate jdbcTemplateObject;

    @Override
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        this.jdbcTemplateObject = new JdbcTemplate(dataSource);
    }

    @Override
    public void create(String name, Integer count) {
        String sql = "insert into Book (name, count) values (?, ?)";
        jdbcTemplateObject.update(sql, name, count);
        System.out.println("Created Record Name = " + name + " Count = " + count);
    }

    @Override
    public Book getBook(Integer id) {
        String sql = "select * from Book where id = ?";
        Book book = jdbcTemplateObject.queryForObject(sql, new Object[]{id}, new BookMapper());
        return book;
    }

    @Override
    public List<Book> listBooks() {
        String sql = "select * from Book";
        List <Book> books = jdbcTemplateObject.query(sql, new BookMapper());
        return books;
    }

    @Override
    public void delete(Integer id) {
        String sql = "delete from Book where id = ?";
        jdbcTemplateObject.update(sql, id);
        System.out.println("Deleted Record with ID = " + id );
    }

    @Override
    public void update(Integer id, Integer count) {
        String sql = "update Book set count = ? where id = ?";
        jdbcTemplateObject.update(sql, count, id);
        System.out.println("Updated Record with ID = " + id );
    }
}

添加 BookMapper.java

package com.xfc;

import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 数据映射配置
 */
public class BookMapper  implements RowMapper<Book> {

    @Override
    public Book mapRow(ResultSet rs, int i) throws SQLException {
        Book book = new Book();
        book.setId(rs.getInt("id"));
        book.setName(rs.getString("name"));
        book.setCount(rs.getInt("count"));
        return book;
    }
}

修改 Beans.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-3.0.xsd ">

    <!-- Initialization for data source -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring-demo?serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!-- Definition for bookJDBCTemplate bean -->
    <bean id="bookJDBCTemplate" class="com.xfc.BookJDBCTemplate">
        <property name="dataSource"  ref="dataSource"/>
    </bean>

</beans>

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        BookJDBCTemplate bookJDBCTemplate = (BookJDBCTemplate)context.getBean("bookJDBCTemplate");
        // 1. 添加数据
        bookJDBCTemplate.create("脂砚斋重评石头记", 11);
        bookJDBCTemplate.create("泥淖之子", 2);
        bookJDBCTemplate.create("中国文学作品选注", 15);

        // 2. 查询列表
//        List<Book> books = bookJDBCTemplate.listBooks();
//        for (Book record : books) {
//            System.out.print("ID : " + record.getId() );
//            System.out.print(", Name : " + record.getName() );
//            System.out.println(", Count : " + record.getCount());
//        }

        // 3. 修改数据
//        bookJDBCTemplate.update(2, 20);

        // 4. 查询单条
//        Book book = bookJDBCTemplate.getBook(2);
//        System.out.println("ID : " + book.getId() + ", Name : " + book.getName() + ", Count : " + book.getCount());

        // 5. 删除数据
//        bookJDBCTemplate.delete(1);
//        bookJDBCTemplate.delete(2);
//        bookJDBCTemplate.delete(3);
    }
}

依次取消 MainApp.java 中各部分的注释并运行,并配合观察数据库表中的数据。

当然,此处 JDBC 示例相对较为简单,对于较为复杂的业务,可以根据需求进行相应的扩展。

SQL 的存储过程

SimpleJdbcCall 类可以被用于调用一个包含 IN 和 OUT 参数的存储过程。

[❗️TODO]{.label .danger} 暂无示例代码

Spring 事务管理

事务管理

一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性说成是 ACID

  • 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
  • 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
  • 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
  • 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。

Spring 框架在不同的底层事务管理 APIs 的顶部提供了一个抽象层。Spring 的事务支持旨在通过添加事务能力到 POJOs 来提供给 EJB 事务一个选择方案。Spring 支持编程式和声明式事务管理。EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现。

局部事物 vs. 全局事务

局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。

局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。

全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。

编程式 vs. 声明式

Spring 支持两种类型的事务管理:

  • 编程式事务管理:这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。
  • 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注释或 XML 配置来管理事务。

声明式事务管理比编程式事务管理更可取,尽管它不如编程式事务管理灵活,但它允许你通过代码控制事务。但作为一种横切关注点,声明式事务管理可以使用 AOP 方法进行模块化。Spring 支持使用 Spring AOP 框架的声明式事务管理。

Spring 事务抽象

Spring 事务管理的五大属性:隔离级别、传播行为、是否只读、事务超时、回滚规则。{.wavy}

Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义。

事务隔离级别和传播类型,均在 TransactionDefinition 中定义了相关常量。

事务隔离级别:

  1. ISOLATION_DEFAULT

    这是默认的隔离级别。

  2. ISOLATION_READ_COMMITTED

    表明能够阻止误读;可以发生不可重复读和虚读。

  3. ISOLATION_READ_UNCOMMITTED

    表明可以发生误读、不可重复读和虚读。

  4. ISOLATION_REPEATABLE_READ

    表明能够阻止误读和不可重复读;可以发生虚读。

  5. ISOLATION_SERIALIZABLE

    表明能够阻止误读、不可重复读和虚读。

事务传播类型:

  1. PROPAGATION_MANDATORY

    支持当前事务;如果不存在当前事务,则抛出一个异常。

  2. PROPAGATION_NESTED

    如果存在当前事务,则在一个嵌套的事务中执行。

  3. PROPAGATION_NEVER

    不支持当前事务;如果存在当前事务,则抛出一个异常。

  4. PROPAGATION_NOT_SUPPORTED

    不支持当前事务;而总是执行非事务性。

  5. PROPAGATION_REQUIRED

    支持当前事务;如果不存在事务,则创建一个新的事务。

  6. PROPAGATION_REQUIRES_NEW

    创建一个新事务,如果存在一个事务,则把当前事务挂起。

  7. PROPAGATION_SUPPORTS

    支持当前事务;如果不存在,则执行非事务性。

  8. TIMEOUT_DEFAULT

    使用默认超时的底层事务系统,或者如果不支持超时则没有。

编程式事务管理

Spring 提供两种方式的编程式事务管理,分别是:使用 TransactionTemplate 和直接使用 PlatformTransactionManager。

保持上一示例的数据库 spring-demobook 并添加新表 edition

CREATE TABLE `edition`  (
  `bid` int(11) NOT NULL,
  `edition` varchar(255) NOT NULL,
  `press` varchar(255) NOT NULL
)

代码示例:

本示例需要的文件: BookEdition.java、BookDao.java、BookEditionMapper.java、BookJDBCTemplate.java、Beans.xml、MainApp.java。

新建 BookEdition.java

package com.xfc;

public class BookEdition {

    private Integer id;
    private Integer count;
    private String name;
    private Integer bid;
    private String edition;
    private String press;

    // setter and getter...
}

修改 BookDao.java

package com.xfc;

import org.springframework.transaction.PlatformTransactionManager;

import java.util.List;
import javax.sql.DataSource;

public interface BookDao {

    void setDataSource(DataSource dataSource);

    void setTransactionManager(PlatformTransactionManager transactionManager);

    void create(String name, Integer count, String edition, String press);

    List<BookEdition> listBooks();
}

新建 BookEditionMapper.java

package com.xfc;

import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class BookEditionMapper implements RowMapper<BookEdition> {

    @Override
    public BookEdition mapRow(ResultSet rs, int rowNum) throws SQLException {
        BookEdition bookEdition = new BookEdition();
        bookEdition.setId(rs.getInt("id"));
        bookEdition.setCount(rs.getInt("count"));
        bookEdition.setName(rs.getString("name"));
        bookEdition.setBid(rs.getInt("bid"));
        bookEdition.setEdition(rs.getString("edition"));
        bookEdition.setPress(rs.getString("press"));
        return bookEdition;
    }
}

修改 BookJDBCTemplate.java

package com.xfc;

import java.util.List;
import javax.sql.DataSource;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

public class BookJDBCTemplate implements BookDao {

    private DataSource dataSource;
    private JdbcTemplate jdbcTemplateObject;
    private PlatformTransactionManager transactionManager;

    @Override
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        this.jdbcTemplateObject = new JdbcTemplate(dataSource);
    }

    @Override
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @Override
    public void create(String name, Integer count, String edition, String press) {
        TransactionDefinition def = new DefaultTransactionDefinition();
        // 记录事务状态
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 保存Book
            String sql1 = "insert into Book (name, count) values (?, ?)";
            jdbcTemplateObject.update(sql1, name, count);
            // 获取Book表中最新的ID
            String sql2 = "select max(id) from Book";
            int bid = jdbcTemplateObject.queryForObject(sql2, Integer.class);
            // 保存Edition
            String sql3 = "insert into Edition(bid, edition, press) values (?, ?, ?)";
            jdbcTemplateObject.update(sql3, bid, edition, press);
            System.out.println("Created Name = " + name + ", Count = " + count + ", Edition = " + edition + ", Press = " + press);
            // 为了方便测试,可以在提交之前编写可预期的异常代码,例如:
            // System.out.println(1 / 0);
            // 并在 catch 块中捕获ArithmeticException
            // 提交事务状态
            transactionManager.commit(status);
        } catch (DataAccessException e) {
            System.out.println("Error in creating record, rolling back");
            transactionManager.rollback(status);
            throw e;
        }
    }

    @Override
    public List<BookEdition> listBooks() {
        String sql = "select * from Book, Edition where Book.id = Edition.bid";
        List<BookEdition> bookEdition = jdbcTemplateObject.query(sql, new BookEditionMapper());
        return bookEdition;
    }
}

修改 Beans.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-3.0.xsd ">

    <!-- Initialization for data source -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring-demo?serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!-- Initialization for TransactionManager -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Definition for bookJDBCTemplate bean -->
    <bean id="bookJDBCTemplate" class="com.xfc.BookJDBCTemplate">
        <property name="dataSource" ref="dataSource"/>
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

</beans>

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MainApp {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        BookJDBCTemplate bookJDBCTemplate = (BookJDBCTemplate) context.getBean("bookJDBCTemplate");

        bookJDBCTemplate.create("脂砚斋重评石头记", 11, "2017年10月第1版", "天津古籍出版社");
        bookJDBCTemplate.create("泥淖之子", 2, "2018.2", "广西人民出版社");
        bookJDBCTemplate.create("中国文学作品选注", 15, "2017年6月北京第1版", "中华书局");

        List<BookEdition> bookEdition = bookJDBCTemplate.listBooks();
        for (BookEdition record : bookEdition) {
            System.out.println("ID : " + record.getId() + ", Name : " + record.getName() + ", Edition : " + record.getEdition() + ", Press : " + record.getPress() + ", Count : " + record.getCount());
        }
    }
}

BookJDBCTemplate.create() 方法体中进行异常回滚测试,解除 System.out.println(1 / 0); 代码注释,抛出并捕获 ArithmeticException 异常。运行 MainApp.java 得到如下结果:

Created Name = 脂砚斋重评石头记, Count = 11, Edition = 2017年10月第1版, Press = 天津古籍出版社
Error in creating record, rolling back
Exception in thread “main” java.lang.ArithmeticException: / by zero
at com.xfc.BookJDBCTemplate.create(BookJDBCTemplate.java:46)
at com.xfc.MainApp.main(MainApp.java:13)

我们即可观察到事务实现回滚。

恢复代码,再次运行 MainApp.java 得到如下结果:

Created Name = 脂砚斋重评石头记, Count = 11, Edition = 2017年10月第1版, Press = 天津古籍出版社
Created Name = 泥淖之子, Count = 2, Edition = 2018.2, Press = 广西人民出版社
Created Name = 中国文学作品选注, Count = 15, Edition = 2017年6月北京第1版, Press = 中华书局
ID : 1, Name : 脂砚斋重评石头记, Edition : 2017年10月第1版, Press : 天津古籍出版社, Count : 11
ID : 2, Name : 泥淖之子, Edition : 2018.2, Press : 广西人民出版社, Count : 2
ID : 3, Name : 中国文学作品选注, Edition : 2017年6月北京第1版, Press : 中华书局, Count : 15

声明式事务管理

声明式事务管理方法允许你在配置的帮助下而不是源代码硬编程来管理事务。这意味着你可以将事务管理从事务代码中隔离出来。你可以只使用注解或基于配置的 XML 来管理事务,bean 配置会指定事务型方法。

代码示例:

本示例需要的文件:BookEdition.java、BookDao.java、BookEditionMapper.java、BookJDBCTemplate.java、Beans.xml、MainApp.java,其中 BookEdition.java、BookEditionMapper.java 文件与上一示例一致,此处不再重述。

修改 BookDao.java

package com.xfc;

import javax.sql.DataSource;
import java.util.List;

public interface BookDao {

    void setDataSource(DataSource dataSource);

    void create(String name, Integer count, String edition, String press);

    List<BookEdition> listBooks();
}

修改 BookJDBCTemplate.java

package com.xfc;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.List;

public class BookJDBCTemplate implements BookDao {

    private JdbcTemplate jdbcTemplateObject;

    @Override
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplateObject = new JdbcTemplate(dataSource);
    }

    @Override
    public void create(String name, Integer count, String edition, String press) {
        try {
            // 保存Book
            String sql1 = "insert into Book (name, count) values (?, ?)";
            jdbcTemplateObject.update(sql1, name, count);
            // 获取Book表中最新的ID
            String sql2 = "select max(id) from Book";
            int bid = jdbcTemplateObject.queryForObject(sql2, Integer.class);
            // 保存Edition
            String sql3 = "insert into Edition(bid, edition, press) values (?, ?, ?)";
            jdbcTemplateObject.update(sql3, bid, edition, press);
            System.out.println("Created Name = " + name + ", Count = " + count + ", Edition = " + edition + ", Press = " + press);
            // 测试异常
            throw new RuntimeException("simulate Error condition") ;
        } catch (DataAccessException e) {
            System.out.println("Error in creating record, rolling back");
            throw e;
        }
    }

    @Override
    public List<BookEdition> listBooks() {
        String sql = "select * from Book, Edition where Book.id = Edition.bid";
        List<BookEdition> bookEdition = jdbcTemplateObject.query(sql, new BookEditionMapper());
        return bookEdition;
    }
}

修改 Beans.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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- Initialization for data source -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring-demo?serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="create"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="createOperation" expression="execution(* com.xfc.BookJDBCTemplate.create(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/>
    </aop:config>

    <!-- Initialization for TransactionManager -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Definition for bookJDBCTemplate bean -->
    <bean id="bookJDBCTemplate" class="com.xfc.BookJDBCTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

修改 MainApp.java

package com.xfc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MainApp {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        BookDao bookJDBCTemplate = (BookDao) context.getBean("bookJDBCTemplate");

        bookJDBCTemplate.create("脂砚斋重评石头记", 11, "2017年10月第1版", "天津古籍出版社");
        bookJDBCTemplate.create("泥淖之子", 2, "2018.2", "广西人民出版社");
        bookJDBCTemplate.create("中国文学作品选注", 15, "2017年6月北京第1版", "中华书局");

        List<BookEdition> bookEdition = bookJDBCTemplate.listBooks();
        for (BookEdition record : bookEdition) {
            System.out.println("ID : " + record.getId() + ", Name : " + record.getName() + ", Edition : " + record.getEdition() + ", Press : " + record.getPress() + ", Count : " + record.getCount());
        }
    }
}

为了便于观察效果,推荐先清除数据库旧有数据。

运行 MainApp.java 得到如下结果:

Created Name = 脂砚斋重评石头记, Count = 11, Edition = 2017年10月第1版, Press = 天津古籍出版社
Exception in thread “main” java.lang.RuntimeException: simulate Error condition

观察数据库,得知当执行异常时,错误数据并未被添加到数据库,实现了事务回滚。

去除 BookJDBCTemplate.create() 方法体中的异常代码片段,再次运行得到如下结果:

Created Name = 脂砚斋重评石头记, Count = 11, Edition = 2017年10月第1版, Press = 天津古籍出版社
Created Name = 泥淖之子, Count = 2, Edition = 2018.2, Press = 广西人民出版社
Created Name = 中国文学作品选注, Count = 15, Edition = 2017年6月北京第1版, Press = 中华书局
ID : 1, Name : 脂砚斋重评石头记, Edition : 2017年10月第1版, Press : 天津古籍出版社, Count : 11
ID : 2, Name : 泥淖之子, Edition : 2018.2, Press : 广西人民出版社, Count : 2
ID : 3, Name : 中国文学作品选注, Edition : 2017年6月北京第1版, Press : 中华书局, Count : 15

Spring Web MVC 框架

注:此部分内容日常开发中应用较多,部分常规的内容不作详细说明。

Spring web MVC 框架提供了 模型-视图-控制 的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。

  • 模型 封装了应用程序数据,并且通常它们由 POJO 组成。
  • 视图 主要用于呈现模型数据,并且通常它生成客户端的浏览器可以解释的 HTML 输出。
  • 控制器 主要用于处理用户请求,并且构建合适的模型并将其传递到视图呈现。

DispatcherServlet

Spring Web 模型-视图-控制(MVC)框架是围绕 DispatcherServlet 设计的,DispatcherServlet 用来处理所有的 HTTP 请求和响应。

DispatcherServlet 工作流程图:

dispatcherservlet

Hello World 例子

准备

创建新的动态web项目:File --> New --> Project --> Java Enterprise --> 勾选Web Application。

在 WEB-INF 目录下创建 jsp、lib 文件夹。将此此前用到的 spring 相关的jar导入到 lib 目录,并添加到 libaraies 。

在 WEB-INF 文件夹下创建 hello-servlet.xml(文件名遵循 [servlet-name]-servlet.xml )。

在 src 目录下创建包 com.xfc,并在包路径下创建 HelloController.java。

配置 web 容器,以下示例使用 tomcat 作为 web 容器。

代码示例:

hello-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
       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.0.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="com.xfc"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

web.xml

<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>spring-mvc</display-name>
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

index.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head>
    <title>Hello World</title>
</head>
<body>
<h2>${message}</h2>
</body>
</html>

HelloController.java

package com.xfc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.ui.ModelMap;

@Controller
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping(method = RequestMethod.GET)
    public String printHello(ModelMap model) {
        model.addAttribute("message", "Hello Spring MVC Framework!");
        return "index";
    }
}

启动项目并在浏览器访问 http://localhost:8080/hello

shituxiaoguo

Spring MVC 表单处理例子

代码示例:

基于上一示例。

新建 Book.java

package com.xfc;

public class Book {

    private Integer id;
    private String name;
    private Integer count;
    
    // setter and getter...
}

新建 BookController.java

package com.xfc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class BookController {

    @RequestMapping(value = "/book", method = RequestMethod.GET)
    public ModelAndView student() {
        return new ModelAndView("book", "command", new Book());
    }

    @RequestMapping(value = "/addBook", method = RequestMethod.POST)
    public String addStudent(@ModelAttribute("SpringWeb") Book book, ModelMap model) {
        model.addAttribute("name", book.getName());
        model.addAttribute("count", book.getCount());
        model.addAttribute("id", book.getId());
        return "result";
    }
}

新建 book.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html>
<head>
    <title>Spring MVC Form Handling</title>
</head>
<body>

<h2>Student Information</h2>
<form:form method="POST" action="/addBook">
    <table>
        <tr>
            <td><form:label path="name">Name</form:label></td>
            <td><form:input path="name"/></td>
        </tr>
        <tr>
            <td><form:label path="count">Count</form:label></td>
            <td><form:input path="count"/></td>
        </tr>
        <tr>
            <td><form:label path="id">id</form:label></td>
            <td><form:input path="id"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Submit"/>
            </td>
        </tr>
    </table>
</form:form>
</body>
</html>

新建 result.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html>
<head>
    <title>Spring MVC Form Handling</title>
</head>
<body>

<h2>Submitted Book Information</h2>
<table>
    <tr>
        <td>Name</td>
        <td>${name}</td>
    </tr>
    <tr>
        <td>Count</td>
        <td>${count}</td>
    </tr>
    <tr>
        <td>ID</td>
        <td>${id}</td>
    </tr>
</table>
</body>
</html>

启动项目并在浏览器访问 http://localhost:8080/book

bookformweb

填写表单后 submit :

booksubmitresult

Spring 页面重定向例子

代码示例:

基于上一示例。

新建 WebController.java

package com.xfc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class WebController {
    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index() {
        return "index";
    }

    @RequestMapping(value = "/redirect", method = RequestMethod.GET)
    public String redirect() {
        return "redirect:finalPage";
    }

    @RequestMapping(value = "/finalPage", method = RequestMethod.GET)
    public String finalPage() {
        return "final";
    }
}

修改 index.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html>
<head>
    <title>Spring Page Redirection</title>
</head>
<body>
<h2>Spring Page Redirection</h2>
<p>Click below button to redirect the result to new page</p>
<form:form method="GET" action="/redirect">
    <table>
        <tr>
            <td>
                <input type="submit" value="Redirect Page"/>
            </td>
        </tr>
    </table>
</form:form>
</body>
</html>

新建 final.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html>
<head>
    <title>Spring Page Redirection</title>
</head>
<body>

<h2>Redirected Page</h2>

</body>
</html>

启动项目并在浏览器访问 http://localhost:8080/index

redirect_index

点击 Redirect Page 按钮:

redirect_final

Spring 静态页面例子

代码示例:

基于上一示例。

修改 WebController.java

package com.xfc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class WebController {
    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index() {
        return "index";
    }

    @RequestMapping(value = "/staticPage", method = RequestMethod.GET)
    public String redirect() {
        return "redirect:/pages/final.html";
    }
}

修改 hello-servlet.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="com.xfc"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    
    <!-- 映射静态文件 -->
    <mvc:resources mapping="/pages/**" location="/WEB-INF/pages/"/><mvc:annotation-driven/>
</beans>

在 WEB-INF 下创建 pages/final.html 文件。

<html>
<head>
    <title>Spring Static Page</title>
</head>
<body>

<h2>A simple HTML page</h2>

</body>
</html>

修改 index.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring Landing Page</title>
</head>
<body>
<h2>Spring Landing Pag</h2>
<p>Click below button to get a simple HTML page</p>
<form:form method="GET" action="/staticPage">
    <table>
        <tr>
            <td>
                <input type="submit" value="Get HTML Page"/>
            </td>
        </tr>
    </table>
</form:form>
</body>
</html>

启动项目并在浏览器访问 http://localhost:8080/index

static_index

点击 Get HTML Page 按钮:

static_final

Spring 异常处理例子

代码示例:

基于上一示例。

创建 error.jsp

<html>
    <head>
        <title>Spring Error Page</title>
    </head>
    <body>

        <p>An error occured, please contact webmaster.</p>

    </body>
</html>

创建 ExceptionPage.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html>
    <head>
        <title>Spring MVC Exception Handling</title>
    </head>
    <body>
        <h2>Spring MVC Exception Handling</h2>
        <h3>${exception.exceptionMsg}</h3>
    </body>
</html>

创建 SpringException.java

package com.xfc;

public class SpringException extends RuntimeException {

    private String exceptionMsg;

    public SpringException(String exceptionMsg) {
        this.exceptionMsg = exceptionMsg;
    }

    public String getExceptionMsg() {
        return this.exceptionMsg;
    }

    public void setExceptionMsg(String exceptionMsg) {
        this.exceptionMsg = exceptionMsg;
    }
}

修改 hello-servlet.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="com.xfc"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--映射静态文件-->
    <mvc:resources mapping="/pages/**" location="/WEB-INF/pages/"/><mvc:annotation-driven/>

    <!--异常处理-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="com.xfc.SpringException">ExceptionPage</prop>
            </props>
        </property>
        <property name="defaultErrorView" value="error"/>
    </bean>
</beans>

修改 BookController.java

package com.xfc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class BookController {

    @RequestMapping(value = "/book", method = RequestMethod.GET)
    public ModelAndView student() {
        return new ModelAndView("book", "command", new Book());
    }

    @RequestMapping(value = "/addBook", method = RequestMethod.POST)
    @ExceptionHandler({SpringException.class})
    public String addStudent(@ModelAttribute("SpringWeb") Book book, ModelMap model) {
        if(book.getName().length() < 20) {
            throw new SpringException("Given name is too short");
        } else {
            model.addAttribute("name", book.getName());
        }
        if(book.getCount() < 10) {
            throw new SpringException("Given count is too low");
        } else {
            model.addAttribute("count", book.getCount());
        }
        model.addAttribute("id", book.getId());
        return "result";
    }
}

启动项目并在浏览器访问 http://localhost:8080/book

biaodance

填写表单并 Submit:

exception_page

Spring 使用 Log4J 记录日志

[❗️TODO]{.label .danger} 待完善

参考

版权声明

本文链接:https://www.chinmoku.cc/java/framework/spring-tutorial/

本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。

Press ESC to close