Spring 是一个轻量级Java开发框架,最早有Rod Johnson 创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring 负责基础架构,因此Java开发者可以专注于应用程序的开发。
Spring 最根本的使命是解决企业级应用开发的复杂性,即简化Java开发
Spring 可以做很多事情,它为企业级开发提供了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。
为了降低Java开发的复杂性,Spring 采取了一下4种关键策略:
IOC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
优点:
缺点:
应用场景: JavaEE企业级开发应用开发,包括SSH、SSM等
Spring价值:
Spring 总共大约有20个模块,由1300多个不同的文件构成。而这些组件被分别整合在 核心容器(Core Container)、AOP(Aspect Oriented Programming)和设备支持(Instrmentation)、数据访问与集成(Data Access/Integeration)、Web、消息(Messaging)、Test等6个模块中。 以下是 Spring 5 的模块结构图:
这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是任何以Spring为基础的应用核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。
Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
Spring 提供了以下5种标准的事件:
Spring 应用一般有以下组件:
使用 Spring 有以下方式:
控制反转即IOC(Inversion of Control),它把传统上由程序代码直接操作对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI)),装配对象,配置对象,并且管理这些对象的整个生命周期。
Spring 中的IOC 的实现原理就是工厂模式加反射
interface Fruit{
public abstract void eat();
}
class Apple implements Fruit{
public void eat(){
System.out.println("Apple");
}
}
class Orange implements Fruit{
public void eat(){
System.out.println("Orange");
}
}
class Factory{
public static Fruit getInstance(String ClassName){
Fruit f = null;
try{
f = (Fruit)Class.forName(ClassName).newInstance();
} catch (Exception e){
e.printStackTrace();
}
return f;
}
}
class Client{
public static void main(String[] a){
Fruit f = Factory.getInstance("io.github.dunwu.spring.Apple");
if(f != null){
f.eat();
}
}
}
Spring 的 IOC 设计支持以下功能:
其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象
对于 IOC 来说,最重要的就是容器。容器管理着 bean 的生命周期,控制着Bean 的依赖注入。
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
依赖关系
BeanFactory:是Spring 里面最底层的接口,包含了各种Bean的定义,读取Bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
加载方式
BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预加载入所有的单实例Bean,通过预加载入单实例bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相当于基本的BeanFactory,ApplicationContext唯一不足的是占用空间。当应用程序配置Bean较多时,程序启动较慢。
创建方式
BeanFactory 通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader.
注册方式
BeanFactory 和 ApplicationContext 都支持BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFatory需要手动注册,而ApplicationContext则是自动注册。
Spring 作者 Red Johnson 设计了两个接口用以表示容器。
BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称为“低级容器”
ApplicationContext 可以称之为“高级容器”。因为他比 BeanFactory 多了更多的功能,他继承了多个接口。因此 具备了更多的功能。例如资源获取,支持多种消息(例如 JSP tap 的支持),对 BeanFactory 多个工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”,代表着整个大容器的所有功能。该接口定义了一个refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的bean.
当然,除了这两个大接口,还有其他的辅助接口。
BeanFactory 和 ApplicationContext的关系
最上面的是 BeanFactory,下面的 3个绿色的,都是功能扩展接口
看下面的隶属 ApplicationContext 粉红色的“高级容器”,依赖着“低级容器”,这里说的是依赖,不是继承呦(小宝贝)。他也依赖着“低级容器”的getBean 功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer 模式)
通常用户看到的就是“高级容器”。但 BeanFactory 也非常够用了。
左边灰色区域的是“低级容器”,只能负载加载Bean、获取Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 bean 工厂所有配置,生命周期事件回调等。
小结
IOC在Spring里,只需要低级容器就可以实现,2个步骤:
1) 加载配置文件,解析成 BeanDefinition 放在 Map 里
2) 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法——完成依赖注入。至于高级容器ApplicationContext,他包含了低级容器的功能,当他执行 refresh模板方法的时候,将刷新整个容器的Bean。同时其作为高级容器,包含了太多的功能。一句话,它不仅仅是IOC。它支持不同信息源头,支持BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。
控制反转IOC是一个很大的概念,可以用不同的方式来实现。其主要实现的方式有两种:依赖注入和依赖查找
依赖注入: 相对于IOC而言,依赖注入(DI)更加准确的描述了IOC得设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IOC容器负责,“查找资源” 的逻辑应该从应用组件的代码中抽取出来,交给IOC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象
依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean 的setter 方法或者带参数的构造器后者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为:
依赖注入是时下最流行的IOC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection),构造器注入(Constructor Injection),三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被弃用。
构造函数 注入 | setter |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖setter属性 | 会覆盖setter属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖
Spring Bean 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中的形式定义
一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。
这里有三种重要的方法给 Spring 容器提供配置元数据。
Spring 配置文件是个XML文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。
当定义一个在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean定义中的scope属性来定义。如,当Spring要在需要的时候每次产生一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回一个实例,这个bean的scope属性必须设为 singleton。
Spring框架支持以下五种bean的作用域:
注意:缺省的Spring Bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
不是,Spring框架中的单例bean不是线程安全的。
spring 中的bean默认是单例模式,spring框架并没有对单例bean进行多线程的封装处理。
实际上大部分时候 spring bean 无状态的(比如 dao类),所有某种程度上来说 bean 也是安全的,但如果bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全,最简单的就是改变bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean(),所以就可以保证线程安全了
- 有状态:就是有数据存储功能。
- 无状态:就是不会保存数据。
在一般情况下,只有无状态的Bean 才可以在多线程环境下共享,在Spring中,绝大部分Bean 都可以声明为 singleton 作用域,因为Spring 对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal 和 线程同步机制都是为了解决多线程中相同的访问冲突问题。同步机制采用了“时间换空间”的方式。
TreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。TreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进TreadLocal。
在传统的Java应用中,bean的生命周期很简单。使用Java关键字new 进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中一个典型的生命周期过程。
bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。
正如你所见,在bean准备就绪之前,bean工厂执行了若干启动步骤
我们对上图进行详细描述:
此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
现在你已经了解了如何创建和加载一个spring容器。但是一个空的容器并没有太大的价值,在你把东西放进去之前,它里面什么都没有。为了从Spring的DI(依赖注入)中受益,我们必须将应用对象装配进Spring容器。
有两个重要的bean生命周期方法,第一个是setup,它是在容器加载bean的时候被调用。第二个方法是 teardown 它是容器卸载类的时候被调用。
bean标签有两个重要的属性(init-method和destory-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@postConstruct和@PreDestory)
在Spring 框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype.
Spring 提供以下几种集合的配置元素:
<list>
类型用于注入一列值,允许有相同的值。<set>
类型用于注入一组值,不允许有相同的值<map>
类型用于注入一组键值对,键和值都可以为任意类型<props>
类型用于注入一组键值对,键和值都只能为String类型装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把他们装配到一起。
在Spring 框架中,在配置wenjain中设定bean的依赖关系是一个很好的机制,Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过bean工厂自动处理bean之间的协作,这意味着 Spring 可以通过向Bean Factory 中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。
在Spring框架xml配置中共有5种自动装配:
使用@Autowired 注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config/>
在启动Spring IOC 时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowired、@Resource或@Inject时,就会在IOC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先容器中查询对应类型的bean:
自动装配的局限性是:
可以
基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。
以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。
另一个例子是@Bean 注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。
@Configuration
public class StudentConfig{
@Bean
public StudentBean myStudent() {
return new StudentBean();
}
}
注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置<context:annotation-config/>
元素。
@Service: 此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是@Component,因为它以更好的方式指定了意图。
这个注解表明bean的属性必须配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required 注解的bean属性未被设置,容器设置将抛出 BeanInitializationException。示例:
public class Employee{
private String name;
@Required
public void setName(){
this.name = name;
}
public string getName() {
return name;
}
}
@Autowired 默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配,。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和 / 或 多个参数 的PN 方法。
public class Employee{
private String name;
@Autowired
public void setName(){
this.name = name;
}
public string getName(){
return name;
}
}
@Autowired 可用于: 构造函数、成员变量、Setter方法
Autowired和@Resource之间的区别:
当你创建多个相同类型的bean 并希望仅使用属性装配其中一个 bean 时,您可以使用Qualifier 注解 和 @Autowired 通过指定应该装配哪个确切的bean来消除歧义。
@RequestMapping 注解用于将特定 HTTP 请求方式映射到将处理相应请求的控制中的特定类/方法。此注解可应用于两个级别:
Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO 和 iBATIS,JPA,TopLink,JDO,OJB。Spring 的事务管理同样支持以上所有ORM框架及JDBC。
在使用Spring JDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写 statements 和 queries 从数据存取数据,JDBC 也可以在Spring 框架提供的模板类的帮助下更有效地被使用,这个模板叫 Jdbc Template
通过使用JDBC抽象和DAO模块,保证数据库代码简洁,并能够避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring 应用中的对象提供事务管理服务。
Spring DAO(数据访问对象)使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作,这使得用户容易在持久性技术之间切换。它还允许,当我们在编写代码时,无需考虑捕获每种技术不同的异常
JdbcTemplate 类提供了很多便利的方法解决诸如把数据库转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理
在Spring 中有两种方式访问Hibernate:
用Spring 的 SessionFactory 调用 LocalSessionFactory。集成过程分为三步:
Spring支持两种类型的事务管理:
Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log 实现的
spring 事务传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为:
1) PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置
2) PROPAGATION_SUPPORTS:支持当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
3) PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
4) PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新的事务。
5) PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6) PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
7) PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
spring 有五大隔离级别,默认值为ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
脏读: 表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录A,此时事务还未提交,然后另一个事务尝试读取到了记录A。
不可重复读: 是指在一个事务内,多次读同一个数据
幻读: 指同一个事务内多次查询返回的结果集不一样。比如同一个事务A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录数据内容被修改了,所有数据行的记录就变多或者变少了。
大多数Spring 框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Hibernate事务
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JPA事务
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Java原生API事务
通常用于跨越多个事务管理源(多数据源),则需要使用下面的内容
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
******通知声明式地管理事务******
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务属性 -->
<!--<tx:advice>元素声明事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="*"/>
<!--propagation配置事务传播行为-->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<!--isolation配置事务的隔离级别-->
<tx:method name="update*" isolation="SERIALIZABLE"/>
<!--rollback-for配置事务遇到异常必须回滚,no-rollback-for配置事务遇到异常必须不能回滚-->
<tx:method name="add*" rollback-for="java.io.IOException" no-rollback-for="com.dmsd.spring.tx.BookStockException"/>
<!--read-only配置事务只读属性-->
<tx:method name="find*" read-only="true"/>
<!--timeout配置事务的超时属性-->
<tx:method name="get*" timeout="3"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
Spring中注解的方式@Transactional标注事务方法。为了将方法定义为支持事务处理,可以在方法上添加@Transactional注解。根据Spring AOP基于代理机制,只能标注公有方法。如果在类上标注@Transactional注解,那么这个类中所有公有方法都会被定义为支持事务。
******用 @Transactional 注解声明式地管理事务******
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在方法上添加
//添加事务注解
//1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
//如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
//REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起.
//2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
//3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的
//属性进行设置. 通常情况下去默认值即可.
//4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据,
//这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
//5.使用 timeout 指定强制回滚之前事务可以占用的时间.
@Transactional(
propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
rollbackFor = IOException.class,
readOnly=false,
timeout=3)
@Override
public void purchase(String username, String isbn) {}
因为spring事务是基于aop的代理机制,当方法中调用this本身的方法时候即使在this的方法标明事务注解,但是事务注解会失效
失效代码
@Transactional
@Override
public void purchase(String username, String isbn) {
this.update(username, isbn);
}
@Transactional
public void update(String username, String isbn) {
//1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新数的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
原因: 因为调用ths本身方法不走代理机制,这个时候可以通过配置解决这个问题。
解决事务失效
<!--开启aspectj代理,并暴露aop代理到ThreadLocal-->
<aop:aspectj-autoproxy expose-proxy="true"/>
@Transactional
@Override
public void purchase(String username, String isbn) {
((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
}
OOP(Object-Oriented Programming) 面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量的代码重复,而不利于各个模块的重用。
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
AOP 实现的关键在于代理模式,AOP 代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以 Spring AOP 为代表。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ 的静态代理方式具有更好的性能,但是AspectJ 需要特定的编译器进行处理吗,而Spring AOP 则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args): proxy是最终生成的代理实例;method 是被代理目标实例的某个具体方法;args 是被代理目标实例某个方法的具体入参,在方法反射调用时使用。
将 Advice 应用于目标对象后创建的对象成为代理。在客户端对象的情况下,目标对象和代理对象是相同的。
Advice + Target Object = Proxy
1、切面(Aspect):切面是 通知 和 切点 的结合。通知 和 切点 共同定义了切面的全部内容。 在Spring AOP 中,切面可以使用通用类(基于模式风格)或者 在普通类中以 @AspectJ 注解来实现。
2、连接点 (Join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
3、通知 (Advice):在AOP术语中,切面的工作被称为通知。
4、切入点 (Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
5、 引入 (Introduction):引入允许我们向现有类添加新方法或属性。
6、目标对象 (Target Object):被一个或者多个切面(aspect)所通知(advice)的对象。它通常是一个代理对象。也有人把它叫做被通知(advice)对象。既然Spring AOP 是通过运行时代理实例的,这个对象永远是一个被代理(proxied)对象。
7、织入 (Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:
通过在代理类中包裹切面,Spring 在运行期把切面织入到Spring 管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext 的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring 运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。
因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用Aspect来补充。
在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。
Spring切面可以应用5种类型的通知:
同一个aspect,不同的advice的执行顺序
- 没有异常情况下的执行顺序:
1) around before advice 环绕前通知
2) before advice 前置通知
3) target method 执行方法
4) around after advice 环绕后通知
5) after advice 后置通知
6) afterReturning 返回通知- 有异常情况下的执行顺序:
1) around before advice 环绕前通知
2) before advice 前置通知
3) target method 执行方法
4) around after advice 环绕之后发生
5) after advice 后置通知
6) afterThrowing 异常通知
7) java.lang.RuntimeException 异常发生
aspect 由 pointcount 和 advice 组成,切面是通知和切点的结合。它即包含了横切逻辑定义,也包括了连接点的定义。Spring AOP 就是负责实施切面的框架,它将切面所定义的横切逻辑编织到切面所指定的连接点中。
AOP 的工作重心在于如何增强编织目标对象的连接点上,这里包含两个工作:
可以简单地认为,使用 @Aspect 注解的类就是切面
在这种情况下,切面由常规类以及基于XML的配置实现
在这种情况下(基于 @AspectJ 的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。
原文:https://blog.51cto.com/u_15180480/2731765