从0到1学习Spring框架3

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 从0到1学习Spring框架

7.Spring表达式语言(SpEL)

7.1.SpEL简介

(1)什么是表达式语言

SpEL:Spring Expression Language, Spring 表达式语言

(2)SpEL特点

SpEL是强大的表达式语言。

支持运行时查询、操纵一个对象图功能。

SpEL语言的语法类似于EL,提供了更多的功能。

SpEL是一个基于技术中立的API,允许需要时与其他表达式语言集成。

SpEL与Spring不是直接绑定关系,它可以独立存在,并应用到其它平台

7.2.SpEL基本语法

XML配置文件中使用:#{表达式}

Bean注解中使用:@Value(“#{表达式}”)

引用其他对象属性:#{对象名.属性}

(1)算数运算符

  • 算数运算符:+, -, *, /, %, ^

1.xml中使用:#{表达式}

  • 写一个student实体类
public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Student() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 然后在applicationContext.xml中配置属性
<bean id="student" class="com.tjetc.domain.Student" >
    <property name="name" value="#{'张三'}"></property>
    <property name="age" value="#{10*2}"></property>
</bean>
  • 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean(Student.class);
System.out.println(student);
  • 运行结果

Student{name='李四', age=18}

2.Bean注解中使用 :@Value(“#{表达式 }”)

  • 在实体类中用@Value(“#{10+8}”)注解赋值,加上@Component纳入spring容器管理
@Component
public class Student {
    @Value("李四")
    private String name;
    @Value("#{10+8}")
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Student() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • applicationContext.xml文件,配置扫描包
<context:component-scan base-package="com.tjetc"></context:component-scan>
  • 其他的代码不变

3.以用其他对象的属性:#{对象名.属性}

<bean id="a" class="com.tjetc.domain.A">
   <property name="s" value="#{b.firstName}"></property>
  </bean>
  <bean id="b" class="com.tjetc.domain.B">
    <property name="firstName" value="张"></property>
    <property name="lastName" value="无忌"></property>
</bean>

4.使用类的静态变量:#{T(类的全路径名).静态变量名}

<bean id="a" class="com.tjetc.domain.A">
   <property name="s" value="#{b.firstName}"></property>
   <property name="d" value="#{T(java.lang.Math).PI}"></property>
</bean>

5.使用类的静态方法:#{T(类的全路径名).方法名(参数)}

<bean id="a" class="com.tjetc.domain.A">
   <property name="s" value="#{b.firstName+'  '+b.lastName}"></property>
   <property name="d" value="#{T(java.lang.Math).max(3.1,12.3)}"></property>
</bean>

6.加号还可以作为字符串拼接

<bean id="a" class="com.tjetc.domain.A">
   <property name="s" value="#{b.firstName+'  '+b.lastName}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B">
   <property name="firstName" value="张"></property>
   <property name="lastName" value="无忌"></property>
</bean>

7.使用类的非静态方法#{bean的id.方法名(参数)}

public class B {
  public int sum() {
    return 10+20;
  }
}
<bean id="a" class="com.tjetc.domain.A">
  <property name="i" value="#{b.sum()}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B"></bean>

(2)比较运算符

<,>==,>=,<=,!=,lt(小于),gt(大于),eq(等于),le(小于等于),ge(大于等于)

 <property name="b" value="#{1 lt 1}"></property>

A [i=0, d=0.0, s=null, b=false]

(3)逻辑运算符号

and, or, not(!)

<property name="b" value="#{not(1==1 or 2==3)}"></property>

A [i=0, d=0.0, s=null, b=false]

(4)三目运算符

#{条件表达式?’true’:’false’}

 <property name="s" value="#{2>1?'大于':'不大于'}"></property>

A [i=0, d=0.0, s=大于, b=false]

(5)正则表达式

表达式 说明
. 除了换行符之外的任意字符
* 匹配前面的子表达式零次或多次
/…/ 代表一个模块的开启和结束
+ 匹配前面的子表达式一次或多次
? 匹配前面的子表达式零次或一次
{m} 正好出现m次
{m,} 至少m次
{,n} 至少n次
{m,n} 至少m次,至多n次
\d 数字
\w 字母数字下划线,单词
^ 匹配输入字符串的开始位置
$ 匹配输入字符串的结束位置
\s 任何空白字符
\S 任何非空白子符
\d 匹配一个数字字符。等价于[0-9]
\D 匹配一个非数字字符。等价于[ ^ 0-9]
\W 匹配任何非单词字符。
  • 语法
  • #{变量名或值 matches ‘正则表达式’}
  • 例子
<bean id="a" class="com.tjetc.domain.A">
   <property name="b" value="#{b.firstName matches '1[3578]\d{9}'}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B"></bean>

A [i=0, d=0.0, s=大于, b=true]

8.SpringAOP

8.1.SpringAOP简介

(1)什么是AOP

  • AOP:Aspect Oriented Programming 面向切面的编程
  • (2)AOP与OOP
  • AOP:Aspect Oriented Programming 面向切面的编程
  • OOP:Object Oriented Programming 面向对象的编程

(3)AOP与OOP的区别

  • 面向目标不同:简单来说OOP是面向名词领域,AOP面向动词领域。
  • 思想结构不同:OOP是纵向结构,AOP是横向结构。
  • 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程的某个步骤或阶段。
  • OOP与AOP联系:
  • 两者之间是一个相互补充和完善的关系。
  • AOP的优点:
  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • Spring IOC容器不依赖AOP,如果不需要可以不导入AOP相关包。

(4)Spring AOP提供了两种模式

  • 基于XML的模式
  • 基于@AspectJ注解模式

(5)基于Spring 的 AOP,重要应用有哪些

  • 用AOP声明性事务代替EJB的企业服务
  • 用AOP做日志处理
  • 用AOP做权限控制,如Spring Security

(6)AOP中的专业术语

  • 连接点Joinpoint
  • 方法的位置
  • 程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。 Spring仅支持方法的连接点。
  • 切点Pointcut
  • 定位到方法的条件

  • 每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点。但在这为数从多的连接点中,如何定位到某个感兴趣的连接点上?AOP通过“切点”定位特定连接点。
  • 增强Advice
  • 增强是织入到目标类连接点上的一段程序代码。增强既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以SPRING所提供的增强接口都是带方位名的:BeforeAdvice,AfterReturningAdivce,throwsAdvice等等。
  • 目标对象Target
  • 增强逻辑的织入的目标类(被代理的目标类)
  • 引介Introduction
  • 引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口。通过AOP的引介功能,我们可以动态地为该业务类添加接口的实例逻辑,让业务类成为这个接口的实现类。
  • 织入Weaving
  • 织入是将增强添加对目标类具体连接点上的过程。
  • 根据不同的实现技术,AOP有三种织入方式:
  • 编译期织入,这要求使用特殊的JAVA编译器
  • 类装载期织入,这要求使用特殊的类加载器;
  • 动态代理织入,在运行期为目标类添加增强生成的方式。
  • 代理Proxy
  • 一个类被AOP增强后,就产出了一个结果类,它是整合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类。所以我们可以采用调用原类相同的方式调用代理类。
  • 切面Aspect
  • 切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SPRINGAOP就是负责实施切面的框架,它将切面所定义的横切逻辑到切面所指定的连接点中。

(6)通知的类型(5种)

  • before:前置通知(应用:各种校验)
  • 在方法执行前执行,如果通知抛出异常,阻止方法运行
  • afterReturning:后置通知(应用:常规数据处理)
  • 方法正常返回后执行,如果方法中抛出异常,通知无法执行
  • 必须在方法执行后才执行,所以可以获得方法的返回值。
  • around:环绕通知(应用:十分强大,可以做任何事情)
  • 方法执行前后分别执行,可以阻止方法的执行
  • 必须手动执行目标方法
  • afterThrowing:抛出异常通知(应用:包装异常信息)
  • 方法抛出异常后执行,如果方法没有抛出异常,无法执行
  • after:最终通知(应用:清理现场)
  • 方法执行完毕后执行,无论方法中是否出现异常

8.2.AOP编程XML方式声明

(1)XML配置文件的方式声明切面

  • 添加maven依赖
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.15.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>
  • 写业务类(不写注解)
public class UserService {
    public String login(String name){
        System.out.println(name+":用户登录--login()...");
        return name;
    }
}
  • 写切面类
public class TransactionPoint {
    //增强部分
    public void before(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println("参数:"+arg);
        }
        System.out.println("前置增强");
    }
    public void afterReturning(){
        System.out.println("后置增强");
    }
    public void after(){
        System.out.println("最终增强");
    }
    public void afterThrowimg(){
        System.out.println("例外增强");
    }
    /*
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        try{
            System.out.println("环绕增强开始");
            proceed = pjp.proceed();
            System.out.println("环绕增强结束");
        }catch (Exception e){
            System.out.println("环绕例外增强");
             e.printStackTrace();
        }finally {
            System.out.println("环绕最终增强");
        }
        return proceed;
    }*/
}
  • applicationContext.xml配置bean和切面
<bean id="userService" class = "com.tjetc.service.UserService"></bean>
        <bean id="transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean>
        <!-- aop配置 -->
        <aop:config>
                <!--配置切面  -->
                <aop:aspect id="myaspect" ref="transactionPoint">
                        <!-- 切点 -->
                        <aop:pointcut expression="execution(* com.tjetc.service..*.*(..))" id="mycut"/>
                        <!-- 增强 -->
                        <aop:before method="before" pointcut-ref="mycut"/>
                        <aop:after-returning method="afterReturning" pointcut-ref="mycut"/>
                        <aop:after method="after" pointcut-ref="mycut"/>
                        <aop:after-throwing method="afterThrowimg" pointcut-ref="mycut"/>
                </aop:aspect>
        </aop:config>
  • 测试代码
 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean(UserService.class);
        userService.login("LiXiang");
  • 运行结果

参数:LiXiang 前置增强 LiXiang:用户登录--login()... 后置增强 最终增强

(2)配置切面

1.切面的普通类:<bean id = "transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean>
2.<aop:config>子节点<aop:aspect id = "myaspect" ref = "personAspect">启动和增强</aop:aspect>

(3)配置切点

1.切点配置的位置
(1)aop:config下,aop:config下所有的切面都能使用该切面
(2)aop:aspect下,只能是该切面能使用该切点
2.<aop:pointcut expression=”execution(* com.tjetc.service..*.*(..))” id=”mycut”>

(4)配置增强

1.增强的配置位置
  aop:aspect下
2.增强5个
    (1)前置增强
    <aop:before method=”before” pointcut-ref=”mycut”>
    (2)后置增强
    <aop:after-returning method=”afterReturning” pointcut-ref=”mycut”>
    (3)例外增强
    <aop:after-throwing method=”afterThrowing” pointcut-ref=”mycut”>
    (4)最终增强
    <aop:after method=”after” pointcut-ref=”mycut”>
    (5)环绕增强
    <aop:around method=”around” pointcut-ref=”mycut”>

(5)后置增强的返回值

  • 第一步:在配置文件的aop:after-returning添加属性returning=”变量名”
  • 第二步:在切面类的afterReturning(Object 变量名)方法添加参数Object 变量名
  • 第三步:测试调用有返回值的方法
 <aop:after-returning method="afterReturning" pointcut-ref="mycut" returning="res"/>
public void afterReturning(JoinPoint jp,Object res) {
    System.out.println("后置通知:res="+res);
  }

(6)异常增强得到异常对象

  • 第一步:在applicationContext.xml配置aop:after-throwing的属性throwing=”ex”
  • 第二步:在切面类的afterThrowing(Exception ex)
  • 第三步:在业务类的login()方法抛出异常
  • 第四步:测试调用login()方法
<aop:after-throwing method="afterThrowing" pointcut-ref="mycut" throwing="ex"/>
public void afterThrowing(Exception ex) {
    System.out.println("例外通知:ex="+ex);
  }

(7)在增强里接受参数

  • 使用JoinPoint接口的getArgs()接受参数
  • 第一步:直接在切面类的增强方法里写JoinPoint jp参数
  • 第二步:在方法体写jp.getArgs();得到数组,遍历数组得到每一个参数的值输出.
public void before(JoinPoint jp) {
    Object[] args = jp.getArgs();
    for (Object object : args) {
      System.out.println("接收参数:"+object);
    }
    System.out.println("前置通知");
  }

8.3.AOP编程注解方式声明

(1)AOP注解方式编程

@AspectJ是一种风格样式,可以把普通的java类声明为一个切面

  • applicationContext.xml中添加AOP命名空间(1+2)
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop 
https://www.springframework.org/schema/aop/spring-aop.xsd
  • 启动@Aspect注解支持
<aop:aspectj-autoproxy/>
  • 启动注解扫包描机制
<context:component-scan base-package="com.tjetc"/>
  • 添加aspectjweaver的maven依赖
<dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
</dependency>
  • 写切面类
@Component
@Aspect
public class TransactionPoint {
    //切点
    @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
    private void anyMethod(){}  //方法签名返回void类型
    //增强部分
    @Before("anyMethod()")
    public void before(){
        System.out.println("前置增强");
    }
    @AfterReturning("anyMethod()")
    public void afterReturning(){
        System.out.println("后置增强");
    }
    @After("anyMethod()")
    public void after(){
        System.out.println("最终增强");
    }
    @AfterThrowing("anyMethod()")
    public void afterThrowimg(){
        System.out.println("例外增强");
    }
    @Around("anyMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知开始");
        Object proceed = pjp.proceed();
        System.out.println("环绕通知结束");
        return proceed;
    }
}
  • 写业务类
@Service
public class UserService {
    public void login(){
        System.out.println("用户登录--login()...");
    }
}
  • 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);
userService.login();
  • 运行结果

环绕通知开始 前置增强 用户登录--login()... 后置增强 最终增强 环绕通知结束

(2)测试异常

  • 将UserService中加入除0异常
@Service
public class UserService {
    public void login(){
        System.out.println("用户登录--login()...");
        System.out.println(1/0);
    }
}
  • 测试运行

前置增强 用户登录--login()... 例外增强 最终增强 Exception in thread "main" java.lang.ArithmeticException: / by zero

没有走后置增强,直接走的例外增强。

(3)环绕增强单独使用

  • 切点类中只写around增强
@Component
@Aspect
public class TransactionPoint {
    //切点
    @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
    private void anyMethod(){}  //方法签名返回void类型
    @Around("anyMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        try{
            System.out.println("环绕增强开始");
            proceed = pjp.proceed();
            System.out.println("环绕增强结束");
        }catch (Exception e){
            System.out.println("环绕例外增强");
             e.printStackTrace();
        }finally {
            System.out.println("环绕最终增强");
        }
        return proceed;
    }
}
  • 测试运行

环绕增强开始 用户登录--login()... 环绕增强结束 环绕最终增强

(4)联合使用pointcut表达式

在一个切面类中可以声明多个切点表达式,把几个切点签名用&& || !连起来使用

@Component
@Aspect//切面类=切点+增强
public class TransactionPrint {
    //切点
  @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
  private void anyMethod1(){} //方法签名,返回值void
  @Pointcut("execution(* com.tjetc.dao..*.*(..))") //定位到连接点的条件
  private void anyMethod2(){} //方法签名,返回值void
  @Pointcut("anyMethod1() || anyMethod2()") //定位到连接点的条件
  private void anyMethod(){} //方法签名,返回值void

(5)声明Advice

本类的方法直接写方法名()

  @Before("anyMethod()")
  public void before() {
    System.out.println("前置增强");
  }

非本类的方法写类的全路径名.方法名() (确保方法签名是public能访问的;否则报错)

@Component
@Aspect
public class Transcation2 {
  @Before("com.tjetc.aspect.TransactionPrint.anyMethod()")
  public void before() {
    System.out.println("前置增强2");
  }
}

(6)@AfterReturning返回值

第一步:在@AfterReturning添加returning属性retruning=”方法参数的名称”

第二步:在方法里写一个参数,名称是returning属性的值,接收返回值

   @AfterReturning(value = "anyMethod()",returning = "result")
    public void afterReturning(Object result){
        System.out.println("返回值:"+result);
        System.out.println("后置增强");
    }

(7)@AfterThrowing异常

当异常发生时异常通知如何得到异常信息?

实现步骤:

第一步:在@AfterThrowing添加属性throwing=”方法参数的名称”

第二步:在方法里写一个参数,名称是throwing属性的值,接收异常对象

@AfterThrowing(value="anyMethod()",throwing="ex")
  public void afterThrowing(Exception ex) {
    System.out.println("异常通知,ex="+ex);
  }

(8)在增强里接收参数

使用JoinPoint接口的getArgs()接收参数

    @Before("anyMethod()")
    public void before(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println("参数:"+arg);
        }
        System.out.println("前置增强");
    }

8.4.JDK动态代理

*(1)JDK动态代理是java.lang.reflect.包提供的方式,它必须借助一个接口才能产生代理对象,所以要先定义接口,代码如下:

public interface HelloWord {
    void sayHelloWord();
}

(2)然后提供HelloWord的实现类

public class HelloWordImpl implements HelloWord {
    @Override
    public void sayHelloWord() {
        System.out.println("Hello Word");
    }
}

(3)这时就可以进行JDK动态代理了,先建立起代理对象和真实服务对象的关系,然后实现代理逻辑,代理类要实现java.lang.reflect.InvocationHandler接口,重写invoke()方法

public class JDKProxyExample implements InvocationHandler {
    //真实对象
    private Object target = null;
    /**
     *建立代理对象和真实对象的代理关系,并返回代理对象
     * @Param target 真实对象
     * @return 代理对象
     */
    public Object bind(Object target){
        this.target = target;
        return      Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    /**
     * 代理方法逻辑
     * @param proxy 代理对象
     * @param method 代理方法对象
     * @param args 当前方法参数
     * @return 代理结果返回
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置增强");
        //参数一:真实对象  参数二: 方法参数
        Object invoke = method.invoke(target, args);
        System.out.println("后置增强");
        return invoke;
    }
}

**第1步,建立代理对象和真实对象的关系.**这里是使用了bind方法创建代理对象,方法里面首先用类的属性target保存了真实对象,然后通过如下代码建立并生成了代理对象.

Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);

其中newProxyInstance方法的三个参数:

第1个:类加载器,我们采用了target本身的类加载器.

第2个:把生成的动态代理对象下挂在哪些接口下,这个写法就是放在target实现接口下.

第3个:定义实现方法逻辑的代理类.

**第2步,实现逻辑方法.**invoke方法可以实现代理逻辑.

invoke(Object proxy, Method method, Object[] args)

invoke方法的三个参数含义如下:

proxy,代理对象,就是bind方法生成的对象

method:代理方法对象

args:调度方法的参数

当我们使用了代理对象调度方法后,他就会进入到invoke方法里面

Object invoke = method.invoke(target, args);

这行代码相当于调用真实的对象方法,只是通过反射实现而已.

第3步,测试JDK动态代理

public static void main(String[] args) {
    JDKProxyExample jdk = new JDKProxyExample();
    //绑定关系,因为挂在接口上的实现类,所以声明代理对象HelloWord proxy
    HelloWord bind = (HelloWord) jdk.bind(new HelloWordImpl());
    //此时bind是一个代理对象,调用代理方法
    bind.sayHelloWord();
}

运行结果:

前置增强 Hello Word 后置增强

此时,在调度打印Hello Word之前和之后都可以加入相关的逻辑,甚至可以步调度Hello Word的打印.

8.5.CGLIB动态代理

JDK动态代理必须提供接口才嫩不过使用,在一些不能提供接口的环境中,只能采用其他的第三方方技术,比如CGLIB动态代理。它的优势在于不需要提供接口,只需要一个非抽象类就可以实现动态代理。

(1)我们还是用HelloWordImpl这个实现类,这次不需要接口

public class HelloWordImpl {
    public void sayHelloWord() {
        System.out.println("Hello Word");
    }
}

(2)采用CGLIB动态代理技术,实现MethodInterceptor接口

public class CGLIBProxyExample implements MethodInterceptor {
      //真实类对象
        private HelloWordImpl target = null;
        public CGLIBProxyExample() {
            super();
            // TODO Auto-generated constructor stub
        }
        public CGLIBProxyExample(HelloWordImpl target) {
            super();
            this.target = target;
        }
        /**
         * 生成CGLIB代理对象
         */
        public HelloWordImpl bind(){
            Enhancer enhancer = new Enhancer();
            //指定父类,即目标类。 因为cglib原理 子类增强父类,参数为真实类的class对象
            enhancer.setSuperclass(HelloWordImpl.class);
            //设置回掉接口.参数为代理类对象
            enhancer.setCallback(this);
            //生成并返回代理对象
            return (HelloWordImpl)enhancer.create();
        }
        /**
         * @param proxy 代理对象
         * @param method 方法
         * @param args 方法参数
         * @param methodProxy 方法代理
         * return 代理逻辑返回
         * @throws Throwable 异常
         */
        @Override
        public Object intercept(Object proxy, Method method, Object[] args,
                                MethodProxy methodProxy) throws Throwable {
            System.out.println("前置增强");
            Object o=methodProxy.invokeSuper(proxy, args);
            System.out.println("后置增强");
            return o;
        }
    }
}

这里面用了CGLIB的加强者Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置那个类为它的代理类。最后调用create()方法。

(3)测试CGLIB动态代理

  public static void main(String[] args) {
    HelloWordImpl hello = new HelloWordImpl();
    HelloWordImpl h = new CGLIBProxyExample(hello).bind();
    System.out.println(h);
    h.sayHelloWord();
  }

运行结果:

前置增强 Hello Word 后置增强

掌握了JDK动态代理就很容易掌握CGLIB动态代理,因为二者是相似的。他们都是用getProxy方法生成代理对象的,制定代理的逻辑类。而二者的区别就在于一个要实现接口,一个不需要实现接口。

9.Spring事务管理

9.1.事务的分类

  • 本地事务:local transaction 使用单一资源管理器,管理本地资源。
  • 全局事务:global transaction 通过事务管理和多种资源管理器,管理多种不同的资源。
  • 编程式事务:通过编码方式,开启事务、提交事务、回滚事务。
  • 声明性事务:通过xml配置或注解,实现事务管理,Spring AOP 和 EJB都是声明性事务。
  • JTA事务:Java Transaction API ,使用javax.transaction.UserTransaction接口,访问多种资源管理器。
  • CMT事务:Container Management transaction ,通过容器自动控制事务的开启,提交和回滚。

事务策略接口:PlatformTransactionManager

事务状态接口:TransactionStatus

事务定义接口:TransactionDefinition

9.2.Spring声明性事务

XML管理声明性事务

(1)pom.xml中添加依赖

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

(2)配置文件db.properties配置数据源

jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc.mysql:///cc
jdbc.username = root
jdbc.password = 123456

(3)applicationContext.xml文件

<!--配置基本扫描包-->
<context:component-scan base-package = "com.tjetc"></context:component-scan>
<!--加载db.properties获取四大金刚的值-->
<context:properties-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean id = "dataSource" class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name = "driverClassName" value = "${jdbc.driver}"></property>
    <property name = "url" value = "${jdbc.url}"></property>
    <property name = "username" value = "${jdbc.username}"></property>
    <property name = "password" value = "${jdbc.password}"></property>
</bean>
<!--配置数据源事务管理器-->
<bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name ="dataSource" ref = "dataSource"></property>
</bean>
<!--配置事务增强:tx:advice对目标的哪些方法使用事务增强-->
<tx:advice>
    <tx:attributes>
      <tx:method name = "add*" propagation = "REQUIRED" rollback-for = "Throwable"/>
        <tx:method name = "update*" propagation = "REQUIRED" rollback-for = "Throwable"/>
        <tx:method name = "del*" propagation = "REQUIRED" rollback-for = "Throwable"/>
        <!--*代表除了上面方法之外的其他方法-->
        <tx:method name = "*" propagation = "REQUIRED" read-only = "true"/>
    </tx:attributes>
</tx:advice>

tx:method属性

属性 是否需要? 默认值 描述
name 事务属性关联的方法名,通配符(*)可以用来指定一批关联到相同的事务属性的方法。
propagation REQUIRED 事务传播行为
isolation DEFAULT 事务隔离级别
timeout -1 事务超时的时间(以秒为单位)
read-only false 事务是否只读?
rollback-for 将触发进行回滚的Exception(s)
no-rollback-for 不被触发进行回滚的Exception(s)

(4)Student实体类

public class Student {
    private int id;
    private String name;
    private int age;
}

(5)dao层

@Repository
public class StudentDao extends JdbcDaoSupport {
    @Resource
    private JdbcTemplate jdbcTemplate;
    public boolean add(Student student){
        String sql = "insert into student(name,age) values(?,?)";
        int i = jdbcTemplate.update(sql, student.getName(), student.getAge());
        System.out.println("受影响的行数:"+i);
        return i>0;
    }
}

(6)service层

@Service
public class StudentService {
    @Autowired
    private StudentDao studentDao;
    public void add(Student student){
        studentDao.add(student);
        System.out.println(1/0);
        studentDao.add(student);
    }
}

(7)测试代码

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    StudentService studentService = context.getBean(StudentService.class);
    studentService.add(new Student("张三",18));
}
注解方式管理声明式事务

(1)pom.xml、db.properties配置文件同上

(2)applicationContext.xml文件配置tx:annotation-driven注解

 <!-- 配置扫描包 -->
    <context:component-scan base-package="com.tjetc"></context:component-scan>
    <!-- 加载从db.properties取得4大金刚的值 -->
    <context:property-placeholder location="classpath:db.properties"/>
    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <property name="driverClassName" value="${jdbc.driver}"></property>
       <property name="url" value="${jdbc.url}"></property>
       <property name="username" value="${jdbc.username}"></property>
       <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!-- 配置数据源事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"></property>
    </bean>
   <!--tx:annotation-driven 代表可以采用@Transactional注解方式使用事务-->
<tx:annotation-driven transaction-manager = "txManager"/>

(3)业务层的类上或者方法上写@Transactional注解

  • 写在类上代表类的所有方法都是用事务
  • 写在方法上值堆该方法使用事务
@Service
// @Transactional(rollbackFor=Throwable.class)//写在类上代表类的所有方法都使用事务
public class StudentService {
  @Autowired
  private StudentDao studentDao;
     @Transactional(rollbackFor=Throwable.class)//写在方法只对该方法使用事务
  public void add(Student student) {
      studentDao.add(student);
    System.out.println(1/0);
      studentDao.add(student);
    throw new ArrayIndexOutOfBoundsException("异常回滚测试...");
  }
}

(4)测试代码

public static void main(String[] args) {
    //实例化容器对象
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      //从容器得到bean
      StudentService studentService = context.getBean(StudentService.class);
      //调用方法
      studentService.add(new Student("张三", 20));
  }

9.3.事务传播特性

  • PROPAGATION_REQUIRED , required , 必须 【默认值】
  • 支持当前事务,A如果有事务,B将使用该事务。
  • 如果A没有事务,B将创建一个新的事务。
  • PROPAGATION_SUPPORTS ,supports ,支持
  • 支持当前事务,A如果有事务,B将使用该事务。
  • 如果A没有事务,B将以非事务执行。
  • PROPAGATION_MANDATORY,mandatory ,强制
  • 支持当前事务,A如果有事务,B将使用该事务。
  • 如果A没有事务,B将抛异常。
  • PROPAGATION_REQUIRES_NEW , requires_new ,必须新的
  • 如果A有事务,将A的事务挂起,B创建一个新的事务
  • 如果A没有事务,B创建一个新的事务
  • PROPAGATION_NOT_SUPPORTED ,not_supported ,不支持
  • 如果A有事务,将A的事务挂起,B将以非事务执行
  • 如果A没有事务,B将以非事务执行
  • PROPAGATION_NEVER ,never,从不
  • 如果A有事务,B将抛异常
  • 如果A没有事务,B将以非事务执行
  • PROPAGATION_NESTED ,nested ,嵌套
  • A和B底层采用保存点机制,形成嵌套事务。
  • 如果当前存在事务,则在嵌套事务内执行。
  • 如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    @Autowired
    private StudentDao studentDao;
    @Transactional(rollbackFor=Throwable.class)
  public void methodA() {
    System.out.println("methodA()...");
    studentDao.add(new Student("李四", 21));
    serviceB.methodB();
  }
}
@Service
public class ServiceB {
  @Autowired
  private StudentDao studentDao;
  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
  public void methodB() {
    System.out.println("methodB()...");
    studentDao.add(new Student("赵六", 22));
  }
}

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

9.4.事务的四个关键属性

  • 原子性:事务时一个原子操作,有一系列动作完成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性:一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
  • 隔离性:可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏。
  • 持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写到持久化存储器中

9.5.事务隔离级别

  • Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
  • Read Commited:读已提交数据(会出现不可重复读和幻读)
  • Repeatable Read:可重复读(会出现幻读)
  • Serializable:串行化


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
XML 安全 Java
|
2月前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
55 0
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
10天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
5天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
31 13
|
17天前
|
IDE Java 测试技术
互联网应用主流框架整合之Spring Boot开发
通过本文的介绍,我们详细探讨了Spring Boot开发的核心概念和实践方法,包括项目结构、数据访问层、服务层、控制层、配置管理、单元测试以及部署与运行。Spring Boot通过简化配置和强大的生态系统,使得互联网应用的开发更加高效和可靠。希望本文能够帮助开发者快速掌握Spring Boot,并在实际项目中灵活应用。
34 5
|
27天前
|
缓存 Java 数据库连接
Spring框架中的事件机制:深入理解与实践
Spring框架是一个广泛使用的Java企业级应用框架,提供了依赖注入、面向切面编程(AOP)、事务管理、Web应用程序开发等一系列功能。在Spring框架中,事件机制是一种重要的通信方式,它允许不同组件之间进行松耦合的通信,提高了应用程序的可维护性和可扩展性。本文将深入探讨Spring框架中的事件机制,包括不同类型的事件、底层原理、应用实践以及优缺点。
63 8
|
2月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
81 6
|
2月前
|
Java 数据库连接 数据库
不可不知道的Spring 框架七大模块
Spring框架是一个全面的Java企业级应用开发框架,其核心容器模块为其他模块提供基础支持,包括Beans、Core、Context和SpEL四大子模块;数据访问及集成模块支持数据库操作,涵盖JDBC、ORM、OXM、JMS和Transactions;Web模块则专注于Web应用,提供Servlet、WebSocket等功能;此外,还包括AOP、Aspects、Instrumentation、Messaging和Test等辅助模块,共同构建强大的企业级应用解决方案。
104 2
|
3月前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
97 1
Spring 框架:Java 开发者的春天