Spring框架学习第二部分

前言
  🎄:CSDN的小伙伴们大家好,今天继续跟大家分享spring框架的一些操作。如果这篇文章对你有用,麻烦给我点个小赞以示鼓励吧🎄
  🏡:博客主页:空山新雨后的java知识图书馆
  ☔️:晚上下雨了,天气转凉。
  📝:所见锐明当自信,不可因人所说如何而易吾之自信。——薛敬轩📝
  📖上一篇文章:spring框架学习第一部分📖
  👏欢迎大家一起学习,进步。加油👊



一、Spring Aop简介

1.1、AOP的概念

  之前在介绍spring框架的时候就提到过,spring由七部分组成,其中有一个就叫spring AOP,那么具体什么叫做spring AOP呢?
  AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
  AOP 是 OOP(面向对象程序设计) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1.2、AOP的作用及优势

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

优势:减少重复代码,提高开发效率,并且便于维护

1.3、AOP的底层实现原理

  实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

1.4、什么是AOP的动态代理技术?

  常用的动态代理技术分为两种
  JDK 代理 : 基于接口的动态代理技术
  cglib 代理:基于父类的动态代理技术
原理如下图在这里插入图片描述
接下来我将演示原始的jdk动态代理和cglib动态代理技术怎么实现

  1.4.1、jdk动态代理技术

演示创建一个目标接口,因为jdk动态代理技术是基于目标接口的,所以创建接口

package com.study.proxy.jdk;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk
 * @className TargetInterface
 * @date 2022/4/27 9:26
 * @Description 目标接口
 */
public interface TargetInterface {
    public void save();

}

创建一个目标接口的实现

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk.targetImpl
 * @className TartgetImpl
 * @date 2022/4/27 9:27
 * @Description 目标接口实现类
 */
public class TargetImpl implements TargetInterface {

    public void save() {
        System.out.println("save 方法别执行");
    }
}

然后创建一个增强类

package com.study.proxy.jdk;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk
 * @className Enhance
 * @date 2022/4/27 9:28
 * @Description 增强类
 */
public class Enhance {
    public void beforeEnhance() {
        //前置增强
        System.out.println("前置增强方法");
    }
    public void afterEnhance() {
        //后置增强
        System.out.println("后置增强方法");
    }
}

测试

package com.study.proxy.jdk;

import com.study.proxy.jdk.targetImpl.TargetImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk
 * @className TargetProxyTest
 * @date 2022/4/27 9:31
 * @Description 代理对象增强测试,该类是之前的基于jdk的动态代理对象的方法
 */
public class TargetProxyTest {
    public static void main(String[] args) {
        //目标对象
        final TargetImpl target = new TargetImpl();

        //增强对象
        final Enhance enhance = new Enhance();

        //返回值即是动态生成的对象
        TargetInterface targetInterface = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//目标对象类加载器
                target.getClass().getInterfaces(),//目标对象相同的接口的字节码对象数组
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        enhance.beforeEnhance();//前置增强
                        Object invoke = method.invoke(target, args);//执行目标方法
                        enhance.afterEnhance();//后置增强
                        return invoke;
                    }
                }
        );
        //      调用代理对象的方法
        targetInterface.save();
    }
}

输出这个

前置增强方法
save 方法别执行
后置增强方法

那么我们的代理也就完成了。

  1.4.2、cjlib的动态代理技术

  cjlib是不依赖接口的,那么就使用一个目标类,和一个增强类即可。
  目标类和增强类同jdk里面的,只是不用实现接口。
测试

package com.study.proxy.cglib;

import com.study.proxy.jdk.TargetInterface;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk
 * @className TargetProxyTest
 * @date 2022/4/27 9:31
 * @Description 代理对象增强测试,该类是之前的基于jdk的动态代理对象的方法
 */
public class TargetProxyTest {
    public static void main(String[] args) {
        //目标对象
        final Target target = new Target();

        //增强对象
        final Enhance enhance = new Enhance();

        //创建增强器
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(Target.class);

        //设置回调
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //前置增强

                enhance.beforeEnhance();
                //执行目标
                Object invoke = method.invoke(target, args);
                enhance.afterEnhance();
                //后置增强


                return null;
            }
        });

        //创建代理对象,现在用target接收是因为目标对象和代理对象是父子关系
        Target proxy = (Target) enhancer.create();
        proxy.save();
    }
}

  以上两种技术能看懂即可,不用掌握会写,因为spring框架会自动帮我们完成增强,我们只需要会配置spring的参数就可以了。

  1.4.3、AOP封装的动态代理技术的相关术语

  Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
相关术语:

Target(目标对象):代理的目标对象
Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
Aspect(切面):是切入点和通知(引介)的结合
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

  1.4.4、AOP开发需要明确的事项

1、需要编写的内容

  • 编写核心业务代码(目标类的目标方法)
  • 编写切面类,切面类中有通知(增强功能方法)
  • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合

2、AOP技术实现的内容
  Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
3、AOP底层使用那种代理方式
  在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。实现了接口,采用jdk方式,未实现接口,就采用cjlib方式。

二、springAOP的开发流程介绍

2.1、基于xml的AOP开发

  2.1.1、基于xml的AOP开发的步骤演示

那么基于xml的AOP的开发主要是在以下几个步骤
①导入 AOP 相关坐标
首先在maven项目中导入坐标

<!--导入spring的context坐标,context依赖aop-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

②创建目标接口和目标类(内部有切点)
接口

package com.study.aop;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk
 * @className TargetInterface
 * @date 2022/4/27 9:26
 * @Description 目标接口
 */
public interface TargetInterface {
    public void save();

}

目标类

package com.study.aop;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk.targetImpl
 * @className TartgetImpl
 * @date 2022/4/27 9:27
 * @Description 目标接口实现类
 */
public class TargetImpl implements TargetInterface {

    public void save() {
        System.out.println("save 方法被执行");
    }
}

切点就是save方法

③创建切面类(内部有增强方法)

package com.study.aop;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.aop
 * @className MyAspect
 * @date 2022/4/27 11:07
 * @Description 切面类
 */
public class MyAspect {
    public void before() {
        System.out.println("Spring aop前置增强");
    }
}

主要就是将增强方法写进去

④将目标类和切面类的对象创建权交给 spring
在applicationContext.xml文件中进行配置

 <!--配置目标到spring容器当中-->
    <bean id="target" class="com.study.aop.TargetImpl"></bean>

    <!--配置切面类-->
    <bean id="myAspect" class="com.study.aop.MyAspect"></bean>

⑤在 applicationContext.xml 中配置织入关系
那么首先需要导入AOP和context的命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置切面织入关系,告诉spring,哪些方法(切点)需要进行哪些增强-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面,切点+通知,就是将切点的save方法进行before前置增强,这里的方法书写格式为修饰+返回值+方法的全限定名-->
            <aop:before method="before" pointcut="execution(public void com.study.aop.TargetImpl.save())"></aop:before>
        </aop:aspect>

    </aop:config>

⑥测试代码

package com.study.aop;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.aop
 * @className test
 * @date 2022/4/27 11:15
 * @Description 测试spring进行增强的方法
 */
//spring测试替换原来的运行周期
@RunWith(SpringJUnit4ClassRunner.class)
//这个是用来找到spring配置文件的位置的,根路径下
@ContextConfiguration("classpath:applicationContext.xml")
public class test {
    //注入切点对象
    @Autowired
    private TargetInterface target;


    /**
     * @Date  2022/4/27 11:18
     * @Param
     * @Return void
     * @MetodName test1
     * @Author wang
     * @Description 测试增强是否成功
     */
    @Test
    public void test1() {
        target.save();
    }
}

输出结果:
Spring aop前置增强
save 方法被执行

  2.1.2、相关参数及术语的解释

1、切点表达式

切点表达式的写法
表达式语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))

表达式特点

  • 访问修饰符可以省略

  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意

  • 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类

  • 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
    实例
    execution(public void com.study.aop.Target.method())
    限定修饰符,范围值为void,该包下的,Target类中的method方法
    execution(void com.study.aop.Target.*(..))
    省略修饰符,该类下的任意方法,参数任意
    execution(* com.study.aop.*.*(..))
    这个比较常用,任意返回值,某个包下任意的类,任意方法,任意参数
    execution(* com.study.aop..*.*(..))
    这个与上一个的区别在于某个包及其子包下的任意类,任意方法,任意参数
    execution(* *..*.*(..))
    啥都是任意的

切点表达式的抽取
  当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.study.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>

2、通知的类型

  通知就是告诉spring,我的某个切点,在程序运行过程中执行某个切面的某个通知类型的方法。
通知的配置语法
<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>

通知类型的分类

名称标签说明
前置通知<aop:before>用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知<aop:after-returning> 用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知<aop:around>用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知<aop:throwing>用于配置异常抛出通知,指定增强的方法在出现异常时执行
最终通知<aop:after> 用于配置最终通知,无论增强方法执行是否有异常都会执行

代码演示

目标接口类

package com.study.aop;
/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk
 * @className TargetInterface
 * @date 2022/4/27 9:26
 * @Description 目标接口
 */
public interface TargetInterface {
    public void save();

}

目标类

package com.study.aop;
/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk.targetImpl
 * @className TartgetImpl
 * @date 2022/4/27 9:27
 * @Description 目标接口实现类
 */
public class TargetImpl implements TargetInterface {

    public void save() {
        System.out.println("save 方法被执行");
        /*模拟异常,这个模拟异常操作在进行到异常通知的时候,打开,其他时候模拟建议关闭*/
        int i = 1/0;
    }
}

测试类

package com.study.aop;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
 * @author wang
 * @version 1.0
 * @packageName com.study.aop
 * @className test
 * @date 2022/4/27 11:15
 * @Description 测试spring进行增强的方法
 */
//spring测试替换原来的运行周期
@RunWith(SpringJUnit4ClassRunner.class)
//这个是用来找到spring配置文件的位置的,根路径下
@ContextConfiguration("classpath:applicationContext.xml")
public class test {
    //注入切点对象
    @Autowired
    private TargetInterface target;
    /**
     * @Date  2022/4/27 11:18
     * @Param
     * @Return void
     * @MetodName test1
     * @Author wang
     * @Description 测试增强是否成功
     */
    @Test
    public void test1() {
        target.save();
    }
}

前置通知
代码

package com.study.aop;
import org.aspectj.lang.ProceedingJoinPoint;
/**
 * @author wang
 * @version 1.0
 * @
packageName com.study.aop
 * @className MyAspect
 * @date 2022/4/27 11:07
 * @Description 切面类
 */
public class MyAspect {
    public void before() {
        System.out.println("Spring aop前置增强");
        /*效果:Spring aop前置增强
               save 方法被执行*/
    }

配置

 <aop:before method="before" pointcut="execution( * com.study.aop.*.*(..))"></aop:before>

后置通知
代码

	public void afterRunning() {
    System.out.println("spring AOP 后置增强");
    /*效果:Spring aop前置增强
           save 方法被执行
           spring AOP 后置增强*/
}

配置

  <!--后置增强-->
        <aop:after-returning method="afterRunning"
                             pointcut="execution(* com.study.aop.*.*(..))"></aop:after-returning>

环绕通知
代码

 /**
 *
 * @param pjp 传入一个切点对象,这个对象就是要执行的切点。
 * @return
 * @throws Throwable
 */
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("spring AOP 环绕前增强");
    //切点执行方法
    Object proceed = pjp.proceed();
    System.out.println("spring AOP 环绕后增强");
    return proceed;

    /*效果:Spring aop前置增强
            spring AOP 环绕前增强
            save 方法被执行
            spring AOP 环绕后增强
            spring AOP 后置增强*/
}

配置

<!--环绕增强-->
            <aop:around method="around" pointcut="execution(* com.study.aop.*.*(..))"></aop:around>

异常抛出通知
代码

 public void throwException() {
    System.out.println("spring AOP 抛出异常增强");
    /*效果:
    * Spring aop前置增强
        spring AOP 环绕前增强
        save 方法被执行
        spring AOP 抛出异常增强*/
}

配置

 <!--异常增强-->
            <aop:after-throwing method="throwException"
                                pointcut="execution(* com.study.aop.*.*(..))"></aop:after-throwing>

最终通知
代码

public void after() {
        System.out.println("spring AOP 最终增强");
        /*效果:
        * Spring aop前置增强
            spring AOP 环绕前增强
            save 方法被执行
            spring AOP 最终增强
            spring AOP 抛出异常增强*/
    }

配置

 <!--最终增强-->
            <aop:after method="after" pointcut="execution(* com.study.aop.*.*(..))"></aop:after>

2.2、基于注解的AOP的开发

  2.2.1、基于注解的AOP开发的步骤

①创建目标接口和目标类(内部有切点)
接口类

package com.study.anno;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk
 * @className TargetInterface
 * @date 2022/4/27 9:26
 * @Description 目标接口
 */
public interface TargetInterface {
    public void save();

}

目标类

package com.study.anno;

import org.springframework.stereotype.Component;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.proxy.jdk.targetImpl
 * @className TartgetImpl
 * @date 2022/4/27 9:27
 * @Description 目标接口实现类
 */
@Component("target")
public class TargetImpl implements TargetInterface {

    public void save() {
        System.out.println("save 方法被执行");
        /*模拟异常,这个模拟异常操作在进行到异常通知的时候,打开,其他时候模拟建议关闭*/
//        int i = 1/0;
    }
}

②创建切面类(内部有增强方法)

package com.study.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.aop
 * @className MyAspect
 * @date 2022/4/27 11:07
 * @Description 切面类
 */

@Component("myAspect")
@Aspect//配置标志他是切面类在spring容器中
public class MyAspect {

    /*配置前置通知*/
    @Before("execution(* com.study.anno.*.*(..))")
    public void before() {
        System.out.println("Spring aop前置增强");
        /*效果:Spring aop前置增强
               save 方法被执行*/
    }

    public void afterRunning() {
        System.out.println("spring AOP 后置增强");
        /*效果:Spring aop前置增强
               save 方法被执行
               spring AOP 后置增强*/
    }

    /**
     *
     * @param pjp 传入一个切点对象,这个对象就是要执行的切点。
     * @return
     * @throws Throwable
     */
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("spring AOP 环绕前增强");
        //切点执行方法
        Object proceed = pjp.proceed();
        System.out.println("spring AOP 环绕后增强");
        return proceed;

        /*效果:Spring aop前置增强
                spring AOP 环绕前增强
                save 方法被执行
                spring AOP 环绕后增强
                spring AOP 后置增强*/
    }

    public void throwException() {
        System.out.println("spring AOP 抛出异常增强");
        /*效果:
        * Spring aop前置增强
            spring AOP 环绕前增强
            save 方法被执行
            spring AOP 抛出异常增强*/
    }

    public void after() {
        System.out.println("spring AOP 最终增强");
        /*效果:
        * Spring aop前置增强
            spring AOP 环绕前增强
            save 方法被执行
            spring AOP 最终增强
            spring AOP 抛出异常增强*/
    }
}

③将目标类和切面类的对象创建权交给 spring

@Component("target")
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}
@Component("myAspect")
public class MyAspect {
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

④在切面类中使用注解配置织入关系


    /*配置前置通知织入关系*/
    @Before("execution(* com.study.anno.*.*(..))")
    public void before() {
        System.out.println("Spring aop前置增强");
        /*效果:Spring aop前置增强
               save 方法被执行*/
    }

⑤在配置文件中开启组件扫描和 AOP 的自动代理

  <!--配置文件扫描-->
    <context:component-scan base-package="com.study.anno"></context:component-scan>

    <!--要想增强实现,必须添加这个标签 AOP自动代理标签-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

⑥测试

package com.study.anno;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.anno
 * @className test
 * @date 2022/4/27 16:30
 * @Description 测试spring进行增强的方法,通过注解的方式
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_anno.xml")
public class test {
    //注入切点对象
    @Autowired
    private TargetInterface target;

    //测试
    @Test
    public void test() {
        target.save();
    }
}

  可以看到我们通过注解进行AOP的开发,增强的切面的类依旧使用了切点表达式,那么接下来我们就详细的去了解注解的相关术语吧。

  2.2.2、基于注解的AOP开发的相关术语

1、通知的类型

名称标签说明
前置通知@before用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知@after-returning用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知@around用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知@throwing用于配置异常抛出通知,指定增强的方法在出现异常时执行
最终通知@after用于配置最终通知,无论增强方法执行是否有异常都会执行

2、通知配置的语法

通知的配置语法:@通知注解(“切点表达式")

3、切点表达式的抽取

  同 xml配置aop 一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。具体如下:

@@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("MyAspect.myPoint()")
    public void before(){
        System.out.println("前置代码增强.....");
    }
    @Pointcut("execution(* com.study.aop.*.*(..))")
    public void myPoint(){}
}

具体使用抽取切点表达式的方式有两种
使用方式一
@注解类型(“抽取的注解的方法”)
代码演示

 /*抽取表达式后的第一种方式,演示:字符串形式的方法引用
* */
@AfterReturning("pointCut()")
public void afterRunning() {
    System.out.println("spring AOP 后置增强");
    /*效果:Spring aop前置增强
           save 方法被执行
           spring AOP 后置增强*/
}

使用方式二
@注解类型(“切面类名.抽取的注解的方法”)
代码演示

/*抽取表达式的第二种方式演示,切面类名.方法
* */
@After("MyAspect.pointCut()")
public void after() {
    System.out.println("spring AOP 最终增强");
    /*效果:
    * Spring aop前置增强
        spring AOP 环绕前增强
        save 方法被执行
        spring AOP 最终增强
        spring AOP 抛出异常增强*/
}

三、jdbcTemplate的使用

3.1、概述

  JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。

3.2、使用jdbcTemplate的基本开发步骤

①导入spring-jdbc和spring-tx坐标

    <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
  </dependencies>
</project>

②创建数据库表和实体
实体

	package com.study.domain;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.domain
 * @className Account
 * @date 2022/4/28 8:37
 * @Description 账户实体类
 */
public class Account {
    private String name;
    private double money;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

数据库表
在这里插入图片描述
③创建JdbcTemplate对象
④执行数据库操作

 /**
     * @Date  2022/4/27 17:46
     * @Param
     * @Return void
     * @MetodName test1
     * @Author wang
     * @Description 测试jdbc模板开发步骤
     */
    @Test
    public void test1() {
        //获取数据源对象
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("2395");


        //获取jdbctemplate对象
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //设置数据源
        jdbcTemplate.setDataSource(dataSource);

        String name =  "李四";
        double money = 999.99;
        int update = jdbcTemplate.update("insert into account values(?,?)", name, money);
        System.out.println(update);
    }

3.3、jdbcTemplate-spring产生模板对象分析

  我们可以将JdbcTemplate的创建权交给Spring,将数据源DataSource的创建权也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模版对象中,然后通过Spring容器获得JdbcTemplate对象来执行操作。

3.4、jdbcTemplate-spring产生模板对象代码实现

配置

 <!--配置数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="username" value="root"></property>
    <property name="password" value="2395"></property>
</bean>

<!--配置jdbcTemplate对象,注意包不要弄错了-->

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--设置数据源对象到jdbcTemplate对象-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

测试代码

   /**
     * @Date  2022/4/28 9:01
     * @Param
     * @Return void
     * @MetodName test
     * @Author wang
     * @Description 测试spring产生的jdbcTemplate对象
     */
    @Test
    public void test2() {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        JdbcTemplate bean = app.getBean(JdbcTemplate.class);
        bean.update("insert into account values(?,?)","诸葛亮",22222.22);
    }

3.5、jdbcTemplate-spring产生模板对象代码实现(抽取jdbc.properties)

将数据库的连接信息抽取到外部配置文件中,和spring的配置文件分离开,有利于后期维护
jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=2395

配置文件

	<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <!--加载jdbc.properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--配置数据源对象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <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>

    <!--配置jdbcTemplate对象-->

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--设置数据源对象到jdbcTemplate对象-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>

3.6、JdbcTemplate基本使用-常用操作-更新操作(应用)

package com.study.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.test
 * @className jdbcTemplateCRUDTest
 * @date 2022/4/28 9:27
 * @Description 测试spring产生的jdbcTemplate的CURD操作
 */

@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件
@ContextConfiguration("classpath:applicationContext.xml")
public class jdbcTemplateCRUDTest {

    @Autowired//注入参数
    private JdbcTemplate jdbcTemplate;

    /**
     * 测试修改
     */
    @Test
    public void testUpdate() {
        int update = jdbcTemplate.update("update account set money = ? where name = ? ", 258000, "张三");
        System.out.println(update);
    }

    /**
     * 测试删除
     */
    @Test
    public void testDelete() {
        int update = jdbcTemplate.update("delete from account where name = ?", "张三");
        System.out.println(update);
    }
}

3.7、JdbcTemplate基本使用-常用操作-查询操作(应用)

package com.study.test;
import com.study.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.test
 * @className jdbcTemplateCRUDTest
 * @date 2022/4/28 9:27
 * @Description 测试spring产生的jdbcTemplate的CURD操作
 */

@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件
@ContextConfiguration("classpath:applicationContext.xml")
public class jdbcTemplateCRUDTest {

    @Autowired//注入参数
    private JdbcTemplate jdbcTemplate;


    /**
     * 测试查询所有的数据数量
     */
    @Test
    public void testQueryCount() {
        Integer count = jdbcTemplate.queryForObject("select count(*) from account", Integer.class);
        System.out.println(count);
        /*4*/
    }


    /**
     * 测试查询一个对象
     */
    @Test
    public void testQueryOne() {
        Account account = jdbcTemplate.queryForObject("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), "李四");
        System.out.println(account);
        /*Account{name='李四', money=999.99}*/
    }
    /**
     * 测试查询所有的数据
     */
    @Test
    public void testQueryAll() {
        /*查询所有用的是query方法,返回一个list集合*/
        List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
        System.out.println(accountList);
        /*[Account{name='嫦娥', money=2222.2}, Account{name='李四', money=999.99}, Account{name='猪八戒', money=22222.2}, Account{name='诸葛亮', money=22222.22}]*/
    }

四、声明式事务控制

4.1、什么是声明式事务控制

  Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。

声明式事务处理的作用

  • 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可

  • 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
    注意:Spring 声明式事务控制底层就是AOP。

4.2、基于xml配置的声明式事务控制的实现

  4.2.1、实现步骤

声明式事务控制需要明确的三点:
1、谁是通知
2、谁是切点
3、谁是切面

测试声明式事务控制的环境搭建
①、dao层的接口和实现类

package com.study.dao;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.dao
 * @className AccountDao
 * @date 2022/4/28 14:57
 * @Description 转账的dao层
 */
public interface AccountDao {
    /**
     * 转出钱的方法
     * @param outMan
     * @param money
     */
    public void out(String outMan,double money);

    /**
     * 转入钱的方法
     * @param inMan
     * @param money
     */
    public void in(String inMan,double money);

}

实现类

package com.study.dao.impl;
import com.study.dao.AccountDao;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.dao.impl
 * @className AccountDaoImpl
 * @date 2022/4/28 14:59
 * @Description 转账的dao层的实现类
 */
public class AccountDaoImpl implements AccountDao {

    /**注入jdbcTemplate*/
    private JdbcTemplate jdbcTemplate = new JdbcTemplate();
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 转出钱的方法
     * @param outMan
     * @param money
     */
    public void out(String outMan, double money) {
        jdbcTemplate.update("update account set money = money - ? where name = ?",money,outMan);
    }

    /**
     * 转入钱的方法
     * @param inMan
     * @param money
     */
    public void in(String inMan, double money) {
        jdbcTemplate.update("update account set money = money + ? where name = ?",money,inMan);
    }
}

②、service层的接口和实现类

package com.study.service;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.service
 * @className AccountService
 * @date 2022/4/28 15:03
 * @Description 转账操作的service层
 */
public interface AccountService {
    /**
     * 转账的方法,提供转出人和转入人和转账多少即可。
     * @param outMan
     * @param inMan
     * @param money
     */
    public void transfer(String outMan,String inMan,double money);
}

实现类

package com.study.service.impl;
import com.study.dao.AccountDao;
import com.study.service.AccountService;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.service.impl
 * @className AccountServiceImple
 * @date 2022/4/28 15:05
 * @Description 转账操作的service岑的实现类
 */
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;

    /**注入dao*/
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan,money);
        /*模拟异常*/
        int i = 1/0;
        accountDao.in(inMan,money);
    }
}

实体类不要忘记了

package com.study.domain;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.domain
 * @className Account
 * @date 2022/4/28 14:56
 * @Description 账户实体类
 */
public class Account {
    private String name;
    private double money;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

环境搭建完成了就可以测试了

首先引入需要的命名空间
①引入tx命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

②配置事务增强

<!--配置平台事务管理器,后面配置事务增强的通知需要,这里使用的jdbc技术,-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--他的内部需要注入数据源对象-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置,通知,事务的增强,引入transactionManager对象-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--设置事务的属性,配个某个方法执行某个事务,他有几种配法-->
        <tx:attributes>
            <!--第一种,就是方法使用*,代表任意方法,且不写属性,代表使用默认的事物属性配置-->
            <tx:method name="*"/>
            <!--第二种,给任一方法配置你想要的事物属性,-->
            <tx:method name="*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"></tx:method>
            <!--第三种,给某个方法单独配置属性-->
            <tx:method name="insert" timeout="-1" isolation="REPEATABLE_READ"></tx:method>
            <!--第四种,给某一类方法配置属性,这种就是说比如updateUser,updateMoney等等都可以配置这种,-->
            <tx:method name="update*" read-only="true" isolation="REPEATABLE_READ"></tx:method>
        </tx:attributes>
    </tx:advice>

③配置事务 AOP 织入

 <!--配置事务的AOP织入,事务的织入有个专门的标签-->
<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.study.service.impl.*.*(..))"></aop:advisor>
</aop:config>

④测试事务控制转账业务代码

package com.study.controller;

import com.study.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.controller
 * @className AccountController
 * @date 2022/4/28 15:14
 * @Description web层模拟
 */
public class AccountController {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = app.getBean(AccountService.class);
        accountService.transfer("李四","嫦娥",500);
    }
}
@Override
public void transfer(String outMan, String inMan, double money) {
    accountDao.out(outMan,money);
    int i = 1/0;
    accountDao.in(inMan,money);
}

  那么目前存在一个自己造的异常,转账肯定是不会通过的,如果第三步事务控制不失效,那么就会出现,out方法执行,并且数据提交了,但in方法没有执行,就是转账已经转出去了,但是另一人没有收到钱,这在实际情况中是不允许的,因此就有了事务控制,第三步执行,那么这个异常发生,out方法也不会进行数据的提交,就可以避免转账的发生。

  4.2.2、切点方法的事物参数配置

	<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

其中,<tx:method> 代表切点方法的事务参数的配置,例如:
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
属性包括

  • name:切点方法名称

  • isolation:事务的隔离级别

  • propogation:事务的传播行为

  • timeout:超时时间

  • read-only:是否只读

4.3、基于注解方式的声明式事务控制

  4.3.1、测试步骤

测试环境同xml方式的
需要对写注解的几个类

dao

package com.study.dao.impl;

import com.study.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.dao.impl
 * @className AccountDaoImpl
 * @date 2022/4/28 14:59
 * @Description 转账的dao层的实现类
 */
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate = new JdbcTemplate();
    /**
     * 转出钱的方法
     * @param outMan
     * @param money
     */
    public void out(String outMan, double money) {
        jdbcTemplate.update("update account set money = money - ? where name = ?",money,outMan);
    }`在这里插入代码片`

    /**
     * 转入钱的方法
     * @param inMan
     * @param money
     */
    public void in(String inMan, double money) {
        jdbcTemplate.update("update account set money = money + ? where name = ?",money,inMan);
    }
}

service

package com.study.service.impl;
import com.study.dao.AccountDao;
import com.study.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author wang
 * @version 1.0
 * @packageName com.study.service.impl
 * @className AccountServiceImple
 * @date 2022/4/28 15:05
 * @Description 转账操作的service岑的实现类
 */

@Service("accountService")
/*也可以在类上配置,效果为该类所有方法都可以生效配置的事物,但是如果方法上自己有一个配置,会采用一个就近原则,用方法上自己的*/
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    /*事物的配置,在该方法上配置,仅对该方法生效*/
    @Transactional(isolation = Isolation.REPEATABLE_READ ,propagation = Propagation.REQUIRED)
    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan,money);
        /*模拟异常*/
        int i = 1/0;
        accountDao.in(inMan,money);
    }
}

编写 applicationContext.xml 配置文件

	<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--引入配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--配置数据源对象-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>


    <!--配置jdbcTemplate对象,注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!--配置平台事务管理器,后面配置事务增强的通知需要,这里使用的jdbc技术,-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--他的内部需要注入数据源对象-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

    <!--事物的注解驱动, 没有这个,事物管理器没有-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

  4.3.2、注解配置声明式事务控制解析

  ①使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。

  ②注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。

  ③使用在方法上,不同的方法可以采用不同的事务参数配置。

  ④Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空山新雨后~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值