lukazan

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 1、Spring介绍

(1) 概述

        Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在 Spring 中说明对象(模块)的关系。 Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相 调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。

(2) spring容器

        Spring 是一个框架,是一个半成品的软件。有 20 个模块组成。它是一个容器管理对象,容器是装东西的, Spring 容器不装文本,数字。装的是对象。 Spring 是存储对象的容器。
  • 放入容器的对象:将dao类,service类,controller类,工具类放入容器
  • 不放入容器的对象:实体类对象,实体类数据来自数据库。servlet,listener,filter等,这些对象应该放在tomcat容器中。

(3) spring的优点

  1. 轻量:Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需 的 jar 总共在 3M 左右。 Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar 
  2. 针对接口编程:解耦合 Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。
  3. AOP编程的支持:通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。 
  4. 方便集成各种优秀框架:Spring 不排斥各种优秀的开源框架,Spring 提供了对各种优秀框架(如 Struts,Hibernate、MyBatis)等的直接支持,简化框架的使用。 

2、IOC控制反转

(1) IOC介绍

        控制反转(IoC, Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。 通过容器实现对象的创建,属性赋值,依赖的管理。
        Spring 框架使用依赖注入(DI)实现 IoC。底层使用的是反射机制,将创建的对象放在map中。
        Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。 Spring 容器管理着容器中 Bean 之间的依赖关系, Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。 使用 IoC 实现对象之间的解耦和。

(2) 使用spring容器管理对象步骤(朴素用法)

  1. 依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
  1. 定义Service接口与实体类

  2. 创建 Spring 配置文件,在配置文件中使用<bean>标签声明对象,会将创建好的对象放入到spring容器中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean id="someService" class="com.luca.service.SomeServiceImpl"/>   
</beans>
  1. 获取容器中的对象

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");    //创建spring容器对象
SomeService service = context.getBean("someService", SomeService.class);                                  //通过bean的id获取容器中的对象

(3) 原理

ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可。

3、基于XML的注入(DI)

(1) set注入:要求java对象属性实现setXXX方法

<!--1.简单类型注入-->
bean id="school" class="com.luca.domain.School">
    <property name="name" value="北工大"></property>
    <property name="address" value="平乐园100号"></property>
</bean>
<!--2.引用类型注入-->
<bean id="stu" class="com.luca.domain.Student">
    <property name="name" value="张三"></property>
    <property name="school" ref="school"></property>
</bean>

(2) 构造注入:要求java对象实现带参构造方法

<bean id="stu2" class="com.luca.domain.Student">
    <constructor-arg index="0" value="李四"/>        <!--index:指明该参数对应着构造器的第几个参数,从 0 开始。-->
    <constructor-arg name="age" value="39"/>         <!--name:指定参数名称。-->
    <constructor-arg name="school" ref="school"/>
</bean>

(3) 引用类型的自动注入

<bean id="school" class="com.luca.domain.School">
    <property name="name" value="北工大"></property>
    <property name="address" value="平乐园100号"></property>
</bean>
 
<!--给com.luca.domain.Student对象自动注入school属性-->
<!--byname方式:要求school属性的名称与上面创建的bean的id一致-->
<bean id="stu3" class="com.luca.domain.Student" autowire="byName">
    <property name="name" value="张三"></property>
    <property name="age" value="18"></property>
</bean>
 
<!--byType方式:要求school属性的类型与上面创建的bean的class一致-->
<bean id="stu4" class="com.luca.domain.Student" autowire="byType">
    <property name="name" value="张三"></property>
    <property name="age" value="18"></property>
</bean>

(4) 为应用指定多个 Spring 配置文件

        为了避免配置文件变得非常庞大、臃肿,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。
        多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>引入。在 Java代码中只需要使用总配置文件对容器进行初始化即可。
  1. <import/>引入多个配置文件

<import resource="classpath:spring-school.xml"/>
<import resource="classpath:spring-student.xml"/>
  1. 使用通配符*

//要求父配置文件名不能满足*所能匹配的格式,否则将出现循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml 的格式,即不能起名为spring-total.xml。
<import resource="classpath:spring-*.xml"/>

4、基于注解的注入(DI)

(1) 配置组件扫描器

        对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变。需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
<!--开启组件扫描,把base-package中所有包含@Component,@Repository,@Service,@Controller注解的类对象添加到spring容器中-->
<context:component-scan base-package="com.luca"/>
        指定多个包的三种方式:
  1. 使用多个 context:component-scan 指定不同的包路径

<context:component-scan base-package="com.luca.service"/>
<context:component-scan base-package="com.luca.domain"/>
  1. 指定 base-package 的值使用分隔符

<!--分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。-->
<context:component-scan base-package="com.luca.service,com.luca.domain"/>
  1. base-package 是指定到父包名

<!--base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。-->
<context:component-scan base-package="com.luca"/>

(2) 定义Bean的注解

         以下注解的value属性用于指定该bean的id值。@Component不指定value属性,bean的id是类名的首字母小写。
  1. @Component
  2. @Repository:用于对DAO实现类进行注解
  3. @Service:用于对Service实现类进行注解
  4. @Controller:用于对Controller实现类进行注解

(3) 属性注入

  @Value:简单类型属性注入
@Component("myschool")
public class School {
    @Value("湖北工业大学")    //使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。
    private String name;
    @Value("湖北省武汉市洪山区南李路28号")
    private String address;
}

(4) 引用类型注入

  1. @Autowired:需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。

//byType方式注入,使用该注解完成属性注入时,类中无需setter。若属性有setter,则也可将其加到setter上。
@Autowired
private School school;
 
//byName方式注入
@Autowired
@Qualifier("myschool")    //@Qualifier的value属性用于指定要匹配的Bean的id值。
private School school;
 
//@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
@Autowired(required=false)
private School school;
  1. @Resource:JDK提供的注解,默认按名称注入。

//byType方式:@Resource注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入bean,则会按照类型进行Bean的匹配注入。
@Resource
private School school;
 
//byName方式:@Resource注解指定其name属性,则name的值即为按照名称进行匹配的Bean的id。
@Resource("myschool")
private School school;

(5) 注解与XML方式注入对比

  1. 注解优点是:

    • 直观
    • 方便
    • 高效(代码少,没有配置文件的书写那么复杂)。
    • 缺点:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
  2. XML 方式优点是:

    • 配置和代码是分离的
    • 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
    • xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

5、AOP面向切面编程介绍

(1) 不使用AOP的开发方式

        需要在业务方法中添加非业务方法
  1. 写两个非业务方法。非业务方法也称为交叉业务逻辑:doTransaction():用于事务处理doLog():用于日志处理然后,再使主业务方法调用它们。

public void doSome(String name, Integer age) {
    doLog();    //非业务方法
    System.out.println("业务方法doSome(");    //业务方法的功能
    doTrans();  //非业务方法
}
  1. 定义非业务方法的工具类,在业务方法中调用

public void doSome(String name, Integer age) {
    ServiceTools.doLog();    //工具类中的非业务方法
    System.out.println("业务方法doSome(");    //业务方法的功能
    ServiceTools.doTrans();  //工具类中的非业务方法
}
        以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑的可读性,降低了代码的可维护性,同时也增加了开发难度。
  1. 采用动态代理方式,扩展和增强业务方法功能。

public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ServiceTools.doLog();   //在业务方法之前调用非业务方法
        Object obj = method.invoke(target, args);   //执行目标方法,即target对象的方法
        ServiceTools.doTrans(); //在业务方法之后调用非业务方法
        return obj;
    }
}
 
//在主程序中使用调用动态代理的方法
public class MyApp {
    public static void main(String[] args) {
        SomeService target = new SomeServiceImpl();                   //使用jdk的Proxy创建代理对象,创建目标对象
        InvocationHandler handler = new MyIncationHandler(target);    //创建InvocationHandler对象
        SomeService proxy = (SomeService) Proxy.newProxyInstance(     //使用Proxy创建代理
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
 
        //通过代理执行方法,会调用handler中的invoke()
        proxy.doSome();
        proxy.doOther();
    }
}

(2) AOP简介

  1. AOP(Aspect Orient Programming),面向切面编程是从动态角度考虑程序运行过程。AOP底层采用动态代理模式实现,采用了两种代理:JDK的动态代理,与CGLIB的动态代理。
  2. 面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
  3. 面向切面编程的好处
    • 减少重复;
    • 专注业务;
    • 注意:面向切面编程只是面向对象编程的一种补充。
 

(3) AOP术语

  1. 切面(Aspect:切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。
  2. 连接点(JoinPoint):连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
  3. 切入点(Pointcut):切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
  4. 目标对象(Target):目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。
  5. 通知(Advice):通知表示切面的执行时间, Advice也叫增强。 上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说, 通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
  6. 切入点定义切入的位置,通知定义切入的时间。

6、AspectJ对AOP的实现

(1)  AspectJ 的通知类型

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知
  • 最终通知

(2)  AspectJ 的切入点表达式

execution(modifiers-pattern? ret-type-pattern
          declaring-type-pattern?name-pattern(param-pattern) 
          throws-pattern?)
  • modifiers-pattern:访问权限类型
  • ret-type-pattern:返回值类型
  • declaring-type-pattern:包名类名
  • name-pattern(param-pattern):方法名(参数类型和参数个数)
  • throws-pattern:抛出异常类型
  • ?:表示可选的部分
 
以上表达式共 4 个部分:execution(访问修饰符  方法返回值  包名.类名.方法名(参数)  异常类型)  表达式中访问修饰符和异常类型可以省略,各部分间用空格分开。可以使用以下符号:
 
  • execution(public * *(..)):任意公共方法。
  • execution(* set*(..)):任何一个以“set”开始的方法。
  • execution(* com.xyz.service.*.*(..)):在 service 包里的任意类的任意方法。
  • execution(* com.xyz.service..*.*(..)):在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
  • execution(* *..service.*.*(..)):所有包下的 serivce 子包下所有类(接口)中所有方法为切入点

(3) 依赖与约束

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。

(4)  AspectJ基于注解的AOP实现流程

  1. 定义业务接口与实现类

  2. 定义切面类

//@Aspect 是aspectj框架中的注解。作用:表示当前类是切面类
@Aspect
public class MyAspectj {
 
    //前置通知方法的定义要求:1)公共方法public;2)方法没有返回值;3)方法名称自定义;4)方法可以有参数,也可以无参数。如果有参数,参数不是自定义的,有几个参数类型可以使用
    @Before("execution(* *..SomeServiceImpl.doSome(..))")
    public void myBefore1() {
        //切面执行的功能代码
        System.out.println("切面功能:在目标方法之前输出执行时间:" + new Date());
    }
}
  1. 声明目标对象,切面类对象

<bean id="someService" class="com.luca.service.SomeServiceImpl"/>
<!--声明切面对象-->
<bean id="myAspect" class="com.luca.service.MyAspectj"/>
  1. 注册AspectJ的自动代理

<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象-->
<aop:aspectj-autoproxy/>
<aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的,是基于 AspectJ 的注解适配自动代理生成器。<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
  1. 调用目标对象,即业务方法,就能实现功能增强

(5) @Before前置通知-方法有JoinPoint参数

        JoinPoint 类型参数。该对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。所有的通知方法均可包含JoinPoint 类型参数
/*
* 指定通知方法中的参数:JoinPoint
* JoinPoint:业务方法,要加入切面功能的业务方法
*   作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参
*   如果需要用到业务方法的信息,就可以加入JoinPoint
*   JoinPoint的值是由框架赋予的,必须是第一个位置的参数
*/
@Before("execution(* com.luca.service.SomeServiceImpl.doSome(String,Integer))")
public void myBefore2(JoinPoint jp) {
    //切面执行的功能代码
    System.out.println("方法的名称:" + jp.getSignature().getName());
    for (Object arg : jp.getArgs()) {
        System.out.println("参数:" + arg);
    }
    System.out.println("切面功能:在目标方法之前输出执行时间:" + new Date());
} 

(6) @AfterReturning 后置通知-注解有 returning 属性

        可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
        被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。
/*
*后置通知方法的定义要求:1)公共方法public;2)方法没有返回值;3)方法名称自定义;4)方法有参数,推荐使用Object,参数名自定义
*@AfterReturning:后置通知
*  属性:1.value 切入点表达式
*       2.returning 自定义变量,表示目标方法的返回值
*           自定义变量名必须和通知方法的形参名一样
*  特点:
*       1、在目标方法之后执行
*       2、能够获取到目标方法的返回值,根据返回值做不同的处理功能
*          Object res = doOther();
*       3、修改返回值
*/
@AfterReturning(value = "execution(* com.luca.service.SomeServiceImpl.doOther(..))",returning = "res")
public void myAfterReturning(Object res) {
     //Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理
     System.out.println("后置通知:在目标方法之后执行的,获得的返回值是:" + res);
}

(7)  @Around 环绕通知-增强方法有ProceedingJoinPoint参数

        在目标方法执行之前之后执行。被注解为环绕增强的方法要有Object 类型的返回值。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("环绕通知,在方法之前执行");
    String obj = (String) pjp.proceed();
    System.out.println("环绕通知,在方法之后执行");
    return obj+"********";
} 

(8)  @AfterThrowing 异常通知-注解中有 throwing 属性

        在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。TODO:SpringMVC异常映射机制是否是通过这种方式实现的

(9)  @After 最终通知

        无论目标方法是否抛出异常,该增强均会被执行

(10)  @Pointcut 定义切入点

        当较多的通知增强方法使用相同的 execution 切入点表达式时,可以使用AspectJ 提供的@Pointcut 注解,定义 execution 切入点表达式。
  • 将@Pointcut 注解在一个方法之上,以后所有通知方法的execution的value属性值均可使用该方法名作为切入点。
  • 使用@Pointcut 注解的方法一般使用 private 的标识方法,没有实际作用。
@Pointcut("execution(* com.luca.service.SomeService.doFirst(..))")
public void myPointCut() {}
 
使用:见(7)@Around 环绕通知

7、Spring事务管理API

        事务原本是数据库中的概念,在Dao层。但一般情况下,需要将事务提升到业务层,即Service层。这样做是为了能够使用事务的特性来管理具体的业务。在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
  • 使用 Spring 的事务注解管理事务
  • 使用 AspectJ 的 AOP 配置管理事务

(1) 事务管理接口

  1. 事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。 有两个常用的实现类:
    • DataSourceTransactionManager:使用 JDBCMyBatis 进行数据库操作时使用。
    • HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
  1. Spring 事务的默认回滚方式是: 发生运行时异常和 error 时回滚,发生受查(编译)异常时提交

  • Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时, 才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。
  • Error 是程序在运行过程中出现的无法处理的错误,当这些错误发生时程序是无法处理,JVM一般会终止线程。
  • 运行时异常(RuntimeException 类及其子类),只有在运行时才出现的异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。
  • 受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。

(2) 事务定义接口

        事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
  1. 五个事务隔离级别常量

    • DEFAULT: 采用 DB 默认的事务隔离级别。 MySql 的默认为 REPEATABLE_READ Oracle默认为 READ_COMMITTED
    • READ_UNCOMMITTED: 读未提交。未解决任何并发问题。
    • READ_COMMITTED: 读已提交。解决脏读,存在不可重复读与幻读。
    • REPEATABLE_READ: 可重复读。解决脏读、不可重复读,存在幻读
    • SERIALIZABLE: 串行化。不存在并发问题。
  2. 七个事务传播行为常量

    • PROPAGATION_REQUIRED: 若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。Spring 默认的事务传播行为。
    • PROPAGATION_REQUIRES_NEW: 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
    • PROPAGATION_SUPPORTS: 支持当前事务,但若当前没有事务,也可以以非事务方式执行。
    • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
    • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
    • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
  3. 默认事务超时时限

    • 常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限, sql 语句的执行时长。使用默认值。

(3) @Transactional注解

        通过@Transactional注解方式,可将事务织入到相应public方法中,实现事务管理。@Transactional的所有可选属性如下所示:
  • propagation: 用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED
  • isolation: 用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT
  • readOnly: 用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 false
  • timeout: 用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
  • rollbackFor: 指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • rollbackForClassName: 指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackFor: 指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackForClassName: 指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
注意:@Transactional若用在方法上,只能用于public方法上。对于其他非public方法,如果加上了注解@Transactional,虽然Spring不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

(4) 实现注解的步骤

  1. 声明事务管理器

<!--1.声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--连接数据库-->
    <property name="dataSource" ref="dataSource"/>
</bean>
  1. 开启注解驱动
<!--2.开启事务注解驱动,让spring使用注解管理驱动,创建代理对象。transaction-manager:事务管理器bean的id-->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
  1. 业务层public方法加入事务属性
@Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,
        readOnly = false,
        rollbackFor = {NullPointerException.class,NotEnoughException.class}
)
@Override
public void buy(Integer GoodsId, Integer amount) {
    //业务功能
}

8、AspectJ的AOP配置管理事务

        使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。
        使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。

(1) 依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

(2) 步骤

  1. 在容器中添加事务管理器

<!--声明式事务处理:和源代码完全分离-->
<!--1.声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--连接数据库-->
    <property name="dataSource" ref="dataSource"/>
</bean>
  1. 配置事务通知

<!--2.声明业务方法的事务属性(隔离级别,传播行为,超出时间)
      id:自定义名称,表示<tx:advice>和</tx:advice>之间的配置内容
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
    <!-- tx:attributes:配置事务属性 -->
    <tx:attributes>
        <!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务
            name:方法名称,1)完整的方法名称,不带有包和类
                          2)方法可以使用通配符,*表示任意字符
        -->
        <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"/>
        <!--使用通配符,指定很多的方法-->
        <!--建立统一命名规范,指定添加方法-->
        <tx:method name="add*"/>
        <!--指定修改方法-->
        <tx:method name="modify*"/>
        <!--删除方法-->
        <tx:method name="remove*"/>
        <!--查询方法,query,search,find-->
        <tx:method name="*" read-only="true"/>
    </tx:attributes>
</tx:advice>
  1. 配置增强器

<!--3.配置AOP-->
<aop:config>
    <!--配置切入点表达式:指定哪些包中的类要使用事务
        id:切入点表达式的名称,唯一值
        expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理事务
    -->
    <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>

    <!--配置增强器:关联advice和pointcut
        advice-ref:通知,上面 tx:advice 的相关配置信息
        pointcut-ref:切入点表达式的id
    -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
  1. 此时"* *..service..*.*(..)"对应的方法上已经有了添加的事务,不用再使用@Transactional注解。

9、Spring的监听器ContextLoaderListener

        在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet) 中获取到 Spring容器的问题。只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。

(1) 存在的问题:spring容器不唯一

  • Spring容器放在Servlet的doGet()或doPost()方法中创建,每次提交请求都会创建spring容器
  • Spring容器放在 Servlet 初始化时创建,即执行 init()方法时创建。会有多个servlet,每次初始化都会创建spring容器

(2) 使用Spring的监听器ContextLoaderListener

        对于Web应用来说,ServletContext 对象是唯一的,该对象是在Web应用装载时初始化的。ServletContext初始化时创建Spring容器,就保证了Spring容器在整个应用中的唯一性。 将创建好的Spring容器,以属性的形式放入到ServletContext的空间中,就保证了Spring容器的全局性。

(3) 依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

(4) 注册监听器ContextLoaderListener

在 web.xml 中注册ContextLoaderListener监听器,来监听ServletContext。
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Spring 为该监听器接口定义了一个实现类ContextLoaderListener, 完成了两个很重要的工作:1)创建容器对象;2)将容器对象放入到了 ServletContext 的空间中
原理分析:ContextLoaderListener的初始化方法contextInitialized()实现了:
this.context = createWebApplicationContext(servletContext);    //创建容器对象
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);    //将容器对象放入到了ServletContext的空间中

(5) 指定 Spring 配置文件的位置<context-param>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <!--spring配置文件的位置-->
    <param-value>classpath:conf/applicationContext.xml</param-value>
</context-param>

(6) 获取spring容器

  1. 直接从ServletContext中获取

WebApplicationContext ac = (WebApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
  1. 通过WebApplicationContextUtils获取

WebApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());

10、Spring配置文件总结

  • <bean>:声明对象
  • <context:component-scan>:组件扫描器
  • 整合mybatis
    • <context:property-placeholder>:加载数据库属性文件
    • <bean class="com.alibaba.druid.pool.DruidDataSource"/>:声明数据源
    • <bean class="org.mybatis.spring.SqlSessionFactoryBean">:声明SqlSessionFactoryBean对象
    • <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">:声明mybatis扫描器,创建dao对象
  • AOP实现
    • <bean class="com.luca.service.MyAspectj"/>:声明切面对象
    • <aop:aspectj-autoproxy/>:aspectj自动代理生成器
  • spring事务管理
    • <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">:声明事务管理器
    • <tx:annotation-driven>:开启事务注解驱动
  • aspectj事务管理
    • <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">:声明事务管理器
    • <tx:advice>:配置事务通知
    • <aop:config>:配置增强器
<?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="someService" class="com.luca.service.SomeServiceImpl"/>

    <!--开启组件扫描-->
    <context:component-scan base-package="com.luca"/>

    <!--整合MyBatis-->
    <!--加载外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--声明数据源DataSource,作用是连接数据库-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库信息-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
    </bean>
    <!--声明mybatis提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionaFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="dataSource"/>
        <!--mybatis主配置文件的位置-->
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>
    <!--注册Mapper扫描配置器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--指定包名,包名是dao接口所在的包名-->
        <property name="basePackage" value="com.luca.dao"/>
    </bean>

    <!--Aspectj实现AOP-->
    <!--声明切面对象-->
    <bean id="myAspect" class="com.luca.service.MyAspectj"/>
    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象-->
    <aop:aspectj-autoproxy/>

    <!--Spring事务-->
    <!--1.声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接数据库-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--2.开启事务注解驱动,让spring使用注解管理驱动,创建代理对象-->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

    <!--Aspectj事务管理-->
    <!--声明式事务处理:和源代码完全分离-->
    <!--1.声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--2.声明业务方法的事务属性(隔离级别,传播行为,超出时间)
          id:自定义名称,表示<tx:advice>和</tx:advice>之间的配置内容
    -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <!-- tx:attributes:配置事务属性 -->
        <tx:attributes>
            <!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务
                name:方法名称,1)完整的方法名称,不带有包和类
                              2)方法可以使用通配符,*表示任意字符
            -->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"/>
            <!--使用通配符,指定很多的方法-->
            <!--建立统一命名规范,指定添加方法-->
            <tx:method name="add*"/>
            <!--指定修改方法-->
            <tx:method name="modify*"/>
            <!--删除方法-->
            <tx:method name="remove*"/>
            <!--查询方法,query,search,find-->
            <tx:method name="*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!--3.配置AOP-->
    <aop:config>
        <!--配置切入点表达式:指定哪些包中的类要使用事务
            id:切入点表达式的名称,唯一值
            expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理事务
        -->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>


        <!--配置增强器:关联advice和pointcut
            advice-ref:通知,上面 tx:advice 的相关配置信息
            pointcut-ref:切入点表达式的id
        -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
    </aop:config>

</beans>

11、思维导图

posted on 2021-04-21 17:03  lukazan  阅读(442)  评论(0编辑  收藏  举报