lrhya

步履不停。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
计算机类免费电子书详见:https://github.com/lrhaoya/CS-Books

Spring简介

Spring是什么

Spring是一种多层的J2EE应用程序框架,其核心就是提供一种新的机制管理业务对象及其依赖关系。它是一种容器框架,用于创建bean,维护bean之间的关系。

Spring的特点

(1)轻量级:轻量级是针对重量级容器(EJB)来说的,Spring的核心包就不到1M大小,而使用Spring的核心包所需的资源也很小,所以可以在小型设备中使用。

(2)非入侵式:Spring目标是一个非入侵式的服务框架。原因是所有框架都是提供大量的功能供用户去使用,从而简化开发时间和成本,但由于大量的使用了框架的API,使应用程序和框架发生了大量的依赖性,无法从框架中独立出来,更加无法使程序组件在其他程序中使用。

(3)容器:Spring提供了容器功能,容器可以管理对象的生命周期、对象与对象间的关系、我们可以通过编写XML来设置对象关系和初始值,这样容器在启动之后,所有的对象都直接可以使用,不用编写任何编码来产生对象。Spring有两种不同的容器:Bean工厂以及应用上下文。

Spring的工作原理

Spring内部最核心的就是IOC了,动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射,反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行时,跟xml Spring的配置文件来动态的创建对象和调用对象里的方法的 。

Spring还有一个核心就是AOP面向切面编程,可以为某一类对象进行监督和控制(也就是在调用这类对象的具体方法的前后去调用你指定的模块)从而达到对一个模块扩充的功能。这些都是通过配置类达到的。

Spring目地就是让对象与对象(模块与模块)之间的关系没有通过代码来关联,都是通过配置类说明管理的

Spring的体系结构

核心容器

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

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

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

  • context模块建立在由corebeans 模块的基础上建立起来的,它以一种类似于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容器检索对象,还支持列表的投影、选择以及聚合等。

Maven构建的Spring项目时所需要的依赖

 <!-- Spring依赖 -->
   <!-- 1.Spring核心依赖 -->
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-core</artifactId>
       <version>4.3.7.RELEASE</version>
   </dependency>
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-beans</artifactId>
       <version>4.3.7.RELEASE</version>
   </dependency>
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
       <version>4.3.7.RELEASE</version>
   </dependency>
<!-- 2.Spring dao依赖 -->
<!-- spring-jdbc包括了一些如jdbcTemplate的工具类 -->
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</artifactId>
       <version>4.3.7.RELEASE</version>
   </dependency>
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-tx</artifactId>
       <version>4.3.7.RELEASE</version>
   </dependency>
   <!-- 3.Spring web依赖 -->
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-web</artifactId>
       <version>4.3.7.RELEASE</version>
   </dependency>
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-webmvc</artifactId>
       <version>4.3.7.RELEASE</version>
   </dependency>
   <!-- 4.Spring test依赖:方便做单元测试和集成测试 -->
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-test</artifactId>
       <version>4.3.7.RELEASE</version>
  </dependency>

一共四个方面:

1)spring核心依赖:spring-core、spring-beans、spring-context

2)spring dao依赖(提供JDBCTemplate):spring-jdbc、spring-tx

3)spring web依赖:spring-web、spring-webmvc

4)spring test依赖:spring-test

 

 

Spring Bean

Spring Bean 的定义

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

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

  • 如何创建一个 bean

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

  • bean 的依赖关系

上述所有的配置元数据转换成一组构成每个 bean 定义的下列属性。

Bean 与 Spring 容器的关系:

Spring中 配置元数据的方式

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

Spring Bean的作用域

当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton

Spring 框架支持以下五个作用域,分别为singleton、prototype、request、session和global session,5种作用域说明如下所示,

注意,如果你使用 web-aware ApplicationContext 时,其中三个是可用的。

作用域描述
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 时,如果没有指定作用域配置项,则 Bean 的作用域被默认为 singleton。

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

也就是说,当将一个bean定义设置为singleton作用域的时候,Spring IoC容器只会创建该bean定义的唯一实例。

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

prototype 作用域:

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

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

Spring Bean 的生命周期

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

Spring IOC 容器可以管理 Bean 的生命周期, Spring 允许在 Bean 生命周期的特定点执行定制的任务

在 Bean 的声明里设置init-method和destroy-method属性,为Bean指定初始化和销毁方法.

init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法

//这里是 HelloWorld.java 的文件的内容
package com.tutorialspoint;
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.");
  }
}

/*
下面是 MainApp.java 文件的内容。在这里,你需要注册一个在 AbstractApplicationContext 类中声明的关闭 hook 的 registerShutdownHook() 方法。它将确保正常关闭,并且调用相关的 destroy 方法。
*/
package com.tutorialspoint;
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();
     context.registerShutdownHook();
  }
}

//下面是 init 和 destroy 方法必需的配置文件 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.tutorialspoint.HelloWorld"
      init-method="init" destroy-method="destroy">
      <property name="message" value="Hello World!"/>
  </bean>

</beans>

//输出结果
Bean is going through init.
Your Message : Hello World!
Bean will destroy now.

Spring Bean 后置处理器

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

BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。

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

//这里是 HelloWorld.java 的文件的内容
package com.tutorialspoint;
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.");
  }
}
/*
这是实现 BeanPostProcessor 的非常简单的例子,它在任何 bean 的初始化的之前和之后输入该 bean 的名称。你可以在初始化 bean 的之前和之后实现更复杂的逻辑,因为你有两个访问内置 bean 对象的后置处理程序的方法。
这里是 InitHelloWorld.java 文件的内容
*/
package com.tutorialspoint;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
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
  }
}
/*
下面是 MainApp.java 文件的内容。在这里,你需要注册一个在 AbstractApplicationContext 类中声明的关闭 hook 的 registerShutdownHook() 方法。它将确保正常关闭,并且调用相关的 destroy 方法。
*/
package com.tutorialspoint;
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();
     context.registerShutdownHook();
  }
}
//下面是 init 和 destroy 方法需要的配置文件 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">

<!-- init-method="init" destroy-method="destroy"在此处非必需,为方便展示所以添加-->
  <bean id="helloWorld" class="com.tutorialspoint.HelloWorld"
      init-method="init" destroy-method="destroy">
      <property name="message" value="Hello World!"/>
  </bean>

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

</beans>
//输出结果  
BeforeInitialization : helloWorld  // 调用初始化方法之前调用特定方法
Bean is going through init.
AfterInitialization : helloWorld  //调用初始化方法之后调用特定方法
Your Message : Hello World!
Bean will destroy now.

Spring Bean 之间的关系

bean之间的关系:继承;依赖

继承 Bean 配置

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

Spring 允许继承 bean 的配置, 被继承的 bean 称为父 bean. 继承这个父 Bean 的 Bean 称为子 Bean

Bean 从父 Bean中继承配置, 包括 Bean 的属性配置

•子 Bean 也可以覆盖从父 Bean 继承过来的配置

•父 Bean 可以作为配置模板, 也可以作为 Bean 实例. 若只想把父 Bean 作为模板, 可以设置 <bean> 的abstract 属性为 true, 这样 Spring 将不会实例化这个 Bean

并不是 <bean> 元素里的所有属性都会被继承. 比如: autowire, abstract 等.

•也可以忽略父 Bean class 属性, 让子 Bean 指定自己的类, 而共享相同的属性配置. 但此时 abstract 必须设为 true

/*
下面是配置文件 Beans.xml,在该配置文件中我们定义有两个属性 message1 和 message2 的 “helloWorld” bean。然后,使用 parent 属性把 “helloIndia” bean 定义为 “helloWorld” bean 的孩子。这个子 bean 继承 message2 的属性,重写 message1 的属性,并且引入一个属性 message3。
*/
<?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.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>

</beans>
//这里是 HelloWorld.java 文件的内容:
package com.tutorialspoint;
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);
  }
}
//这里是 HelloIndia.java 文件的内容:
package com.tutorialspoint;

public class HelloIndia {
  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("India Message1 : " + message1);
  }

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

  public void getMessage3(){
     System.out.println("India Message3 : " + message3);
  }
}
//下面是 MainApp.java 文件的内容:
package com.tutorialspoint;

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();

     HelloIndia objB = (HelloIndia) context.getBean("helloIndia");
     objB.getMessage1();
     objB.getMessage2();
     objB.getMessage3();
  }
}
/*输出结果
我们创建 “helloIndia” bean 的同时并没有传递 message2,但是由于 Bean 定义的继承,所以它传递了 message2。*/
World Message1 : Hello World!
World Message2 : Hello Second World!
India Message1 : Hello India!
India Message2 : Hello Second World!
India Message3 : Namaste India!

依赖 Bean 配置

Spring 允许用户通过 depends-on 属性设定 Bean 前置依赖的Bean,前置依赖的 Bean 会在本 Bean 实例化之前创建好

如果前置依赖于多个 Bean,则可以通过逗号,空格或的方式配置 Bean 的名称

   <bean id="helloWorld" class="com.tutorialspoint.HelloWorld" depends-on="before"/>

Spring Beans 自动装配

可以使用<bean>元素来声明 bean 和通过使用 XML 配置文件中的<constructor-arg><property>元素来注入 。

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

Spring Beans 自动装配的三种方式:

Spring 自动装配 byName(根据名称自动装配)

必须将目标 Bean 的名称和属性名设置的完全相同

Spring 自动装配 byType(根据类型自动装配)

若 IOC 容器中有多个与目标Bean类型一致的Bean.在这种情况下,Spring 将无法判定哪个 Bean 最合适该属性,所以不能执行自动装配

Spring 自动装配 constructor(通过构造器自动装配)

当 Bean 中存在多个构造器时,此种自动装配方式将会很复杂,不推使用

Spring 自动装配 byName(根据名称自动装配)

这种模式由属性名称指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 auto-wire 属性设置为 byName。然后,它尝试将它的属性与配置文件中定义为相同名称的 beans 进行匹配和连接。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。

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

   //set方法 
 public void setSpellChecker( SpellChecker spellChecker ) {
     this.spellChecker = spellChecker;
  }
   <!-- Definition for textEditor bean -->
  <bean id="textEditor" class="com.tutorialspoint.TextEditor"
     autowire="byName">
     <property name="name" value="Generic Text Editor" />
  </bean>
  <!-- Definition for spellChecker bean -->  
//id="spellChecker",根据名称进行自动装配
  <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
  </bean>

Spring 自动装配 byType(根据类型自动装配)

这种模式由属性类型指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 byType。然后,如果它的 type 恰好与配置文件中 beans 名称中的一个相匹配,它将尝试匹配和连接它的属性。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。

例如,在配置文件中,如果一个 bean 定义设置为自动装配 byType,并且它包含 SpellChecker 类型的 spellChecker 属性,那么 Spring 就会查找定义名为 SpellChecker 的 bean,并且用它来设置这个属性。你仍然可以使用 <property> 标签连接其余属性。

  <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor" 
      autowire="byType">
      <property name="name" value="Generic Text Editor" />
   </bean>
  //id="SpellChecker" 根据类型进行自动装配
   <!-- Definition for spellChecker bean -->
   <bean id="SpellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

Spring 自动装配 constructor(通过构造器自动装配)

这种模式与 byType 非常相似,但它应用于构造器参数。Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 constructor。然后,它尝试把它的构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。如果找到匹配项,它会注入这些 bean,否则,它会抛出异常

例如,在配置文件中,如果一个 bean 定义设置为通过构造函数自动装配,而且它有一个带有 SpellChecker 类型的参数之一的构造函数,那么 Spring 就会查找定义名为 SpellChecker 的 bean,并用它来设置构造函数的参数。你仍然可以使用 <constructor-arg> 标签连接其余属性

 //构造方法  
  public TextEditor( SpellChecker spellChecker, String name ) {
      this.spellChecker = spellChecker;
      this.name = name;
   }
   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor" 
      autowire="constructor">
      <constructor-arg value="Generic Text Editor"/>
   </bean>
// id="SpellChecker" 根据类型进行自动装配,但它应用于构造器参数
   <!-- Definition for spellChecker bean -->
   <bean id="SpellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

当一个Bean既使用自动装配依赖,又使用ref显式指定依赖时,则显式指定的依赖覆盖自动装配依赖;对于大型的应用,不鼓励使用自动装配。虽然使用自动装配可减少配置文件的工作量,但大大将死了依赖关系的清晰性和透明性。依赖关系的装配依赖于源文件的属性名和属性类型,导致Bean与Bean之间的耦合降低到代码层次,不利于高层次解耦。

<!--通过设置可以将Bean排除在自动装配之外-->
<bean id="" autowire-candidate="false"/>

<!--除此之外,还可以在beans元素中指定,支持模式字符串,如下所有以abc结尾的Bean都被排除在自动装配之外-->
<beans default-autowire-candidates="*abc"/>

Spring Bean 基于注解的配置

从 Spring 2.5 开始就可以使用注解来配置依赖注入。而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身。

在 XML 注入之前进行注解注入,因此后者的配置将通过两种方式的属性连线被前者重写。

注解连线在默认情况下在 Spring 容器中不打开。因此,在可以使用基于注解的连线之前,我们将需要在我们的 Spring 配置文件中启用它。

  <!--启用Spring 注解配置-->
  <context:annotation-config/>

一旦 被配置后,你就可以开始注解你的代码,表明 Spring 应该自动连接值到属性,方法和构造函数。

常用注解如下:

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

使用@Autowired 配置依赖

@Autowired 注解自动装配具有兼容类型的单个 Bean属性

  • 构造器, 普通字段(即使是非 public), 一切具有参数的方法都可以应用@Authwired 注解

  • 默认情况下, 所有使用 @Authwired 注解的属性都需要被设置. 当 Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置 @Authwired 注解的 required 属性为 false

  • 默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作. 此时可以在 @Qualifier 注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter 已指定注入 Bean 的名称

  • @Authwired 注解也可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配.

  • @Authwired 注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean.

  • @Authwired 注解用在 java.util.Map 上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值

@Inject 和 @Autowired 注解一样也是按类型匹配注入的 Bean, 但没有 reqired 属性

建议使用 @Autowired 注解

使用@Resource配置依赖

@Resource位于javax.annotation包下,是来自JavaEE规范的一个Annotation,Spring直接借鉴了该Annotation,通过使用该Annotation为目标Bean指定协作者Bean。使用@Resource<property.../>元素的ref属性有相同的效果。 @Resource不仅可以修饰setter方法,也可以直接修饰实例变量,如果使用@Resource修饰实例变量将会更加简单,此时Spring将会直接使用JavaEE规范的Field注入,此时连setter方法都可以不要。

@Resource 注解要求提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为Bean 的名称

使用@PostConstruct和@PreDestroy定制生命周期行为

@PostConstruct@PreDestroy同样位于javax.annotation包下,也是来自JavaEE规范的两个Annotation,Spring直接借鉴了它们,用于定制Spring容器中Bean的生命周期行为。它们都用于修饰方法,无须任何属性。其中前者修饰的方法时Bean的初始化方法;而后者修饰的方法时Bean销毁之前的方法。

Spring4.0增强的自动装配和精确装配

Spring提供了@Autowired注解来指定自动装配,@Autowired可以修饰setter方法、普通方法、实例变量和构造器等。当使用@Autowired标注setter方法时,默认采用byType自动装配策略。在这种策略下,符合自动装配类型的候选Bean实例常常有多个,这个时候就可能引起异常,为了实现精确的自动装配,Spring提供了@Qualifier注解,通过使用@Qualifier,允许根据Bean的id来执行自动装配。

在 classpath中扫描组件

组件扫描(componentscanning): Spring 能够从classpath下自动扫描,侦测和实例化具有特定注解的组件

Spring提供如下几个Annotation来标注Spring Bean:

  • @Component: 标注一个普通的Spring Bean类,标识了一个受 Spring管理的组件

  • @Controller: 标注一个控制器组件类

  • @Service: 标注一个业务逻辑组件类

  • @Repository: 标注一个DAO组件类

对于扫描到的组件,Spring 有默认的命名策略:使用非限定类名,第一个字母小写. 也可以在注解中通过value 属性值标识组件的名称

在Spring配置文件中做如下配置,指定自动扫描的包:

<context:component-scan base-package="edu.shu.spring.domain"/>

•当在组件类上使用了特定的注解之后, 还需要在 Spring 的配置文件中声明 <context:component-scan> :

base-package 属性指定一个需要扫描的基类包Spring 容器将会扫描这个基类包里及其子包中的所有类

当需要扫描多个包时, 可以使用逗号分隔

如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern 属性过滤特定的类,示例:

    <context:component-scan base-package="com.lrh.AuthorityControl.service.*" resource-pattern="autowire/*.class"/>

context:component-scan元素还会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@Autowired和@Resource 、@Inject注解的属性

context:component-scan下可以拥有若干个 context:include-filtercontext:exclude-filter 子节点

context:include-filter 子节点表示要包含的目标类

context:exclude-filter子节点表示要排除在外的目标类

context:include-filtercontext:exclude-filter子节点支持多种类型的过滤表达式

 

Spring IoC (控制反转)

IOC(控制反转)是什么

控制反转(IoC),及上层控制下层,而不是下层控制着上层。

我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的控制

可以采用的方式有: 构造函数传递,Setter传递和接口传递

详见:Spring IoC有什么好处

IOC的优点

DI和IOC其实是一个思想,她们的的好处是:如果依赖的类修改了,比如修改了构造函数,如果没有依赖注入,则需要修改依赖对象调用着,如果依赖注入则不需要。

spring IOC的好处是,对象的构建如果依赖非常多的对象,且层次很深,外层在构造对象时很麻烦且不一定知道如何构建这么多层次的对象。 IOC 帮我们管理对象的创建,只需要在配置文件里指定如何构建,每一个对象的配置文件都在类编写的时候指定了,所以最外层对象不需要关心深层次对象如何创建的,前人都写好了。

Spring IoC 容器

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

通过阅读配置元数据提供的指令,容器知道对哪些对象进行实例化,配置和组装。配置元数据可以通过 XML,Java 注释或 Java 代码来表示。下图是 Spring 如何工作的高级视图。 Spring IoC 容器利用 Java 的 POJO 类和配置元数据来生成完全配置和可执行的系统或应用程序。

IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。

Spring提供了两种不同类型的容器:BeanFactory 容器、ApplicationContext 容器

两者的区别与联系:

  • BeanFactory 是 Spring 框架最核心的接口,提供了高级 IOC 的配置机制。

  • ApplicationContext:提供了更多的高级特性.是BeanFactory的子接口

  • ApplicationContext 建立在 BeanFacotry 基础之上,提供了更多面向应用的功能,如国际化,属性编辑器,事件等等。

  • beanFactory 是 spring 框架的基础设施,是面向 spring 本身。ApplicationContext 是面向使用 Spring 框架的开发者,几乎所有场合都会用到 ApplicationContext。

  • 我们一般称 BeanFactory 为 IoC 容器,而称 ApplicationContext 为应用上下文。

 

spring中是如何使用IOC的

对象的创建不再是代码本身而是交给容器管理,由容器创建对象并管理他们之间的依赖关系以及生命周期。

IOC原理:

public static void main(String[] args) {
		ApplicationContext context = new FileSystemXmlApplicationContext(
				"applicationContext.xml");
		Animal animal = (Animal) context.getBean("animal");
		animal.say();
}

通过加载一个 XML 文件,我们就获得了一个对象

原理:

Spring 通过扫描 XML 将基本信息保存在一个 Map 里面,然后通过反射实例化一个类,再调用实例化对象的 set 方法将 Map 里面的值注入。

 

Spring DI(依赖注入)

DI(依赖注入)是什么

依赖注入

每个基于应用程序的 java 都有几个对象,这些对象一起工作来呈现出终端用户所看到的工作的应用程序。当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能独立于其他 Java 类来增加这些类重用的可能性,并且在做单元测试时,测试独立于其他类的独立性。依赖注入(或有时称为布线)有助于把这些类粘合在一起,同时保持他们独立。

Spring 最认同的技术是控制反转的依赖注入(DI)模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。

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

Spring中依赖注入的方式

属性注入、构造器注入、工厂方法注入(很少使用,不推荐)

Spring 基于构造函数的依赖注入

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

通过构造方法注入Bean 的属性值或依赖的对象,它保证了Bean实例在实例化后就可以使用。

构造器注入在 <constructor-arg>元素里声明属性,<constructor-arg>中没有 name属性。

构造方法注入Bean的属性值

按索引匹配入参

<beans>
   <bean id="exampleBean" class="examples.ExampleBean">
      <constructor-arg index="0" value="2001"/>
      <constructor-arg index="1" value="Zara"/>
   </bean>
</beans>

按类型匹配入参

<beans>
   <bean id="exampleBean" class="examples.ExampleBean">
      <constructor-arg type="int" value="2001"/>
      <constructor-arg type="java.lang.String" value="Zara"/>
   </bean>
</beans>

索引和类型同时匹配入参

<beans>
   <bean id="exampleBean" class="examples.ExampleBean">
      <constructor-arg index="0"  value="2001"/>
      <constructor-arg index="1" type="java.lang.String" value="Zara"/>
   </bean>
</beans>

构造方法注入Bean依赖的对象

package x.y;
public class Foo {
    //构造方法
   public Foo(Bar bar, Baz baz) {
      // ...
   }
}
//配置文件
<beans>
   <bean id="foo" class="x.y.Foo">
      <constructor-arg ref="bar"/>
      <constructor-arg ref="baz"/>
   </bean>
   <bean id="bar" class="x.y.Bar"/>
   <bean id="baz" class="x.y.Baz"/>
</beans>

Spring 基于属性的依赖注入

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

bean的全类名,通过反射的方式在IOC容器中创建Bean,所以要求Bean必须有无参构造器

  • 属性注入即通过 setter 方法注入Bean 的属性值或依赖的对象

  • 属性注入使用 <property> 元素, 使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值

  • 属性注入是实际应用中最常用的注入方式

<?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">

    <!--class:bean的全类名,通过反射的方式在IOC容器中创建Bean,所以要求Bean必须有无参构造器-->
    <!--id 可以指定多个名字,名字之间可用逗号、分号、或空格分隔,id在 IOC 容器中必须是唯一的-->
   <bean id="john-classic" class="com.example.Person">
       <!--为属性赋值-->
      <property name="name" value="John Doe"/>
       <!--ref 部分表明这不是一个直接的值,而是对另一个 bean 的引用。-->
      <property name="spouse" ref="jane"/> 
   </bean>

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

</beans>

 

Spring AOP(面向切面编程)

AOP (面向切面编程)是什么

AOP(Aspect Orient Programming)也就是面向切面编程,作为面向对象编程的一种补充,已经成为一种比较成熟的编程方式。其实AOP问世的时间并不太长,AOP和OOP互为补充,面向切面编程将程序运行过程分解成各个切面。

AOP专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在JavaEE应用中,常常通过AOP来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP已经成为一种非常常用的解决方案。

如下图所示分离出切面

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP相关术语

  • 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象

  • 通知(Advice): 切面必须要完成的工作

  • 目标(Target): 被通知的对象

  • 代理(Proxy): 向目标对象应用通知之后创建的对象

  • 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。

  • 切点(pointcut):实际增强的方法,也就是新增的方法

    每个类都拥有多个连接点AOP,通过切点定位到特定的连接点

    类比:连接点相当于数据库中的记录,切点相当于查询条件

Spring 基于AspectJ的配置声明切面

  • AspectJ:Java 社区里最完整最流行的 AOP 框架

  • 在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP

在 Spring中启用AspectJ注解支持

  • 要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar

     <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
         <version>1.9.4</version>
     </dependency>
  • 将 aop Schema 添加到 <beans> 根元素中.

  <beans  xmlns:aop="http://www.springframework.org/schema/aop" ></beans>
  • 要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy >

  • 当 SpringIOC 容器侦测到 Bean 配置文件中的< aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理

用 AspectJ注解声明切面

  • 要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理

  • 在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类

  • 通知是标注有某种注解的简单的 Java 方法

  • AspectJ 支持 5 种类型的通知注解:

    • @Before: 前置通知, 在方法执行之前执行

    • @After: 后置通知, 在方法执行之后执行

    • @AfterRunning: 返回通知, 在方法返回结果之后执行

    • @AfterThrowing: 异常通知, 在方法抛出异常之后

    • @Around: 环绕通知, 围绕着方法执行

利用方法签名编写AspectJ切入点表达式

最典型的切入点表达式时根据方法的签名来匹配各种方法:

  • execution * com.atguigu.spring.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.

  • execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的所有公有方法.

  • execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 类型数值的方法

  • execution public double ArithmeticCalculator.*(double, ..): 匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数

  • execution public double ArithmeticCalculator.*(double, double): 匹配参数类型为 double, double 类型的方法.

/*
 标识这个方法是个前置通知,  切点表达式表示执行任意类的任意方法. 第一个 * 代表匹配任意修饰符及任意返回值,  第二个 * 代表任意类的对象,第三个 * 代表任意方法, 参数列表中的 ..  匹配任意数量的参数
*/
@Before("execution(* *.*(..))")
 private void logBefore() {
 }

在 AspectJ中,切入点表达式可以通过操作符&&,||, ! 结合起来.

重用切入点定义

  • 在编写 AspectJ 切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现.

  • 在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的.

  • 切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.

  • 其他通知可以通过方法名称引入该切入点.

    @Pointcut("execution(* com.lrhya.AOP.Demo01.*.*(..))")
    //切入点表达式:execution(* com.lrhya.AOP.Demo01.*.*(..))
    private void selectAll() {
    }
    @Before("selectAll()")
    public void beforeAdvice() {
        System.out.println("Going to setup student profile.");
    }
    @AfterReturning(pointcut = "selectAll()", returning = "retVal")
    public void afterReturningAdvice(Object retVal) {
        System.out.println("Returning:" + retVal.toString());
    }

指定切面的优先级

  • 在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.

  • 切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.

  • 实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.

  • 若使用 @Order 注解, 序号出现在注解中

@Order(0)
@Aspect
public class Logging {
}

@Order(1)
@Aspect
public class Logging {
}

 

Spring基于 XML 的配置声明切面

在 Spring中启用XML配置支持

  • 当使用 XML 声明切面时, 需要在 <beans> 根元素中导入 aop Schema

  <beans  xmlns:aop="http://www.springframework.org/schema/aop" ></beans>

正常情况下, 基于注解的声明要优先于基于 XML 的声明**.** 通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的.由于AspectJ得到越来越多的AOP框架支持,所以以注解风格编写的切面将会有更多重用的机会

声明一个切面

  • 在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 <aop:config >元素内部. 对于每个切面而言, 都要创建一个 <aop:aspect > 元素来为具体的切面实现引用后端 Bean 实例.

  • 切面 Bean 必须有一个标示符, 供 <aop:aspect > 元素引用

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

<aop:config>
   <aop:aspect id="myAspect" ref="aBean">
   ...
   </aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>

这里,“aBean” 将被配置和依赖注入,就像前面的章节中你看到的其他的 Spring bean 一样。

声明一个切入点

  • 切入点使用 <aop:pointcut >元素声明

  • 切入点必须定义在 aop:aspect 元素下, 或者直接定义在 aop:config 元素下.

  • 定义在 aop:aspect 元素下: 只对当前切面有效

  • 定义在 aop:config 元素下: 对所有切面都有效

  • 基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点.

<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 Schema 中, 每种通知类型都对应一个特定的 XML 元素.

  • 通知元素需要使用 <pointcut-ref> 来引用切入点, 或用 <pointcut> 直接嵌入切入点表达式. method 属性指定切面类中通知方法的名称.

<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>

Spring事务管理

事务管理是什么

概述:

一个事务是对数据库进行读和写的一个序列。传统数据库(RDB)中事务有两个明显的特性:原子性和可串行性。原子性意指事务中的读和写操作可看作是对数据库的单个原子操作。可串行性意指多个事务并发执行的效果与一次执行这些事务中的一个的效果相同。因而事务管理的任务就是保证事务的原子性和可串行性,它由两部分组成:并发控制和恢复。并发控制涉及到多个事务对数据库的某个公共部分进行同时存取的自动控制。恢复则涉及到将数据库恢复到事务故障之前业已存在的状态。

特点:

  • 事务管理是对于一系列数据库操作进行管理,一个事务包含一个或多个SQL语句,是逻辑管理的工作单元(原子单元)。

  • 事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.

  • 事务就是一系列的动作,它们被当做一个单独的工作单元.这些动作要么全部完成,要么全部不起作用

事务的概念

我们知道,在JavaEE的开发过程中,service方法用于处理主要的业务逻辑,而业务逻辑的处理往往伴随着对数据库的多个操作。以我们生活中常见的转账为例,service方法要实现将A账户转账到B账户的功能,则该方法内必定要有两个操作:先将A账户的金额减去要转账的数目,然后将B账户加上相应的金额数目。这两个操作必定要全部成功,方才表示本次转账成功;若有任何一方失败,则另一方必须回滚(即全部失败)。事务指的就是这样一组操作:这组操作是不可分割的,要么全部成功,要么全部失败。

事务的特性

事务的四个关键属性(ACID)

  • 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.

  • 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.

  • 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.

  • 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.

Spring中的事务管理

  • 作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.

  • Spring 既支持编程式事务管理, 也支持声明式的事务管理

  • 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.

  • 声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.

Spring 事务管理接口

Spring 事务管理为我们提供了三个高层抽象的接口,分别是TransactionProxyFactoryBean,TransactionDefinition,TransactionStatus

1.PlatformTransactionManager事务管理器

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring框架并不直接管理事务,而是通过这个接口为不同的持久层框架提供了不同的PlatformTransactionManager接口实现类,也就是将事务管理的职责委托给Hibernate或者iBatis等持久化框架的事务来实现.

org.springframework.jdbc.datasource.DataSourceTransactionManager:使用JDBC或者iBatis进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager:使用hibernate5版本进行持久化数据时使用
org.springframework.orm.jpa.JpaTransactionManager:使用JPA进行持久化数据时使用
org.springframework.jdo.JdoTransactionManager:当持久化机制是jdo时使用
org.springframework.transaction.jta.JtaTransactionManager:使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用

PlatformTransactionManager接口源码:

public interface PlatformTransactionManager {
    //事务管理器通过TransactionDefinition,获得“事务状态”,从而管理事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //根据状态提交
    void commit(TransactionStatus var1) throws TransactionException;
   //根据状态回滚
    void rollback(TransactionStatus var1) throws TransactionException;
}

2.TransactionDefinition定义事务基本属性

org.springframework.transaction.TransactionDefinition接口用于定义一个事务,它定义了Spring事务管理的五大属性:隔离级别传播行为是否只读事务超时回滚规则

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

2.1隔离级别

什么是事务的隔离级别?我们知道,隔离性是事务的四大特性之一,表示多个并发事务之间的数据要相互隔离,隔离级别就是用来描述并发事务之间隔离程度的大小 在并发事务之间如果不考虑隔离性,会引发如下安全性问题: 脏读 :一个事务读到了另一个事务的未提交的数据 不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致 幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致 在 Spring 事务管理中,为我们定义了如下的隔离级别: ISOLATION_DEFAULT:使用数据库默认的隔离级别 ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读 ISOLATION_READ_COMMITTED:允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生 ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

2.2传播行为

Spring事务传播机制规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间的发生调用时的事务上下文的规则 Spring定义了七种传播行为,这里以方法A和方法B发生嵌套调用时如何传播事务为例说明: PROPAGATION_REQUIRED:A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务 PROPAGATION_SUPPORTS:A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行 PROPAGATION_MANDATORY:A如果有事务,B将使用该事务;如果A没有事务,B将抛异常 PROPAGATION_REQUIRES_NEW:A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务 PROPAGATION_NOT_SUPPORTED:A如果有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行 PROPAGATION_NEVER:A如果有事务,B将抛异常;A如果没有事务,B将以非事务执行 PROPAGATION_NESTED:A和B底层采用保存点机制,形成嵌套事务

2.3是否只读

如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务

2.4事务超时

事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在 TransactionDefinition 中以 int 的值来表示超时时间,默认值是-1,其单位是秒

2.5回滚规则

回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚

3.TransactionStatus事务状态

org.springframework.transaction.TransactionStatus接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息 TransactionStatus接口源码:

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();// 是否是新的事物

    boolean hasSavepoint();// 是否有恢复点

    void setRollbackOnly();// 设置为只回滚

    boolean isRollbackOnly();// 是否为只回滚

    void flush();// 刷新

    boolean isCompleted();// 是否已完成
}

Spring 事务管理实现方式

Spring 事务管理有两种方式:编程式事务管理声明式事务管理 编程式事务管理通过TransactionTemplate手动管理事务,在实际应用中很少使用,我们来重点学习声明式事务管理 声明式事务管理有三种实现方式:基于TransactionProxyFactoryBean的方式基于AspectJ的XML方式基于注解的方式

我们以用户转账为例来学习这三种不同的实现方式,首先来搭建转账环境

1.建表,初始化数据库

create table account
(
  id    bigint auto_increment primary key,
  name  varchar(32) not null,
  money bigint      not null,
  constraint account_name_uindex
  unique (name)
);
insert into account (name, money) values('Bill', 2000),('Jack', 2000);

2.创建DAO实现类

public class TransferDaoImpl extends JdbcDaoSupport implements TransferDao {

    /**
     * @param name 账户名称
     * @param amount 支出金额
     */
    @Override
    public void payMoney(String name, Long amount) {

        String sql = "update account set money=money-? where name=?";
        this.getJdbcTemplate().update(sql, amount, name);
    }

    /**
     * @param name 账户名称
     * @param amount 收入金额
     */
    @Override
    public void collectMoney(String name, Long amount) {

        String sql = "update account set money=money+? where name=?";
        this.getJdbcTemplate().update(sql, amount, name);
    }
}

3.创建Service实现类(事务管理类)

public class TransferServiceImpl implements TransferService {

    private TransferDao transferDao;

    public void setTransferDao(TransferDao transferDao) {
        this.transferDao = transferDao;
    }
     /**
     * @param source 支出方账户名称
     * @param name 收入方账户名称
     * @param amount 转账金额
     */
    @Override
    public void transferMoney(String source, String destination, Long amount) {
        transferDao.payMoney(source, amount);
        int i = 100/0;//此处用于测试抛异常时是否会回滚
        transferDao.collectMoney(destination, amount);
    }
}

4.创建Spring核心配置文件

    <!-- 读取db.properties配置信息 -->
    <context:property-placeholder location="db.properties"></context:property-placeholder>
    <!-- 配置c3p0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${db.driverClass}" />
        <property name="jdbcUrl" value="${db.url}" />
        <property name="user" value="${db.username}" />
        <property name="password" value="${db.password}" />
    </bean>

    <bean id="transferDao" class="com.tx.dao.impl.TransferDaoImpl">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="transferService" class="com.tx.service.impl.TransferServiceImpl">
        <property name="transferDao" ref="transferDao" />
    </bean>

基于TransactionProxyFactoryBean的方式

在spring核心配置文件中添加事务管理器的配置和TransactionProxyFactoryBean代理对象

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!--配置业务层的代理-->
    <bean id="transferServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <!--配置目标对象-->
        <property name="target" ref="transferService" />
        <!--注入事务管理器-->
        <property name="transactionManager" ref="transactionManager" />
        <!--注入事务属性-->
        <property name="transactionAttributes">
            <props>
                <!--
                    prop的格式:
                        * PROPAGATION :事务的传播行为
                        * ISOLATION :事务的隔离级别
                        * readOnly :是否只读
                        * -Exception :发生哪些异常回滚事务
                        * +Exception :发生哪些异常不回滚事务
                -->
                <prop key="transfer*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
// 告诉junit spring的配置文件
@ContextConfiguration({"classpath:demo.xml"})
public class SpringTransactionApplicationTests {

    @Autowired
    TransferService transferService;

    @Resource(name="transferServiceProxy")
    TransferService transferServiceProxy;

    @Test
    public void contextLoads() {
        //注意,此处引入的是代理对象transferServiceProxy,而不是transferService
        transferServiceProxy.transferMoney("Bill","Jack", 200L);
    }
}

运行结果:

java.lang.ArithmeticException: / by zero

    at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:22)
    at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)

执行service事务方法时抛出异常,事务回滚,数据库中数据未发生改变

基于AspectJ的XML方式

在spring核心配置文件中添加事务管理器的配置、事务的增强以及切面

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

   <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transfer*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <!--配置切面-->
    <aop:config>
        <aop:pointcut id="pointcut1" expression="execution(* com.tx.service.impl.*ServiceImpl.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1" />
    </aop:config>

测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
// 告诉junit spring的配置文件
@ContextConfiguration({"classpath:demo.xml"})
public class SpringTransactionApplicationTests {

    @Autowired
    TransferService transferService;

    @Test
    public void contextLoads() {
        transferService.transferMoney("Bill","Jack", 200L);
    }
}

运行结果:

java.lang.ArithmeticException: / by zero

    at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:22)
    at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)

执行service事务方法时抛出异常,事务回滚,数据库中数据未发生改变

基于注解的方式

在spring核心配置文件中添加事务管理器的配置和开启事务注解

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager" />

在事务方法中添加@Transaction注解

    @Transactional
    public void transferMoney(String source, String destination, Long amount) {

        transferDao.payMoney(source, amount);
        int i = 100/0;
        transferDao.collectMoney(destination, amount);
    }

测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringTransactionApplicationTests {

    @Autowired
    TransferService transferService;

    @Test
    public void contextLoads() {
        transferService.transferMoney("Bill","Jack", 200L);
    }
}

运行结果: java.lang.ArithmeticException: / by zero

at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:24)
at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)

总结

在声明式事务管理的三种实现方式中,基于TransactionProxyFactoryBean的方式需要为每个进行事务管理的类配置一个TransactionProxyFactoryBean对象进行增强,所以开发中很少使用;基于AspectJ的XML方式一旦在XML文件中配置好后,不需要修改源代码,所以开发中经常使用;基于注解的方式开发较为简单,配置好后只需要在事务类上或方法上添加@Transaction注解即可,所以开发中也经常使用

 

其他

Spring使用外部属性文件

    <!-- 加载外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

参考文章:spring的工作原理介绍

W3Cshool Spring教程

Spring基础知识汇总 Java开发必看

Spring 事务管理详解

posted on 2020-03-06 15:26  LRH呀  阅读(534)  评论(0编辑  收藏  举报