Spring 知识点笔记

体系结构

Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入。Spring 框架提供约 20 个模块,可以根据应用程序的要求来使用。

核心容器

核心容器由spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL,Spring表达式语言,Spring Expression Language)等模块组成,它们的细节如下:

  • spring-core模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。

  • spring-beans 模块提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。

  • context模块建立在由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模块提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。

它们的完整依赖关系如下图所示:

数据访问/集成

数据访问/集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块,它们的细节如下:注:JDBC=Java Data Base Connectivity,ORM=Object Relational Mapping,OXM=Object XML Mapping,JMS=Java Message Service)

  • JDBC 模块提供了JDBC抽象层,它消除了冗长的JDBC编码和对数据库供应商特定错误代码的解析。

  • ORM 模块提供了对流行的对象关系映射API的集成,包括JPA、JDO和Hibernate等。通过此模块可以让这些ORM框架和spring的其它功能整合,比如前面提及的事务管理。

  • OXM 模块提供了对OXM实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。

  • JMS 模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了spring-messaging模块。。

事务模块为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。(注:编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由spring自动处理,编程式事务粒度更细)

Web

Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成,它们的细节如下:

  • Web 模块提供面向web的基本功能和面向web的应用上下文,比如多部分(multipart)文件上传功能、使用Servlet监听器初始化IoC容器等。它还包括HTTP客户端以及Spring远程调用中与web相关的部分。。

  • Web-MVC 模块为web应用提供了模型视图控制(MVC)和REST Web服务的实现。Spring的MVC框架可以使领域模型代码和web表单完全地分离,且可以与Spring框架的其它所有功能进行集成。

  • Web-Socket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。

  • Web-Portlet 模块提供了用于Portlet环境的MVC实现,并反映了spring-webmvc模块的功能。

其他

还有其他一些重要的模块,像 AOP,Aspects,Instrumentation,Web 和测试模块,它们的细节如下:

  • AOP 模块提供了面向方面的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。

  • Aspects 模块提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。

  • Instrumentation 模块在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。

  • Messaging 模块为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。

  • 测试模块支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。

Spring IoC 容器

Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans

IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而“控制反转”是指new实例工作不由程序员来做而是交给Spring容器来做。Spring 提供了以下两种不同类型的容器:

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

Spring 的 BeanFactory 容器(忽略)

这是一个最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持,这个容器接口在 org.springframework.beans.factory.BeanFactor 中被定义。BeanFactory 和相关的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍旧保留在 Spring 中,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。

在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。

Spring ApplicationContext 容器

Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文。 这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。

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

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

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

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class HelloSpringSingleton {

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/res/applicationContext.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.setName("I'm object A");
objA.sqyHello();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.sqyHello();
}

}

public class HelloWorld {
private String name;

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

public String getName() {
System.out.println(name);
return name;
}

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

}

// /res/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">

<bean id="helloWorld" class="com.zsm.test.HelloWorld"
scope="singleton">
</bean>

<bean id="helloWorld1" class="com.zsm.test.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="name" value="Hello World!"/>
</bean>

</beans>

Spring Bean 定义

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

bean 定义包含称为配置元数据的信息,下述容器也需要知道配置元数据:

  • 如何创建一个 bean

  • bean 的生命周期的详细信息

  • bean 的依赖关系

Spring IoC 容器完全由实际编写的配置元数据的格式解耦。有下面三个重要的方法把配置元数据提供给 Spring 容器:

  • 基于 XML 的配置文件

  • 基于注解的配置

  • 基于 Java 的配置

Spring Bean 作用域

Spring Bean 生命周期

为了定义安装和拆卸一个 bean,我们只要声明带有 init-method 或 destroy-method 参数的 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。

Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁。

初始化及销毁回调

org.springframework.beans.factory.InitializingBean 接口指定一个单一的方法:

1
void afterPropertiesSet() throws Exception;

因此,你可以简单地实现上述接口和初始化工作可以在 afterPropertiesSet() 方法中执行,如下所示:

1
2
3
4
5
public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}

在基于 XML 的配置元数据的情况下,你可以使用 init-method 属性来指定带有 void 无参数方法的名称。例如:

1
<bean id="exampleBean" class="examples.ExampleBean" init-method="init"/>

下面是类的定义:

1
2
3
4
5
public class ExampleBean {
public void init() {
// do some initialization work
}
}

销毁回调 org.springframework.beans.factory.DisposableBean 与上述初始化回调类似。

main方法中,你需要注册一个在 AbstractApplicationContext 类中声明的关闭 hook 的 registerShutdownHook() 方法。它将确保正常关闭,并且调用相关的 destroy 方法。

1
2
3
4
5
6
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
context.registerShutdownHook();
}

建议不要使用 InitializingBean 或者 DisposableBean 的回调方法,因为 XML 配置在命名方法上提供了极大的灵活性。

默认的初始化和销毁方法

如果你有太多具有相同名称的初始化或者销毁方法的 Bean,那么你不需要在每一个 bean 上声明初始化方法和销毁方法。框架使用 元素中的 default-init-method 和 default-destroy-method 属性提供了灵活地配置这种情况,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<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>

Spring——Bean 后置处理器

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

BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你可以配置多个 BeanPostProcessor 接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。

ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}

<bean class="com.zsm.test.InitHelloWorld" />

Bean 定义继承

Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。示例如下:

1
2
3
4
5
6
7
8
9
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld">
<property name="message1" value="Hello World!"/>
<property name="message2" value="Hello Second World!"/>
</bean>

<bean id="helloIndia" class="com.tutorialspoint.HelloIndia" parent="helloWorld">
<property name="message1" value="Hello India!"/>
<property name="message3" value="Namaste India!"/>
</bean>

Spring 依赖注入

基于构造函数的依赖注入

几种写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<beans>

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.zsm.test.di.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>

<!-- Definition for spellChecker bean -->
<!--<bean id="spellChecker" class="com.zsm.test.di.SpellChecker"/>-->

<bean id="spellChecker" class="com.zsm.test.di.SpellChecker">
<!--存在不止一个参数时,构造函数的参数在 bean 定义中的顺序就是把这些参数提供给适当的构造函数的顺序就可以了。考虑下面的类:-->
<!--<constructor-arg ref="helloWorld"/>
<constructor-arg ref="helloWorld2"/>-->

<!--如果你使用 type 属性显式的指定了构造函数参数的类型,容器也可以使用与简单类型匹配的类型。例如:-->
<!--<constructor-arg type="int" value="2001"/>
<constructor-arg type="java.lang.String" value="Zara"/>-->

<!--最后并且也是最好的传递构造函数参数的方式,使用 index 属性来显式的指定构造函数参数的索引。下面是基于索引为 0 的例子,如下所示:-->
<constructor-arg index="0" value="2001"/>
<constructor-arg index="1" value="Zara"/>
</bean>

<bean id="helloWorld" class="com.zsm.test.HelloWorld">
<property name="name" value="Hello World!"/>
</bean>
<bean id="helloWorld2" class="com.zsm.test.HelloWorld2">
<property name="name2" value="Hello World2!"/>
</bean>

</beans>

public class SpellChecker {
public SpellChecker() {
System.out.println("Inside SpellChecker constructor.");
}

public SpellChecker(int num, String desc) {
System.out.println("Inside SpellChecker constructor:" + num + ";" + desc);
}

public SpellChecker(HelloWorld num, HelloWorld2 desc) {
System.out.println("Inside SpellChecker constructor:" + num.getName() + ";" + desc.getName2());
}

public void checkSpelling() {
System.out.println("Inside checkSpelling.");
}

}

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
}

基于设值函数的依赖注入

和构造函数注入唯一的区别就是在基于构造函数注入中,我们使用的是〈bean〉标签中的〈constructor-arg〉元素,而在基于设值函数的注入中,我们使用的是〈bean〉标签中的〈property〉元素。

第二个需要注意的点是,如果你要把一个引用传递给一个对象,那么你需要使用 标签的 ref 属性,而如果你要直接传递一个值,那么你应该使用 value 属性。

如果你有许多的设值函数方法,那么在 XML 配置文件中使用 p-namespace 是非常方便的(ref则须加后缀-ref)。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<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="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>

<bean name="jane" class="com.example.Person">
<property name="name" value="John Doe"/>
</bean>

</beans>


//上述 XML 配置文件可以使用 p-namespace 以一种更简洁的方式重写,如下所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="john-classic" class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
</bean>

<bean name="jane" class="com.example.Person"
p:name="John Doe"/>
</bean>

</beans>

注入内部 Beans

正如你所知道的 Java 内部类是在其他类的范围内被定义的,同理,inner beans 是在其他 bean 的范围内定义的 bean。定义入下:

1
2
3
4
5
<bean id="outerBean" class="...">
<property name="target">
<bean id="innerBean" class="..."/>
</property>
</bean>

示例:

1
2
3
4
5
<bean id="textEditor" class="com.zsm.test.interclass.TextEditor">
<property name="spellChecker">
<bean class="com.zsm.test.interclass.SpellChecker"/>
</property>
</bean>

注入集合

现在如果你想传递多个值,如 Java Collection 类型 List、Set、Map 和 Properties,应该怎么做呢。为了处理这种情况,Spring 提供了四种类型的集合的配置元素,如下所示:

示例入下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<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 javaCollection -->
<bean id="javaCollection" class="com.tutorialspoint.JavaCollection">

<!-- results in a setAddressList(java.util.List) call -->
<property name="addressList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</list>
</property>

<!-- results in a setAddressSet(java.util.Set) call -->
<property name="addressSet">
<set>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</set>
</property>

<!-- results in a setAddressMap(java.util.Map) call -->
<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>

<!-- results in a setAddressProp(java.util.Properties) call -->
<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>

</bean>

</beans>

可以将引用和值混合在一起,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- Passing bean reference  for java.util.Set -->
<property name="addressSet">
<set>
<ref bean="address1"/>
<ref bean="address2"/>
<value>Pakistan</value>
</set>
</property>

<!-- Passing bean reference for java.util.Map -->
<property name="addressMap">
<map>
<entry key="one" value="INDIA"/>
<entry key ="two" value-ref="address1"/>
<entry key ="three" value-ref="address2"/>
</map>
</property>

注入 null 和空字符串的值

1
2
3
<bean id="..." class="exampleBean">
<property name="email" value=""/>
</bean>

相当于:exampleBean.setEmail(“”)。

1
2
3
<bean id="..." class="exampleBean">
<property name="email"><null/></property>
</bean>

相当于:exampleBean.setEmail(null)。

Beans 自动装配

Spring 容器可以在不使用<constructor-arg>和<property> 元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。

你可以使用<bean>元素的 autowire 属性为一个 bean 定义指定自动装配模式:

可以使用 byType 或者 constructor 自动装配模式来连接数组和其他类型的集合。

自动装配的局限性

自动装配始终在同一个项目中使用时,它的效果最好。如果通常不使用自动装配,它可能会使开发人员混淆的使用它来连接只有一个或两个 bean 定义。不过,自动装配可以显著减少需要指定的属性或构造器参数,但你应该在使用它们之前考虑到自动装配的局限性和缺点。

自动装配 ‘byName’

这种模式由属性名称指定自动装配。

例如,在配置文件中,如果一个 bean 定义设置为自动装配 byName,并且它包含 spellChecker 属性(即,它有一个 setSpellChecker(…) 方法),那么 Spring 就会查找定义名为 spellChecker 的 bean,并且用它来设置这个属性。你仍然可以使用 标签连接其余的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--<bean id="textEditor" class="com.zsm.test.interclass.TextEditor">
<property name="spellChecker" ref="spellChecker"/>
<property name="name" value="Generic Text Editor" />
</bean>-->

//使用autowire后
<bean id="textEditor" class="com.tutorialspoint.TextEditor"
autowire="byName">
<property name="name" value="Generic Text Editor" />
</bean>

<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>

自动装配 ‘byType’

这种模式由属性类型指定自动装配。

由构造函数自动装配

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

基于注解的配置

从 Spring 2.5 开始就可以使用注解来配置依赖注入,而不是采用 XML 来描述一个 bean 连线。在 XML 注入之前进行注解注入,因此后者的配置将通过两种方式的属性连线被前者重写。

注解连线在默认情况下在 Spring 容器中不打开。启用配置如下:

1
<context:annotation-config/>

让我们来看看几个重要的注解,并且了解它们是如何工作的:

@Required 注释

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student {
private Integer age;
private String name;
@Required
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
@Required
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

<bean id="student" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

@Autowired 注释

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

1.Setter 方法中的 @Autowired
你可以在 XML 文件中的 setter 方法中使用 @Autowired 注释来除去 元素。当 Spring遇到一个在 setter 方法中使用的 @Autowired 注释,它会在方法中视图执行 byType 自动连接。

2.构造函数中的 @Autowired
你也可以在构造函数中使用 @Autowired。一个构造函数 @Autowired 说明当创建 bean 时,即使在 XML 文件中没有使用 元素配置 bean ,构造函数也会被自动连接。

3.属性中的 @Autowired
你可以在属性中使用 @Autowired 注释来除去 setter 方法。当时使用 为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性。

4.@Autowired 的(required=false)选项
默认情况下,@Autowired 注释意味着依赖是必须的,它类似于 @Required 注释,然而,你可以使用 @Autowired 的 (required=false) 选项关闭默认行为。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TextEditor {
@Autowired
private SpellChecker spellChecker;
public TextEditor() {
System.out.println("Inside TextEditor constructor." );
}
public SpellChecker getSpellChecker( ){
return spellChecker;
}
public void spellCheck(){
spellChecker.checkSpelling();
}
}

<!-- Definition for textEditor bean -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
</bean>

<!-- Definition for spellChecker bean -->
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>

@Qualifier 注释

当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。相当于别名

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Profile {
@Autowired
@Qualifier("student1")
private Student student;
public Profile(){
System.out.println("Inside Profile constructor." );
}
public void printAge() {
System.out.println("Age : " + student.getAge() );
}
public void printName() {
System.out.println("Name : " + student.getName() );
}
}

<!-- Definition for profile bean -->
<bean id="profile" class="com.tutorialspoint.Profile">
</bean>

<!-- Definition for student1 bean -->
<bean id="student1" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

<!-- Definition for student2 bean -->
<bean id="student2" class="com.tutorialspoint.Student">
<property name="name" value="Nuha" />
<property name="age" value="2"/>
</bean>

Spring JSR-250 注释

Spring还使用基于 JSR-250 注释,它包括 @PostConstruct, @PreDestroy 和 @Resource 注释。这些注释并不是必需的,只是一个替代方案

为了定义一个 bean 的安装和卸载,我们使用 init-method 和/或 destroy-method 参数简单的声明一下 。init-method 属性指定了一个方法,该方法在 bean 的实例化阶段会立即被调用。同样地,destroy-method 指定了一个方法,该方法只在一个 bean 从容器中删除之前被调用。你可以使用 @PostConstruct 注释作为初始化回调函数的一个替代,@PreDestroy 注释作为销毁回调函数的一个替代

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TextEditor {
private SpellChecker spellChecker;
@Resource(name= "spellChecker")
public void setSpellChecker( SpellChecker spellChecker ){
this.spellChecker = spellChecker;
}
public SpellChecker getSpellChecker(){
return spellChecker;
}
public void spellCheck(){
spellChecker.checkSpelling();
}
}


public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public String getMessage(){
System.out.println("Your Message : " + message);
return message;
}
@PostConstruct
public void init(){
System.out.println("Bean is going through init.");
}
@PreDestroy
public void destroy(){
System.out.println("Bean will destroy now.");
}
}

<bean id="helloWorld"
class="com.tutorialspoint.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!"/>
</bean>

基于 Java 的配置

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

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class HelloWorldConfig {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}

// 上面的代码将等同于下面的 XML 配置:
<beans>
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
</beans>

在这里,带有 @Bean 注解的方法名称作为 bean 的 ID,它创建并返回实际的 bean。你的配置类可以声明多个 @Bean。一旦定义了配置类,你就可以使用 AnnotationConfigApplicationContext 来加载并把他们提供给 Spring 容器,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfig.class);
HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
helloWorld.setMessage("Hello World!");
helloWorld.getMessage();
}

//你可以加载各种配置类,如下所示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}

@import

@import 注解允许从另一个配置类中加载 @Bean 定义。考虑 ConfigA 类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}

//你可以在另一个 Bean 声明中导入上述 Bean 声明,如下所示:
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B a() {
return new A();
}
}

现在,当实例化上下文时,不需要同时指定 ConfigA.class 和 ConfigB.class,只有 ConfigB 类需要提供,如下所示:

1
2
3
4
5
6
7
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}

生命周期回调

@Bean 注解支持指定任意的初始化和销毁的回调方法,就像在 bean 元素中 Spring 的 XML 的初始化方法和销毁方法的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Foo {
public void init() {
// initialization logic
}
public void cleanup() {
// destruction logic
}
}

@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup" )
public Foo foo() {
return new Foo();
}
}

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

1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public Foo foo() {
return new Foo();
}
}

事件处理

Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期。当加载 beans 时,ApplicationContext 发布某些类型的事件。例如,当上下文启动时,ContextStartedEvent 发布,当上下文停止时,ContextStoppedEvent 发布。

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

由于 Spring 的事件处理是单线程的,所以如果一个事件被发布,直至并且除非所有的接收者得到的该消息,该进程被阻塞并且流程将不会继续。因此,如果事件处理被使用,在设计应用程序时应注意。

自定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import org.springframework.context.ApplicationEvent;
public class CustomEvent extends ApplicationEvent{
public CustomEvent(Object source) {
super(source);
}
public String toString(){
return "My Custom Event";
}
}

//下面是 CustomEventPublisher.java 文件的内容:
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
public class CustomEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
public void setApplicationEventPublisher(ApplicationEventPublisher publisher){
this.publisher = publisher;
}
public void publish() {
CustomEvent ce = new CustomEvent(this);
publisher.publishEvent(ce);
}
}

//下面是 CustomEventHandler.java 文件的内容:
import org.springframework.context.ApplicationListener;
public class CustomEventHandler implements ApplicationListener<CustomEvent>{
public void onApplicationEvent(CustomEvent event) {
System.out.println(event.toString());
}
}

//下面是 MainApp.java 文件的内容:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
CustomEventPublisher cvp = (CustomEventPublisher) context.getBean("customEventPublisher");
cvp.publish();
cvp.publish();
}
}

//下面是配置文件 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="customEventHandler"
class="com.tutorialspoint.CustomEventHandler"/>

<bean id="customEventPublisher"
class="com.tutorialspoint.CustomEventPublisher"/>

</beans>


//输出以下信息:
My Custom Event
My Custom Event

Spring 框架的 AOP

Spring 框架的一个关键组件是面向方面的编程(AOP)框架。面向方面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样的常见的很好的方面的例子,如日志记录、审计、声明式事务、安全性和缓存等。

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

AOP 术语

通知的类型

Spring 方面可以使用下面提到的五种通知工作:

基于 AOP 的 XML架构

声明一个 aspect

一个 aspect 是使用 元素声明的,支持的 bean 是使用 ref 属性引用的,如下所示:

1
2
3
4
5
6
7
8
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

声明一个切入点

一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法):

1
2
3
4
5
6
7
8
9
10
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

声明建议

你可以使用 <aop:{ADVICE NAME}> 元素在一个 中声明五个建议中的任何一个,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.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>

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
public class Logging {
/**
* This is the method which I would like to execute
* before a selected method execution.
*/
public void beforeAdvice(){
System.out.println("Going to setup student profile.");
}
/**
* This is the method which I would like to execute
* after a selected method execution.
*/
public void afterAdvice(){
System.out.println("Student profile has been setup.");
}
/**
* This is the method which I would like to execute
* when any method returns.
*/
public void afterReturningAdvice(Object retVal){
System.out.println("Returning:" + retVal.toString() );
}
/**
* This is the method which I would like to execute
* if there is an exception raised.
*/
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("There has been an exception: " + ex.toString());
}
}

public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
System.out.println("Age : " + age );
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
System.out.println("Name : " + name );
return name;
}
public void printThrowException(){
System.out.println("Exception raised");
throw new IllegalArgumentException();
}
}

public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("BeansAop.xml");
Student student = (Student) context.getBean("student");
student.getName();
student.getAge();
student.printThrowException();
}
}

<?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.zsm.test.aop.*.*(..))"/>
<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>

<!--<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll"
expression="execution(* com.zsm.test.aop.Student.getName(..))"/>
<aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
<aop:after pointcut-ref="selectAll" method="afterAdvice"/>
</aop:aspect>
</aop:config>-->

<!-- Definition for student bean -->
<bean id="student" class="com.zsm.test.aop.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

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

</beans>

//运行结果
Going to setup student profile.
Name : Zara
Student profile has been setup.
Returning:Zara
Going to setup student profile.
Age : 11
Student profile has been setup.
Returning:11
Going to setup student profile.
Exception raised
Student profile has been setup.
There has been an exception: java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
at com.zsm.test.aop.Student.printThrowException(Student.java:22)
...

基于 AOP 的 @AspectJ

@AspectJ 作为通过 Java 5 注释注释的普通的 Java 类,它指的是声明 aspects 的一种风格。通过在你的基于架构的 XML 配置文件中包含以下元素,@AspectJ 支持是可用的。

1
<aop:aspectj-autoproxy/>

注意:aspectjrt.jar,aspectjweaver.jar添加到lib

声明一个 aspect

Aspects 类和其他任何正常的 bean 一样,除了它们将会用 @AspectJ 注释之外,它和其他类一样可能有方法和字段,如下所示:

1
2
3
@Aspect
public class AspectModule {
}

声明一个切入点

一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法)。在处理基于配置的 XML 架构时,切入点的声明有两个部分:

  • 一个切入点表达式决定了我们感兴趣的哪个方法会真正被执行。

  • 一个切入点标签包含一个名称和任意数量的参数。方法的真正内容是不相干的,并且实际上它应该是空的。

下面的示例中定义了一个名为 ‘businessService’ 的切入点,该切入点将与 com.xyz.myapp.service 包下的类中可用的每一个方法相匹配:

1
2
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // expression 
private void businessService() {}

声明建议

你可以使用 @{ADVICE-NAME} 注释声明五个建议中的任意一个,如下所示。这假设你已经定义了一个切入点标签方法 businessService():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Before("businessService()")
public void doBeforeTask(){
...
}
@After("businessService()")
public void doAfterTask(){
...
}
@AfterReturning(pointcut = "businessService()", returning="retVal")
public void doAfterReturnningTask(Object retVal){
// you can intercept retVal here.
...
}
@AfterThrowing(pointcut = "businessService()", throwing="ex")
public void doAfterThrowingTask(Exception ex){
// you can intercept thrown exception here.
...
}
@Around("businessService()")
public void doAroundTask(){
...
}

你可以为任意一个建议定义你的切入点内联。下面是在建议之前定义内联切入点的一个示例:

1
2
3
4
@Before("execution(* com.xyz.myapp.service.*.*(..))")
public doBeforeTask(){
...
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Aspect
public class Logging {
/** Following is the definition for a pointcut to select
* all the methods available. So advice will be called
* for all the methods.
*/
@Pointcut("execution(* com.zsm.test.aop.anno.*.*(..))")
private void selectAll(){}
/**
* This is the method which I would like to execute
* before a selected method execution.
*/
@Before("selectAll()")
public void beforeAdvice(){
System.out.println("Going to setup student profile.");
}
/**
* This is the method which I would like to execute
* after a selected method execution.
*/
@After("selectAll()")
public void afterAdvice(){
System.out.println("Student profile has been setup.");
}
/**
* This is the method which I would like to execute
* when any method returns.
*/
@AfterReturning(pointcut = "selectAll()", returning="retVal")
public void afterReturningAdvice(Object retVal){
System.out.println("Returning:" + retVal.toString() );
}
/**
* This is the method which I would like to execute
* if there is an exception raised by any method.
*/
@AfterThrowing(pointcut = "selectAll()", throwing = "ex")
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("There has been an exception: " + ex.toString());
}
}

<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 student bean -->
<bean id="student" class="com.zsm.test.aop.anno.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

<!-- Definition for logging aspect -->
<bean id="logging" class="com.zsm.test.aop.anno.Logging"/>

</beans>

Spring JDBC 框架

Spring JDBC 提供几种方法和数据库中相应的不同的类与接口。我将给出使用 JdbcTemplate 类框架的经典和最受欢迎的方法。这是管理所有数据库通信和异常处理的中央框架类。

JdbcTemplate 类

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

JdbcTemplate 类的实例是线程安全配置的。所以你可以配置 JdbcTemplate 的单个实例,然后将这个共享的引用安全地注入到多个 DAOs 中。

使用 JdbcTemplate 类时常见的做法是在你的 Spring 配置文件中配置数据源,然后共享数据源 bean 依赖注入到 DAO 类中,并在数据源的设值函数中创建了 JdbcTemplate。

配置数据源

我们在数据库 TEST 中创建一个数据库表 Student。假设你正在使用 MySQL 数据库,如果你使用其他数据库,那么你可以改变你的 DDL 和相应的 SQL 查询。

1
2
3
4
5
6
CREATE TABLE Student(
ID INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
AGE INT NOT NULL,
PRIMARY KEY (ID)
);

现在,我们需要提供一个数据源到 JdbcTemplate 中,所以它可以配置本身来获得数据库访问。你可以在 XML 文件中配置数据源,其中一段代码如下所示:

1
2
3
4
5
6
7
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>

注意点:
1.Could not load JDBC driver class [com.mysql.jdbc.Driver]异常: mysql-connector-java-5.1.8.jar
2.MySQL在高版本需要指明是否进行SSL连接,在mysql连接字符串url中加入ssl=true或者false即可,如:url=jdbc:mysql://127.0.0.1:3306/framework?useSSL=true

执行 SQL 语句

我们看看如何使用 SQL 和 jdbcTemplate 对象在数据库表中执行 CRUD(创建、读取、更新和删除)操作。

查询一个整数类型:

1
2
String SQL = "select count(*) from Student";
int rowCount = jdbcTemplateObject.queryForInt( SQL );

查询一个 long 类型:

1
2
String SQL = "select count(*) from Student";
long rowCount = jdbcTemplateObject.queryForLong( SQL );

一个使用绑定变量的简单查询:

1
2
String SQL = "select age from Student where id = ?";
int age = jdbcTemplateObject.queryForInt(SQL, new Object[]{10});

查询字符串:

1
2
String SQL = "select name from Student where id = ?";
String name = jdbcTemplateObject.queryForObject(SQL, new Object[]{10}, String.class);

查询并返回一个对象:

1
2
3
4
5
6
7
8
9
10
11
String SQL = "select * from Student where id = ?";
Student student = jdbcTemplateObject.queryForObject(SQL, new Object[]{10}, new StudentMapper());
public class StudentMapper implements RowMapper<Student> {
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student student = new Student();
student.setID(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
return student;
}
}

查询并返回多个对象:

1
2
3
4
5
6
7
8
9
10
11
String SQL = "select * from Student";
List<Student> students = jdbcTemplateObject.query(SQL, new StudentMapper());
public class StudentMapper implements RowMapper<Student> {
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student student = new Student();
student.setID(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
return student;
}
}

在表中插入一行:

1
2
String SQL = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL, new Object[]{"Zara", 11} );

更新表中的一行:

1
2
String SQL = "update Student set name = ? where id = ?";
jdbcTemplateObject.update( SQL, new Object[]{"Zara", 10} );

从表中删除一行:

1
2
String SQL = "delete Student where id = ?";
jdbcTemplateObject.update( SQL, new Object[]{20} );

执行 DDL 语句

你可以使用 jdbcTemplate 中的 execute(..) 方法来执行任何 SQL 语句或 DDL 语句。下面是一个使用 CREATE 语句创建一个表的示例:

1
2
3
4
5
6
String SQL = "CREATE TABLE Student( " +
"ID INT NOT NULL AUTO_INCREMENT, " +
"NAME VARCHAR(20) NOT NULL, " +
"AGE INT NOT NULL, " +
"PRIMARY KEY (ID));"
jdbcTemplateObject.execute( SQL );

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class StudentJDBCTemplate implements StudentDAO {
private DataSource dataSource;
private JdbcTemplate jdbcTemplateObject;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public void create(String name, Integer age) {
String SQL = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL, name, age);
System.out.println("Created Record Name = " + name + " Age = " + age);
return;
}
public Student getStudent(Integer id) {
String SQL = "select * from Student where id = ?";
Student student = jdbcTemplateObject.queryForObject(SQL,
new Object[]{id}, new StudentMapper());
return student;
}
public List<Student> listStudents() {
String SQL = "select * from Student";
List <Student> students = jdbcTemplateObject.query(SQL,
new StudentMapper());
return students;
}
public void delete(Integer id){
String SQL = "delete from Student where id = ?";
jdbcTemplateObject.update(SQL, id);
System.out.println("Deleted Record with ID = " + id );
return;
}
public void update(Integer id, Integer age){
String SQL = "update Student set age = ? where id = ?";
jdbcTemplateObject.update(SQL, age, id);
System.out.println("Updated Record with ID = " + id );
return;
}
}

public class StudentMapper implements RowMapper<Student> {
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
return student;
}
}

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("BeansJdbc.xml");
StudentJDBCTemplate studentJDBCTemplate = (StudentJDBCTemplate)context.getBean("studentJDBCTemplate");
System.out.println("------Records Creation--------" );
studentJDBCTemplate.create("Zara", 11);
studentJDBCTemplate.create("Nuha", 2);
studentJDBCTemplate.create("Ayan", 15);
System.out.println("------Listing Multiple Records--------" );
List<Student> students = studentJDBCTemplate.listStudents();
for (Student record : students) {
System.out.print("ID : " + record.getId() );
System.out.print(", Name : " + record.getName() );
System.out.println(", Age : " + record.getAge());
}
System.out.println("----Updating Record with ID = 2 -----" );
studentJDBCTemplate.update(2, 20);
System.out.println("----Listing Record with ID = 2 -----" );
Student student = studentJDBCTemplate.getStudent(2);
System.out.print("ID : " + student.getId() );
System.out.print(", Name : " + student.getName() );
System.out.println(", Age : " + student.getAge());
}
}

<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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>

<!-- Definition for studentJDBCTemplate bean -->
<bean id="studentJDBCTemplate" class="com.zsm.test.jdbc.StudentJDBCTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>

SQL 的存储过程

SimpleJdbcCall 类可以被用于调用一个包含 IN 和 OUT 参数的存储过程。你可以在处理任何一个 RDBMS 时使用这个方法,就像 Apache Derby, DB2, MySQL, Microsoft SQL Server, Oracle,和 Sybase。

为了了解这个方法,我们使用 Student 表,它可以在 MySQL TEST 数据库中使用下面的 DDL 进行创建:

1
2
3
4
5
6
CREATE TABLE Student(
ID INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
AGE INT NOT NULL,
PRIMARY KEY (ID)
);

下一步,考虑接下来的 MySQL 存储过程,该过程使用 学生 Id 并且使用 OUT 参数返回相应的学生的姓名和年龄。所以让我们在你的 TEST 数据库中使用 MySQL 命令提示符创建这个存储过程:

1
2
3
4
5
6
7
8
9
10
11
12
DELIMITER $$
DROP PROCEDURE IF EXISTS `TEST`.`getRecord` $$
CREATE PROCEDURE `TEST`.`getRecord` (
IN in_id INTEGER,
OUT out_name VARCHAR(20),
OUT out_age INTEGER)
BEGIN
SELECT name, age
INTO out_name, out_age
FROM Student where id = in_id;
END $$
DELIMITER ;

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class StudentJDBCTemplate implements StudentDAO {
private DataSource dataSource;
private SimpleJdbcCall jdbcCall;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcCall = new SimpleJdbcCall(dataSource).withProcedureName("getRecord");
}
public void create(String name, Integer age) {
JdbcTemplate jdbcTemplateObject = new JdbcTemplate(dataSource);
String SQL = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL, name, age);
System.out.println("Created Record Name = " + name + " Age = " + age);
}
public Student1 getStudent(Integer id) {
SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id);
Map<String, Object> out = jdbcCall.execute(in);
Student1 student = new Student1();
student.setId(id);
student.setName((String) out.get("out_name"));
student.setAge((Integer) out.get("out_age"));
return student;
}
}

事务管理

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

  • 原子性(Atomicity):事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。

  • 一致性(Consistency):这表示数据库的引用完整性的一致性,表中唯一的主键等。

  • 隔离性(Isolation):可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。

  • 持久性(Durability):一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。

一个真正的 RDBMS 数据库系统将为每个事务保证所有的四个属性。使用 SQL 发布到数据库中的事务的简单视图如下:

  • 使用 begin transaction 命令开始事务。

  • 使用 SQL 查询语句执行各种删除、更新或插入操作。

如果所有的操作都成功,则执行提交操作,否则回滚所有操作。

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

编程式 vs. 声明式

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

  • 编程式事务管理 :这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。

  • 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注释或 XML 配置来管理事务。

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

事务抽象

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

Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义,如下所示:

1
2
3
4
5
6
7
8
public interface PlatformTransactionManager {
//根据指定的传播行为,该方法返回当前活动事务或创建一个新的事务。
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
//该方法提交给定的事务和关于它的状态。
void commit(TransactionStatus status) throws TransactionException;
//该方法执行一个给定事务的回滚。
void rollback(TransactionStatus status) throws TransactionException;
}

TransactionDefinition 是在 Spring 中事务支持的核心接口,它的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
public interface TransactionDefinition {
//该方法返回传播行为。Spring 提供了与 EJB CMT 类似的所有的事务传播选项。
int getPropagationBehavior();
//该方法返回该事务独立于其他事务的工作的程度。
int getIsolationLevel();
//该方法返回该事务的名称。
String getName();
//该方法返回以秒为单位的时间间隔,事务必须在该时间间隔内完成。
int getTimeout();
//该方法返回该事务是否是只读的。
boolean isReadOnly();
}

TransactionStatus 接口为事务代码提供了一个简单的方法来控制事务的执行和查询事务状态。

1
2
3
4
5
6
7
8
9
10
11
12
public interface TransactionStatus extends SavepointManager {
//在当前事务时新的情况下,该方法返回 true。
boolean isNewTransaction();
//该方法返回该事务内部是否有一个保存点,也就是说,基于一个保存点已经创建了嵌套事务。
boolean hasSavepoint();
//该方法设置该事务为 rollback-only 标记。
void setRollbackOnly();
//该方法返回该事务是否已标记为 rollback-only。
boolean isRollbackOnly();
//该方法返回该事务是否完成,也就是说,它是否已经提交或回滚。
boolean isCompleted();
}

编程式事务管理

我们可以直接使用 PlatformTransactionManager 来实现编程式方法从而实现事务。要开始一个新事务,你需要有一个带有适当的 transaction 属性的 TransactionDefinition 的实例。当 TransactionDefinition 创建后,你可以通过调用 getTransaction() 方法来开始你的事务,该方法会返回 TransactionStatus 的一个实例。 TransactionStatus 对象帮助追踪当前的事务状态,并且最终,如果一切运行顺利,你可以使用 PlatformTransactionManager 的 commit() 方法来提交这个事务,否则的话,你可以使用 rollback() 方法来回滚整个操作。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public void create(String name, Integer age, Integer marks, Integer year){
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);//开始事务
try {
String SQL1 = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL1, name, age);
// Get the latest student id to be used in Marks table
String SQL2 = "select max(id) from Student";
int sid = jdbcTemplateObject.queryForObject( SQL2, Integer.class );
String SQL3 = "insert into Marks(sid, marks, year) " +
"values (?, ?, ?)";
jdbcTemplateObject.update( SQL3, sid, marks, year);
System.out.println("Created Name = " + name + ", Age = " + age);
transactionManager.commit(status);//提交事务
} catch (DataAccessException e) {
System.out.println("Error in creating record, rolling back");
transactionManager.rollback(status);
throw e;
}
return;
}

<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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<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 studentJDBCTemplate bean -->
<bean id="studentJDBCTemplate" class="com.zsm.test.transaction.StudentJDBCTemplate">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
</bean>

</beans>

声明式事务管理

声明式事务管理方法允许你在配置的帮助下而不是源代码硬编程来管理事务。这意味着你可以将事务管理从事务代码中隔离出来。 bean 配置会指定事务型方法。下面是与声明式事务相关的步骤:

  • 我们使用标签,它创建一个事务处理的建议,同时,我们定义一个匹配所有方法的切入点,我们希望这些方法是事务型的并且会引用事务型的建议。

  • 如果在事务型配置中包含了一个方法的名称,那么创建的建议在调用方法之前就会在事务中开始进行。

  • 目标方法会在 try / catch 块中执行。

  • 如果方法正常结束,AOP 建议会成功的提交事务,否则它执行回滚操作。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- Initialization for data source -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<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.zsm.test.transac.StudentJDBCTemplate.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 studentJDBCTemplate bean -->
<bean id="studentJDBCTemplate" class="com.zsm.test.transac.StudentJDBCTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

</beans>

public class StudentJDBCTemplate implements StudentDAO{
private JdbcTemplate jdbcTemplateObject;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public void create(String name, Integer age, Integer marks, Integer year){
try {
String SQL1 = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL1, name, age);
// Get the latest student id to be used in Marks table
String SQL2 = "select max(id) from Student";
int sid = jdbcTemplateObject.queryForObject( SQL2, Integer.class );
String SQL3 = "insert into Marks(sid, marks, year) " + "values (?, ?, ?)";
jdbcTemplateObject.update( SQL3, sid, marks, year);
System.out.println("Created Name = " + name + ", Age = " + age);
// to simulate the exception.
throw new RuntimeException("simulate Error condition") ;
} catch (DataAccessException e) {
System.out.println("Error in creating record, rolling back");
throw e;
}
}
public List<StudentMarks> listStudents() {
String SQL = "select * from Student, Marks where Student.id=Marks.sid";
List <StudentMarks> studentMarks=jdbcTemplateObject.query(SQL,
new StudentMarksMapper());
return studentMarks;
}
}

//会输出如下所示的异常。在这种情况下,事务会回滚并且在数据库表中不会创建任何记录。
Created Name = Zara, Age = 11
Exception in thread "main" java.lang.RuntimeException: simulate Error condition

Spring Web MVC 框架

Spring web MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。

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

下面是对应于 DispatcherServlet 传入 HTTP 请求的事件序列:

  • 收到一个 HTTP 请求后,DispatcherServlet 根据 HandlerMapping 来选择并且调用适当的控制器。

  • 控制器接受请求,并基于使用的 GET 或 POST 方法来调用适当的 service 方法。Service 方法将设置基于定义的业务逻辑的模型数据,并返回视图名称到 DispatcherServlet 中。

  • DispatcherServlet 会从 ViewResolver 获取帮助,为请求检取定义视图。

  • 一旦确定视图,DispatcherServlet 将把模型数据传递给视图,最后呈现在浏览器中。

上面所提到的所有组件,即 HandlerMapping、Controller 和 ViewResolver 是 WebApplicationContext 的一部分,而 WebApplicationContext 是带有一些对 web 应用程序必要的额外特性的 ApplicationContext 的扩展。

需求的配置

你需要映射你想让 DispatcherServlet 处理的请求,通过使用在 web.xml 文件中的一个 URL 映射。

web.xml 文件将被保留在你的应用程序的 WebContent/WEB-INF 目录下。好的,在初始化 HelloWeb DispatcherServlet 时,该框架将尝试加载位于该应用程序的 WebContent/WEB-INF 目录中文件名为 [servlet-name]-servlet.xml 的应用程序内容。

如果你不想使用默认文件名 [servlet-name]-servlet.xml 和默认位置 WebContent/WEB-INF,你可以通过在 web.xml 文件中添加 servlet 监听器 ContextLoaderListener 自定义该文件的名称和位置,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<web-app...>
....
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/HelloWeb-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>

现在,检查 HelloWeb-servlet.xml 文件的请求配置,该文件位于 web 应用程序的 WebContent/WEB-INF 目录下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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.tutorialspoint" />

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

</beans>

以下是关于 HelloWeb-servlet.xml 文件的一些要点:

  • [servlet-name]-servlet.xml 文件将用于创建 bean 定义,重新定义在全局范围内具有相同名称的任何已定义的 bean。

  • context:component-scan标签将用于激活 Spring MVC 注释扫描功能,该功能允许使用注释,如 @Controller 和 @RequestMapping 等等。

  • InternalResourceViewResolver 将使用定义的规则来解决视图名称。按照上述定义的规则,一个名称为 hello 的逻辑视图将发送给位于 /WEB-INF/jsp/hello.jsp 中实现的视图。

定义控制器

DispatcherServlet 发送请求到控制器中执行特定的功能。@Controller 注释表明一个特定类是一个控制器的作用。@RequestMapping 注释用于映射 URL 到整个类或一个特定的处理方法。

1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/hello")
public class HelloController{
@RequestMapping(method = RequestMethod.GET)
public String printHello(ModelMap model) {
model.addAttribute("message", "Hello Spring MVC Framework!");
return "hello";
}
}

@Controller 注释定义该类作为一个 Spring MVC 控制器。在这里,第一次使用的 @RequestMapping 表明在该控制器中处理的所有方法都是相对于 /hello 路径的。下一个注释 @RequestMapping(method = RequestMethod.GET) 用于声明 printHello() 方法作为控制器的默认 service 方法来处理 HTTP GET 请求。你可以在相同的 URL 中定义其他方法来处理任何 POST 请求。