Spring知识点总结

Spring知识点总结


(笔者的一点对spring的简单总结,有不足请多多指教)

1、什么是Spring

(1)Spring 是个java企业级应用的开源开发轻量级框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。

(2)主要有三大特点:容器、IOC(控制反转)、AOP(面向切面编程)。

2、Spring的优点

(1)轻量:Spring 是轻量的,基本的版本大约2MB。

(2)控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。

(3)面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开

(4)事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。

(5)异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。

(6)容器:Spring 包含并管理应用中对象的生命周期和配置。

(7)MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。

3、Spring的体系结构

1、体系结构图如下:

Spring 体系结构

2、Spring框架整体被分为五个模块:核心容器(core container), 面向切面编程(aop), 数据访问(date access), web, test.

Spring4去掉了spring3中的struts,添加了messaging和websocket,其他模块保持不变。

(1)core 模块组成

​ spring-core:依赖注入IoC与DI的最基本实现

​ spring-beans:Bean工厂与bean的装配

​ spring-context:spring的context上下文即IoC容器

​ spring-expression:spring表达式语言

它们的完整依赖关系

因为spring-core依赖了commons-logging,而其他模块都依赖了spring-core,所以整个spring框架都依赖了commons-logging,如果有自己的日志实现如log4j,可以排除对commons-logging的依赖,没有日志实现而排除了commons-logging依赖,编译报错

(2)aop 模块组成

​ spring-aop:面向切面编程

​ spring-aspects:集成AspectJ

​ spring-instrument:提供一些类级的工具支持和ClassLoader级的实现,用于服务器

​ spring-instrument-tomcat:针对tomcat的instrument实现(包含了spring的tomcat设备代理)

(3) data access 模块组成

​ spring-jdbc:jdbc的支持

​ spring-tx:事务控制

​ spring-orm:对象关系映射,集成orm框架

​ spring-oxm:对象xml映射

​ spring-jms:java消息服务

(4)web 模块组成

​ spring-web:基础web功能,如文件上传

​ spring-webmvc:mvc实现

​ spring-webmvc-portlet:基于portlet的mvc实现

​ spring-websocket:为web应用提供的高效通信工具

(5) test 模块组成

​ test部分只有一个模块,我将spring-context-support也放在这里

​ spring-test:spring测试,提供junit与mock测试功能

​ spring-context-support:spring额外支持包,比如邮件服务、视图解析等

4、Spring IOC(控制反转)

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

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

1、Spring BeanFactory 容器

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

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

在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory

2、Spring ApplicationContext 容器
(1)Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文

(2)Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。

(3)ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。

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

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

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

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

3、bean的定义

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

有下面三个重要的方法把配置元数据提供给 Spring 容器:

(1)基于 XML 的配置文件

(2)基于注解的配置

(3)基于 Java 的配置

Bean 与 Spring 容器的关系

下图表达了Bean 与 Spring 容器之间的关系:

Spring Bean 定义

4、bean的作用域

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

实例:

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

5、bean生命周期

(1)初始化:配置一个方法作为生命周期初始化方法,spring会在对象创建之后立刻调用 init-method

(2)销毁:配置一个方法作为生命周期的销毁方法,spring容器在关闭并销毁所有容器中的对象之前调用destory-method

 <bean init-method=“init”  destory-method=“destory”></bean>        对应注解为@PostConstruct

    <bean name=“hello” class=“完整类名”></bean>                     对应注解为@PreDestory

四个阶段:

1、实例化 Instantiation

2、属性赋值 Populate

3、初始化 Initialization

4、销毁 Destruction

5、Spring DI(依赖注入)

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

1、基于构造函数的依赖注入

构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

实例:

   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <constructor-arg ref="spellChecker"/>
   </bean>

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

2、基于设值函数的依赖注入

Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。

实例

public class TextEditor {
   private SpellChecker spellChecker;
   // a setter method to inject the dependency.
   public void setSpellChecker(SpellChecker spellChecker) {
      System.out.println("Inside setSpellChecker." );
      this.spellChecker = spellChecker;
   }
   // a getter method to return spellChecker
   public SpellChecker getSpellChecker() {
      return spellChecker;
   }
   public void spellCheck() {
      spellChecker.checkSpelling();
   }
}

配置:

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

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

注意:

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

(2)如果你要把一个引用传递给一个对象,那么你需要使用 标签的 ref 属性,而如果你要直接传递一个值,那么你应该使用 value 属性。

3、注入内部bean

Java 内部类是在其他类的范围内被定义的,同理,inner beans 是在其他 bean 的范围内定义的 bean。因此在 或 元素内 元素被称为内部bean,如下所示

<bean id="outerBean" class="...">
      <property name="target">
         <bean id="innerBean" class="..."/>
      </property>
   </bean>

4、集合的注入

1.array数组的注入

img

2.list集合的注入

img

3.map集合的注入

img

​ 4.properties的注入

img

6、Spring beans 自动装配

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

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

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

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

1、自动装配 byName

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

实例

public class TextEditor {
   private SpellChecker spellChecker;
   private String name;
   public void setSpellChecker( SpellChecker spellChecker ){
      this.spellChecker = spellChecker;
   }
   public SpellChecker getSpellChecker() {
      return spellChecker;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
}
正常配置文件
<!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
       <property name="spellChecker" ref="spellChecker" />
       <property name="name" value="Generic Text Editor" />
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>
   
自动装配配置
   <!-- 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 -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

2、自动装配 `byType

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

正常配置
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <property name="spellChecker" ref="spellChecker" />
      <property name="name" value="Generic Text Editor" />
   </bean>

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

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

3、由构造函数自动装配

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

实例

public class TextEditor {
   private SpellChecker spellChecker;
   private String name;
   public TextEditor( SpellChecker spellChecker, String name ) {
      this.spellChecker = spellChecker;
      this.name = name;
   }
   public SpellChecker getSpellChecker() {
      return spellChecker;
   }
   public String getName() {
      return name;
   }
}
正常配置
<!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <constructor-arg  ref="spellChecker" />
      <constructor-arg  value="Generic Text Editor"/>
   </bean>

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

自动装配配置
 <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor" 
      autowire="constructor">
      <constructor-arg value="Generic Text Editor"/>
   </bean>

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

7、Spring 注解配置

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

Spring 应用程序中使用的任何注解,可以考虑到下面的配置文件。

<context:annotation-config/>

一旦 被配置后,你就可以开始注解你的代码,表明 Spring 应该自动连接值到属性,方法和构造函数。让我们来看看几个重要的注解,并且了解它们是如何工作的:

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

1、@Required注解

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

实例

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;
   }
}
会报错,因为age的赋值被注释了
<context:annotation-config/>
<!-- Definition for student bean -->
   <bean id="student" class="com.tutorialspoint.Student">
      <property name="name"  value="Zara" />

      <!-- try without passing age and check the result -->
      <!-- property name="age"  value="11"-->
   </bean>
正常
<bean id="student" class="com.tutorialspoint.Student">
      <property name="name"  value="Zara" />
      <property name="age"  value="11"/>
   </bean>

2、@Autowired 注解

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

@Autowired 注释可以在 setter 方法中被用于自动连接 bean,就像 @Autowired 注释,容器,一个属性或者任意命名的可能带有多个参数的方法。

(1)Setter 方法中的 @Autowired

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

(2)属性中的 @Autowired (常用)

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

(3)构造函数中的 @Autowired)

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

(4)@Autowired 的(required=false)选项

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

3、@Qualifier 注解

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

实例

public class Student {
   private Integer age;
   private String name;
   public void setAge(Integer age) {
      this.age = age;
   }   
   public Integer getAge() {
      return age;
   }
   public void setName(String name) {
      this.name = name;
   }  
   public String getName() {
      return name;
   }
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() );
   }
}
<context:annotation-config/>

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

4、@PostConstruct 和 @PreDestroy 注解

为了定义一个 bean 的安装和卸载,我们使用 init-method 和/或 destroy-method 参数简单的声明一下 。init-method 属性指定了一个方法,该方法在 bean 的实例化阶段会立即被调用。同样地,destroy-method 指定了一个方法,该方法只在一个 bean 从容器中删除之前被调用。

你可以使用 @PostConstruct 注释作为初始化回调函数的一个替代,@PreDestroy 注释作为销毁回调函数的一个替代。

5、@Resource 注解

可以在字段中或者 setter 方法中使用 @Resource 注释,它和在 Java EE 5 中的运作是一样的。@Resource 注释使用一个 ‘name’ 属性,该属性以一个 bean 名称的形式被注入。你可以说,它遵循 by-name 自动连接语义 。

实例

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

如果没有明确地指定一个 ‘name’,默认名称源于字段名或者 setter 方法。在字段的情况下,它使用的是字段名;在一个 setter 方法情况下,它使用的是 bean 属性名称。

6、@Configuration 和 @Bean 注解(常用于配置文件)

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

实例

@Configuration
public class HelloWorldConfig {
   @Bean 
   public HelloWorld helloWorld(){
      return new HelloWorld();
   }
}

相对于
<beans>
   <bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
</beans>

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

7、spring 的事件处理

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

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

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

序号Spring 内置事件 & 描述
1ContextRefreshedEventApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。
2ContextStartedEvent当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
3ContextStoppedEvent当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。
4ContextClosedEvent当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
5RequestHandledEvent这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。

8、Spring AOP(面向切面)

1、AOP的概念

AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。**OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。**例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

​ 而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

(1)优点:

1、降低模块之间的耦合度

2、使系统容易扩展

3、更好的代码复用

(2)应用场景:

场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )

2、AOP的基本概念

(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知

(2)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有 before,after,afterReturning,afterThrowing,around

(4)JoinPoint(连接点):程序执行过程中明确地点,一般是方法的调用

(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

3、通知方法:

(1)前置通知:在我们执行目标方法之前运行(@Before)

(2)后置通知:在我们目标方法运行结束之后 ,不管有没有异常**(@After)**

(3)返回通知:在我们的目标方法正常返回值后运行**(@AfterReturning)**

(4)异常通知:在我们的目标方法出现异常后运行**(@AfterThrowing)**

(5)环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法执行之前相当于前置通知, 执行之后就相当于我们后置通知**(@Around)**

4、基于注解的实现实例

//日志切面类
@Aspect
public class LogAspects {
    //切入点,主要说明在什么地方切入
	@Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))")
	public void pointCut(){};
	
	//@before代表在目标方法执行前切入, 并指定在哪个方法前切入,即在Catalatoe的所有方法执行前都执行该方       法,其他环绕通知而类似
	@Before("pointCut()")
	public void logStart(){
		System.out.println("除法运行....参数列表是:{}");
	}
	@After("pointCut()")
	public void logEnd(){
		System.out.println("除法结束......");
	}
	@AfterReturning("pointCut()")
	public void logReturn(){
		System.out.println("除法正常返回......运行结果是:{}");
	}
	@AfterThrowing("pointCut()")
	public void logException(){
		System.out.println("运行异常......异常信息是:{}");
	}
	@Around("pointCut()")
	public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
		System.out.println("@Arount:执行目标方法之前...");
		Object obj = proceedingJoinPoint.proceed();//相当于开始调div地
		System.out.println("@Arount:执行目标方法之后...");
		return obj;
	}

目标方法:
public class Calculator {
	//业务逻辑方法
	public int div(int i, int j){
		System.out.println("--------");
		return i/j;
	}
}
配置类:
@Configuration
@EnableAspectJAutoProxy
public class Cap10MainConfig {
	@Bean
	public Calculator calculator(){
		return new Calculator();
	}
 
	@Bean
	public LogAspects logAspects(){
		return new LogAspects();
	}

测试类:
@Test
	public void test01(){
		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap10MainConfig.class);	
		Calculator c = app.getBean(Calculator.class);
		int result = c.div(4, 3);
		System.out.println(result);
		app.close();
 
	}
运行结果:
@Arount:执行目标方法之前...
除法运行....参数列表是:{}
--------
@Arount:执行目标方法之后...
除法结束......
除法正常返回......运行结果是:{}
1

5、spring AOP常见的几个注解

(1)@EnableAspectJAutoProxy开启AOP

(2)@EnableTransactionManagement开启spring事务管理,

(3)@EnableCaching开启spring缓存

(4)@EnableWebMvc 开启webMvc

6、spring AOP的实现原理

(1)基于jdk的动态代理(基于接口编程)

JDK 的动态代理主要涉及 java.lang.reflect 包中的两个类: Proxy 和 InvocationHandler。

1、InvocationHandler 是接口,可以通过实现该接口来定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起 。

2、Proxy 利用 InvocationHandler 动态创建出一个实现某一接口的实例,来生成目标类的代理对象

PerformanceMonitor(性能监控器):

public class PerformanceMonitor {

    //通过 ThreadLocal,保存与调用线程相关的性能监视信息
    private static ThreadLocal<PerformanceRecord> record=new
            ThreadLocal<PerformanceRecord>();

    /**
     * 开启监视
     * @param method 需要监视的方法
     */
    public static void begin(String method) {
        System.out.println("开启监视...");
        record.set(new PerformanceRecord(method));
    }

    /**
     * 结束监视
     */
    public static void end() {
        System.out.println("结束监视...");
        record.get().print();

    }
}
/**
 * 新增(JDK 代理)
 */
public void addByJDKProxy() {
    addOrder();
}
/**
 * 新增订单
 */
private void addOrder() {
    System.out.println("模拟新增订单");
    try {
        Thread.currentThread().sleep(50);
    } catch (InterruptedException e) {
        throw new RuntimeException();
    }
}

接着创建动态代理类 PerformanceHandler

public class PerformanceHandler implements InvocationHandler {

    private final Object target;//目标类

    public PerformanceHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName()+"."+method.getName());
        Object obj=method.invoke(target,args);//调用业务类的目标方法
        PerformanceMonitor.end();
        return obj;
    }
}

InvocationHandler 接口只定义了一个方法:public Object invoke(Object proxy, Method method, Object[] args),各参数说明如下:

参数说明
proxy最终生成的代理实例,很少用到。
method被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用。
args被代理实例某个方法的入参,在方法反射调用时使用。

单元测试:

//编织目标业务类与横切代码
PerformanceHandler handler=new PerformanceHandler(orderService);

//创建代理实例
OrderService proxy=(OrderService) Proxy.newProxyInstance(orderService.getClass()
        .getClassLoader(),orderService.getClass().getInterfaces(),handler);

proxy.addByJDKProxy();

这里通过 Proxy 的 newProxyInstance() 的静态方法为编织了业务逻辑和性能监视逻辑的 handler 创建了一个符合 OrderService 接口的代理实例 。 这个方法的各参数说明如下:

参数类型说明
loaderClassLoader类加载器。
interfacesClass<?>[]创建代理实例所需要实现的一组接口。
hInvocationHandler整合了业务逻辑和横切逻辑的编织器对象。

img

(2)基于CGLIB 的动态代理

CGLib 采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截技术来拦截所有父类方法的调用并顺势织入横切逻辑 。

代理类:

public class PerformanceProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);//设置父类
        enhancer.setCallback(this);
        return enhancer.create();//使用字节码技术动态创建子类的实例
    }

    /**
     * 拦截父类所有方法的调用
     *
     * @param o           目标类实例
     * @param method      目标类方法的反射对象
     * @param args        方法的动态入参
     * @param methodProxy 代理类实例
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
        Object result = methodProxy.invokeSuper(o, args);
        PerformanceMonitor.end();
        return result;
    }
}

单元测试:

PerformanceProxy proxy = new PerformanceProxy();
OrderServiceImpl orderService = (OrderServiceImpl) proxy.getProxy(OrderServiceImpl.class);
orderService.addOrder();

输出结果:

开启监视…
模拟新增订单
结束监视…
net.deniro.spring4.aop.OrderServiceImpl$$EnhancerByCGLIB$$aea908ed.addOrder 耗费时间:83 毫秒

代理类名变为 OrderServiceImpl$$EnhancerByCGLIB$$aea908ed.addOrder 这个特殊的类就是 CGLib 为 OrderServiceImpl 动态创建的子类 。


Spring AOP 通过切点(Pointcut)来决定需要在哪些类的哪些方法上织入横切逻辑;接着通过增强(Advice)来描述横切逻辑和方法的具体织入点;然后通过切面(Advisor)把切点与增强装配起来;最后,利用 JDK 或 CGLib 的动态代理技术为目标类创建已织入切面的代理对象。

创建的动态代理对象的性能,CGLib 是 JDK 的 10 倍;而创建动态代理对象所花费的时间上,CGLib 却比 JDK 多花 8 倍的时间。所以,对于单例模式或者具有实例池的代理类,适合采用 CGLib 技术;反之,则适合采用 JDK 技术。

9、Spring 事物管理

1、事物认识

大家所了解的事务Transaction,它是一些列严密操作动作,要么都操作完成,要么都回滚撤销。Spring事务管理基于底层数据库本身的事务处理机制。数据库事务的基础,是掌握Spring事务管理的基础。

事物的四大特性(ACID)

(1) 原子性(Atomicity )

事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。

(2) 一致性(Consistency )

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

(3) 隔离性(Isolation )

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

(4) 持久性(Durability )

指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

2、事务的传播特性

Spring定义了7中传播行为:

(1)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。

(2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

(3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

(4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。

(5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

(6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

(7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。

3、事务的隔离级别
第一种隔离级别:Read uncommitted(读未提交)
如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据

解决了更新丢失,但还是可能会出现脏读

第二种隔离级别:Read committed(读提交)
如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

解决了更新丢失和脏读问题

第三种隔离级别:Repeatable read(可重复读取)
可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。

解决了更新丢失、脏读、不可重复读、但是还会出现幻读

第四种隔离级别:Serializable(可序化)
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读
在这里插入图片描述

4、几个重要概念

(1)脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。

(2)不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。

(3)幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
  
5、事务几种实现方式

(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。

(2)基于 TransactionProxyFactoryBean的声明式事务管理

(3)基于 @Transactional 的声明式事务管理

(4)基于Aspectj AOP配置事务

实例

基于 @Transactional 的声明式事务管理
其他类不做改变,只改变购买股票接口实现类和配置文件
public class BuyStockServiceImpl implements BuyStockService{
    private AccountDao accountDao;
    private StockDao stockDao;  
    
    @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
    @Override
    public void addAccount(String accountname, double money) {
        accountDao.addAccount(accountname,money);
       
    }
 
    @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
    @Override
    public void addStock(String stockname, int amount) {
        stockDao.addStock(stockname,amount);
        
    }
 
    public BuyStockServiceImpl() {
        // TODO Auto-generated constructor stub
    } 
  @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class)
    @Override
    public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
        boolean isBuy = true;
        accountDao.updateAccount(accountname, money, isBuy);
        if(isBuy==true){
            throw new BuyStockException("购买股票发生异常");
        }
            stockDao.updateStock(stockname, amount, isBuy);
        
    }
 
    
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 注册数据源 C3P0 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  >
         <property name="driverClass" value="${jdbc.driverClass}"></property>
         <property name="jdbcUrl"  value="${jdbc.url}"></property>
         <property name="user"  value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
    </bean>
    
    <bean id="accountDao" class="transaction.test3.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="stockDao" class="transaction.test3.dao.StockDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="buyStockService" class="transaction.test3.service.BuyStockServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="stockDao" ref="stockDao"></property>
    </bean>
    
    
    <!-- 事务管理器 -->
    <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 启用事务注解 -->
    <tx:annotation-driven transaction-manager="myTracnsactionManager"/>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值