• Spring之AOP和事务管理


    前言

    AOP是Aspect-Oriented Programming面向切面编程的缩写;

    AOP和IOC一样也是一种编程思想,最终的目的都是为了实现代码在编译期的解耦;

    IOC可实现对象与对象之间的解耦,AOP可实现方法和方法之间的解耦(AOP解耦粒度会更细);

    当我们把dao层和service层的实现类对象,以及实现类对象需要的依赖,放进Sprig的容器管理之后;

    这些实现类对象的某些方法都依赖同一段代码,例如service层和dao层实现类的所有方法都需要增加日志、事务、代码性能测试功能,应该使用AOP;

    AOP思想的目的是可以在不修改源代码的基础上,对原有功能进行增强。

    一、AOP概念

    AOP( 面向切面编程 )是一种思想,它的目的就是在不修改源代码的基础上,对原有功能进行增强。

    SpringAOP是对AOP思想的一种实现,Spring底层同时支持jdk和cglib动态代理

    Spring会根据被代理的类是否有接口自动选择代理方式:

    • 如果有接口,就采用jdk动态代理(当然,也可以强制使用cglib)

    • 没有接口,    就采用cglib的方式

    1.代理技术

    代理技术是实现AOP思想的手段;

    1.1.jdk代理

    需要原始对象和代理对象实现同一个接口;

    package com.zhanggen;
    
    import com.zhanggen.config.SpringConfig;
    import com.zhanggen.log.Loger;
    import com.zhanggen.service.AccountService;
    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;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class AccountServiceTest {
        //日志对象
        @Autowired
        private Loger loger;
        //目标对象
        @Autowired
        private AccountService accountService;
    
        @Test
        public void testSaveAndFind() {
            //产生代理对象
            //1.获取目标对象
            //2.编写增强逻辑
            InvocationHandler invocationHandler = new InvocationHandler() {
    
    
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object obj = null;
                    try {
                        loger.beforMetod();
                        //调用目标对象的的方法逻辑
                        //如果目标对象的方法有返回值,则返回,没有则返回值是null
                        obj = method.invoke(accountService, args);
                        loger.afterMetod();
                    } catch (Exception e) {
                        loger.exceptionMetod();
                    }
                    return obj;
                }
            };
            //3.创建代理对象
            AccountService instance = (AccountService) Proxy.newProxyInstance(
                    accountService.getClass().getClassLoader(),
                    accountService.getClass().getInterfaces(),
                    invocationHandler
            );
            //4.调用代理对象的方法
            instance.save();
        }
    
    }
    jdk代理实现AOP

    1.2.cglib代理

    需要代理对象继承原始对象;

    package com.zhanggen;
    
    import com.zhanggen.config.SpringConfig;
    import com.zhanggen.log.Loger;
    import com.zhanggen.service.impl.AccountServiceImpl;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.InvocationHandler;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import java.lang.reflect.Method;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class AccountServiceTest {
        //日志对象
        @Autowired
        private Loger loger;
        //目标对象
        @Autowired
        private AccountServiceImpl accountService;
    
        @Test
        public void testSaveAndFind() {
            //产生代理对象
            //1.获取目标对象
            //2.编写增强逻辑
            InvocationHandler invocationHandler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object obj = null;
                    try {
                        loger.beforMetod();
                        //调用目标对象的的方法逻辑
                        //如果目标对象的方法有返回值,则返回,没有则返回值是null
                        obj = method.invoke(accountService, args);
                        loger.afterMetod();
                    } catch (Exception e) {
                        loger.exceptionMetod();
                    }
                    return obj;
                }
            };
            //3.使用cglib创建代理对象
            //3-1:创建增强器
            Enhancer enhancer = new Enhancer();
            //3-2:设置父类
            enhancer.setSuperclass(AccountServiceImpl.class);
            //3-3:设置增强逻辑
            enhancer.setCallback(invocationHandler);
            //3-4:得到代理对象
            AccountServiceImpl instance = (AccountServiceImpl) enhancer.create();
            //4.调用代理对象的方法
            instance.save();
        }
    
    }
    cgilib代理

    2.AOP术语

    目标对象: 被代理对象

    连接点:   目标对象中的所有方法;(全部方法)

    切入点:    目标对象中要进行功能增强的方法,可以有1个也可以有多个;(一部分方法)

    增强(通知)对象:  需要增强的功能(日志 事务),一般是1个增强对象,增强对象中有增强方法;

    织入(动词):将切点方法和增强方法拼接过程

    代理对象:经过功能增强之后的对象

    切面(名词):切点+增强,切面是一种描述,描述了这样一件事情: 一个什么样的增强方法加入到了 一个什么样的切点方法的 什么位置,本质上就是在描述增强方法和切点方法的执行顺序

    2.AOP实现过程 

    开发阶段: 核心功能和边缘功能进行分别开发,双方完全没有任何依赖关系;

    运行阶段:使用动态代理技术,动态对核心功能和边缘功能进行自由组装,形成1个大而全的功能

     

    开发阶段分别开发,运行阶段组装运行;

    3.AOP的优势

    • 代码逻辑清晰:开发核心业务的时候,不必关注增强业务的代码
    • 代码复用性高:增强代码不用重复书写
    • 核心功能代码和边缘功能解耦

    二、AOP日志案例

    使用SpringAop完成在业务(Service)层类中的方法上打印日志

    1.创建maven子模块,导入pom依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring</artifactId>
            <groupId>com.zhanggen</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>day03-04-springaop-xml</artifactId>
        <dependencies>
            <!--spring核心-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
            <!--切点表达式解析坐标-->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.7</version>
            </dependency>
    
            <!--测试-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
        </dependencies>
    
    </project>
    pom.xml

    2.创建业务层接口和实现类

    定义service层接口

    package com.zhanggen.service;
    
    import java.util.List;
    
    public interface AccountService {
        //保存
        void save(Object obj);
    
        //查询所有
        List findAll();
    
        //主键查询
        Object findById(Integer id);
    }
    AccountService.interface

    定义service层实现类

    package com.zhanggen.service.impl;
    
    import com.zhanggen.service.AccountService;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class AccountServiceImpl implements AccountService {
        @Override
        public void save(Object obj) {
            System.out.println("save");
        }
    
        @Override
        public List findAll() {
            System.out.println("findAll");
            return null;
        }
    
        @Override
        public Object findById(Integer id) {
            System.out.println("findById");
            return "哈哈哈";
        }
    }
    AccountServiceImpl.java

    3.创建日志类

    package com.zhanggen.log;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Loger {
        public void beforMethod() {
            System.out.println("进入方法前");
        }
    
        public void afterMethod() {
            System.out.println("进入方法后");
        }
    }
    Loger.java

    4.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"
           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/tx
                    http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop
                    https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--配置注解扫描-->
        <context:component-scan base-package="com.zhanggen"></context:component-scan>
        <!--表示这是一段aop设置-->
        <aop:config>
    
            <!--
            配置切点:通过规则表达式选择出1个切点方法
            id="标识"
            expression="execution(切点表达式)"
            -->
            <aop:pointcut id="pt"
                          expression="execution(java.util.List com.zhanggen.service.impl.AccountServiceImpl.findAll())"></aop:pointcut>
            <!--
                配置1个切面:增强方法和切点方法的执行顺序
                增强方法:ref="loger"+ method="beforMethod"
                切点方法:pointcut-ref="pt"
                执行顺序: aop:before 执行顺序(增强方法在切点方法之前运行)
    
                -->
            <aop:aspect ref="loger">
                <aop:before method="beforMethod" pointcut-ref="pt"></aop:before>
                <aop:after method="afterMethod" pointcut-ref="pt"></aop:after>
            </aop:aspect>
    
    
        </aop:config>
    
    </beans>
    applicationContext.xml

    5.测试

    package com.zhanggen;
    
    import com.zhanggen.service.AccountService;
    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;
    
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    
    public class AccountServiceTest {
        @Autowired
        private AccountService accountService;
    
        @Test
        public void Test1() {
            accountService.findAll();
        }
    }
    AccountServiceTest.java

    三、APO配置项(XML)

    在spring配置文件中配置AOP的配置项介绍;

    1.切点表达式

    如果service层所有实现类(目标对象)的所有方法(切点),都需要增加日志功能,如何给多个类和方法进行功能增强呢?

    切点表达式本质是一组匹配规则,用于在目标对象的连接点中挑选出1个或者多个切点

    1.1.匹配1个切点

    切点表达式(expression):通过规则表达式,从目标对象中选择出1个切点方法

    id="标识"

            <aop:pointcut id="pt"
                          expression="execution(java.util.List com.zhanggen.service.impl.AccountServiceImpl.findAll())">
         </
    aop:pointcut>

    1.2.模糊匹配多个切点

     *占位符:表示匹配1个或多个

    ..占位符:表示匹配0个或多个

    <aop:pointcut id="pt"
                          expression="execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))"></aop:pointcut>

    2.四大通知

    四大通知描述的就是增强方法在切点方法的什么位置上执行

    • 前置通知(before) :增强方法在切点运行之前执行
    • 后置通知(after-returning):增强方法在切点正常运行结束之后执行
    • 异常通知(after-throwing):增强方法在切点发生异常的时候执行
    • 最终通知(after):增强方法在切点的最终执行

    4大通知相当于如下代码执行流程;

    try {
      前置通知(before) :增强方法在切点运行之前执行
      // 切点方法执行
      后置通知(after-returning):增强方法在切点正常运行结束之后执行
    }catch (Exception e){
        异常通知(after-throwing):
      增强方法在切点发生异常的时候执行 }
    finally { 最终通知(after):增强方法在切点的最终执行 }

    3.环绕通知

    它是一种特殊的通知,他允许以Java编码的形式,替代以上4大通知;

    3.1.实现环绕通知方法

    package com.zhanggen.log;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Loger {
        public void beforMethod() {
            System.out.println("进入方法前");
        }
    
        public void afterMethod() {
            System.out.println("进入方法后");
        }
    
        //环绕通知方法:可以替代以上所有方法
        public Object aroundMethod(ProceedingJoinPoint pjp) {
            Object obj = null;
            try {
                System.out.println("进入方法前");
                obj = pjp.proceed();
                System.out.println("进入方法后");
            } catch (Throwable throwable) {
                System.out.println("方法出现异常结束");
            } finally {
                System.out.println("方法正常结束");
            }
            return obj;
        }
    }
    Loger.java

    3.2.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/tx
                    http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop
                    https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--配置注解扫描-->
        <context:component-scan base-package="com.zhanggen"></context:component-scan>
        <!--表示这是一段aop设置-->
        <aop:config>
            <!--
            配置切点:通过规则表达式选择出1个切点方法
            id="标识"
            expression="execution(切点表达式)"
            -->
            <aop:pointcut id="pt"
                          expression="execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))"></aop:pointcut>
            <!--
                配置1个切面:增强方法和切点方法的执行顺序
                增强方法:ref="loger"+ method="beforMethod"
                切点方法:pointcut-ref="pt"
                执行顺序: aop:before 执行顺序(增强方法在切点方法之前运行)
    
                -->
            <aop:aspect ref="loger">
                <!--<aop:before method="beforMethod" pointcut-ref="pt"></aop:before>-->
                <!--<aop:after method="afterMethod" pointcut-ref="pt"></aop:after>-->
                <aop:around method="aroundMethod" pointcut-ref="pt"></aop:around>
            </aop:aspect>
    
    
        </aop:config>
    
    </beans>
    applicationContext.xml

    3.3.环绕通知获取切点执行细节

    pjp对象代表当前切点,可以通过这个对象获取到正在调用类的方法、参数、返回值、异常信息、调用时间,作为日志信息的一部分;

    package com.zhanggen.log;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.Date;
    
    @Component
    public class Loger {
        public void beforMethod() {
            System.out.println("进入方法前");
        }
    
        public void afterMethod() {
            System.out.println("进入方法后");
        }
    
        //环绕通知方法:可以替代以上所有方法
        //pjp对象代表切点:可以通过这个对象获取到正在调用类的 方法、参数、返回值、异常信息、调用时间;
        public Object aroundMethod(ProceedingJoinPoint pjp) {
            //日志字符串可变长度字符串
            StringBuilder stringBuilder = new StringBuilder();
            Object obj = null;
            try {
                System.out.println("进入方法前");
                obj = pjp.proceed();
                stringBuilder.append("类:" + pjp.getTarget().getClass().getName());
                Signature signature = (MethodSignature) pjp.getSignature();
                stringBuilder.append("方法名称:" + signature.getName());
                stringBuilder.append("方法参数:" + Arrays.toString(pjp.getArgs()));
                stringBuilder.append("方法返回值:" + obj);
                stringBuilder.append("调用时间:" + new Date());
                System.out.println("进入方法后");
            } catch (Throwable e) {
                System.out.println("方法出现异常结束");
                stringBuilder.append("异常信息:" + e.getMessage());
            } finally {
                System.out.println("方法正常结束");
            }
            //打印日志
            System.out.println(stringBuilder.toString());
            return obj;
        }
    }
    Loger.java

    四、APO配置(注解)

    使用注解的方式替换xml配置文件中的配置,AOP的注解配置在增强方法上;

    1.激活切面自动代理

    配置文件中配置激活切面自动代理

    <?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/tx
                    http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop
                    https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--配置注解扫描-->
        <context:component-scan base-package="com.zhanggen"></context:component-scan>
        <!--激活切面自动代理-->
        <aop:aspectj-autoproxy/>
    
    
    </beans>
    applicationContext.xml

    配置类中配置激活切面自动代理

    package com.zhanggen.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    
    //注解扫描
    @ComponentScan("com.zhanggen")
    //激活切面自动代理
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
    SpringConfig.java

    2.四大通知

    package com.zhanggen.log;
    
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Aspect //切面:增强方法和切点方法的执行顺序
    @Component
    public class Loger {
        // 定义切点:默认id是方法名称
        @Pointcut("execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))")
        public void pt() {
        }
    
        //代表此注解标注的方法会在切点之前运行
        @Before("pt()")
        public void beforMethod() {
            System.out.println("进入方法前");
        }
    
        @AfterReturning("pt()")
        public void afterReturningMethod() {
            System.out.println("方法正常运行结束后");
        }
    
        @AfterReturning("pt()")
        public void afterThrowingMethod() {
            System.out.println("方法出现异常后");
        }
    
        @After("pt()")
        public void afterMethod() {
            System.out.println("方法运行到最后");
        }
    
        //环绕通知方法:可以替代以上所有方法
        //pjp对象代表切点:可以通过这个对象获取到正在调用类的 方法、参数、返回值、异常信息、调用时间;
    //    public Object aroundMethod(ProceedingJoinPoint pjp) {
    //        //日志字符串可变长度字符串
    //        StringBuilder stringBuilder = new StringBuilder();
    //        Object obj = null;
    //        try {
    //            System.out.println("进入方法前");
    //            obj = pjp.proceed();
    //            stringBuilder.append("类:" + pjp.getTarget().getClass().getName());
    //            Signature signature = (MethodSignature) pjp.getSignature();
    //            stringBuilder.append("方法名称:" + signature.getName());
    //            stringBuilder.append("方法参数:" + Arrays.toString(pjp.getArgs()));
    //            stringBuilder.append("方法返回值:" + obj);
    //            stringBuilder.append("调用时间:" + new Date());
    //            System.out.println("进入方法后");
    //        } catch (Throwable e) {
    //            System.out.println("方法出现异常结束");
    //            stringBuilder.append("异常信息:" + e.getMessage());
    //        } finally {
    //            System.out.println("方法正常结束");
    //        }
    //        //打印日志
    //        System.out.println(stringBuilder.toString());
    //        return obj;
    //    }
    }
    Loger.java

    注解版四大通知一起使用,执行顺序有问题,不建议使用

    3.环绕通知

    package com.zhanggen.log;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.*;
    import org.aspectj.lang.reflect.MemberSignature;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.Date;
    
    @Aspect //切面:增强方法和切点方法的执行顺序
    @Component
    public class Loger {
        // 定义切点:默认id是方法名称
        @Pointcut("execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))")
        public void pt() {
        }
        //环绕通知注解配置
        @Around("pt()")
        public Object aroundMethod(ProceedingJoinPoint pjp) {
            //日志字符串可变长度字符串
            StringBuilder stringBuilder = new StringBuilder();
            Object obj = null;
            try {
                System.out.println("进入方法前");
                obj = pjp.proceed();
                stringBuilder.append("类:" + pjp.getTarget().getClass().getName());
                Signature signature = (MemberSignature) pjp.getSignature();
                stringBuilder.append("方法名称:" + signature.getName());
                stringBuilder.append("方法参数:" + Arrays.toString(pjp.getArgs()));
                stringBuilder.append("方法返回值:" + obj);
                stringBuilder.append("调用时间:" + new Date());
                System.out.println("进入方法后");
            } catch (Throwable e) {
                System.out.println("方法出现异常结束");
                stringBuilder.append("异常信息:" + e.getMessage());
            } finally {
                System.out.println("方法正常结束");
            }
            //打印日志
            System.out.println(stringBuilder.toString());
            return obj;
        }
    }
    环绕通知注解配置

    4.测试

    package com.zhanggen;
    
    import com.zhanggen.config.SpringConfig;
    import com.zhanggen.service.AccountService;
    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;
    
    
    @RunWith(SpringJUnit4ClassRunner.class)
    //@ContextConfiguration("classpath:applicationContext.xml") //配置文件
    @ContextConfiguration(classes = SpringConfig.class)         //配置类
    public class AccountServiceTest {
        @Autowired
        private AccountService accountService;
    
        @Test
        public void Test1() {
            accountService.save(1);
            System.out.println("===========================>");
            accountService.findAll();
            System.out.println("===========================>");
            accountService.findById(1);
        }
    }
    AccountServiceTest.java

    五、转账案例

    两个用户可以相互转账功能,利用AOP的环绕通知,手动实现service层事务操作;

    1.创建模块,导入依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring</artifactId>
            <groupId>com.zhanggen</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>day05-01-transfer</artifactId>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.15</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.1.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.20</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.7</version>
            </dependency>
        </dependencies>
    
    </project>
    pom.xml

    2.创建实体类

    package com.zhanggen.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Account {
        private Integer aid;
        private String name;
        private Float balance;
    }
    Account.java

    3.创建dao接口

    package com.zhanggen.dao;
    
    public interface AccountDao {
        //减钱
        void diff(String name, Float amount);
    
        //加钱
        void add(String name, Float amount);
    }
    AccountDao.interface

    4.创建dao实现类

    package com.zhanggen.dao.impl;
    
    import com.zhanggen.dao.AccountDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class AccountDaoImpl implements AccountDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Override
        public void diff(String name, Float amount) {
            System.out.println(name);
            jdbcTemplate.update("update account set balance=balance-? where name=?", amount, name);
        }
    
        @Override
        public void add(String name, Float amount) {
            jdbcTemplate.update("update account set balance=balance+? where name=?", amount, name);
            System.out.println(name);
        }
    }
    AccountDaoImpl.java

    5.创建service接口

    package com.zhanggen.service;
    
    public interface AccountService {
        //转账
        void transfer(String sourceAcountName,String targetAcountName,Float amount);
    
    }
    AccountService.interface

    6.创建service实现类

    package com.zhanggen.service.impl;
    
    import com.zhanggen.dao.AccountDao;
    import com.zhanggen.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class AccountServiceImpl implements AccountService {
        @Autowired
        private AccountDao accountDao;
    
        @Override
        public void transfer(String sourceAcountName, String targetAcountName, Float amount) {
            accountDao.diff(sourceAcountName, amount);
            accountDao.add(targetAcountName, amount);
        }
    }
    AccountServiceImpl.java

    7.加入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"
           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/tx
                    http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop
                    https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--配置注解扫描-->
        <context:component-scan base-package="com.zhanggen"></context:component-scan>
        <!--数据源-->
        <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
            <property name="username" value="zhanggen"></property>
            <property name="password" value="123.com"></property>
        </bean>
        <!--jdbcTemplate对象-->
        <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
        </bean>
    
    </beans>
    applicationContext.xml

    8.测试

    package com.zhanggen;
    import com.zhanggen.service.AccountService;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.junit.runner.RunWith;;
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml") //配置文件
    public class AcountTransferTest {
        @Autowired
        private AccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer("张根", "刘龙华", 20F);
        }
    }
    AcountTransferTest.java

    六、Spring的事务管理方式

    Spring支持两种事务管理方式:编程式事务和声明式事务

    • 编程式事务就是将业务代码和事务代码放在一起书写,它的耦合性太高开发中基本不使用

    • 声明式事务其实就是将事务代码和业务代码隔离开发,然后通过一段配置让他们组装运行,最后达到事务控制的目的(使用);

    其中声明式事务就是通过AOP原理实现的

     

    1.Spring事务管理相关的API

    Spring把事务处理的功能封装到1个工具类中称为事务管理器;

    无论使用编程式事务还是声明式事务管理,事务管理器有以下API;

    但是这些API基本都是由Spring是管理器底层进行调用;

    我们通过注解/xml配置Sping事务管理器即可;

     

    1.1.PlatformTransactionManager

    PlatformTransactionManager这是Spring进行事务管理的一个根接口,我们要使用它的实现类做事务管理

    Mybatis和jdbcTemplate都可以使用它的一个子类(DataSourceTransactionManager)做事务管理

     

    1.2.TransactionDefinition

    TransactionDefinition这个API是用来做事务定义的;

       

    1.2.1.隔离级别

    1.2.2.传播行为

    事务传播行为指的就是当一个业务方法【被】另一个业务方法调用时,应该如何进行事务控制

    a(){// a会将当前自己是否开启了事务这样一个状态传递给b
        
        b();//转账  b必须有事务        required
        b();//记录日志  b有无事务都行   supports
    }
    
    b(){ //自己   事务如何控制
        
    }

    传播行为配置项

    1.2.3.只读性

    只读事务(增 删 改不能使用,只能查询使用)

    换句话说,只读事务只能用于查询方法

    1.2.4.超时时长

    事务超时时间, 此属性需要底层数据库的支持

    它的默认值是-1, 代表不限制

     

    1.3.TransactionStatus

    TransactionStatus代表的是事务的当前状态;

     

    1.4.总结以上3个API之间的关系

    PlatformTransactionManager通过读取TransactionDefinition中定义事务信息参数来管理事务,

    事务被管理之后会产生一些列的事务状态TransactionStatus;

     

    七、Spring引入声明式事务

    我们可以自己通过Spring的AOP去自己实现开发中的事务操作,但是在多人协作开发项目中,每个人使用不同的事务,会难以维护,且代码重用;

    Spring提供了事务管理器,功能比较强大,无需重复造轮子,所以在日常开发中,我们一般通过配置Spring,实现1个项目公用的事务管理器,即采用声明式事务;

    1.事务管理器配置思路

    目标对象:service对象(已完成)

    增强对象:事务控制功能,DataSourceTransactionManager(Spring已经提供)但是这个事务管理器要正常工作,需要我们传递参数:事务隔离级别 事务传播行为 事务超时时间 事务是否只读

    配置切面:增强对象(开启 提交 回滚 关闭)中各个方法跟切点方法的执行顺序(顺序是固定的),我们仅仅需要指定增强对象和切点方法

    2.配置事务管理器(XML)

    2.1.使用默认配置参数

    <?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/tx
                    http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop
                    https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--配置注解扫描-->
        <context:component-scan base-package="com.zhanggen"></context:component-scan>
        <!--数据源-->
        <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
            <property name="username" value="zhanggen"></property>
            <property name="password" value="123.com"></property>
        </bean>
        <!--jdbcTemplate对象-->
        <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
        </bean>
        <!--事务管理器:这个id就写transactionManager-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="druidDataSource"></property>
        </bean>
        <!--配置事务管理(transactionManager)的参数,所有参数都使用默认值-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="*"/>
            </tx:attributes>
        </tx:advice>
        <aop:config>
            <!--配置切点:com.zhanggen.service.impl包下的所有类的所有方法-->
            <aop:pointcut id="pt" expression="execution(* com.zhanggen.service.impl.*.*(..))"/>
            <!--
            配置切面:advisor:这是为声明式事务专门准备的1个切面
            advice-ref="带有参数的增强对象(事务管理器)”pointcut-ref="切点""
            -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
        </aop:config>
    
    </beans>
    applicationContext.xml

    2.2.指定事务管理器参数

    name=“方法名”: 指的是事务参数作用方法的名称,支持模糊匹配,只要匹配成功1个,下一条就不再向下继续匹配

    isolation="DEFAULT":事务隔离级别,DEFAULT表示根据数据库来定

    propagation="REQUIRED":事务传播行为

    timeout="-1":事务超时时长,-1永远不超时

    read-only="false":事务是否只读

    no-rollback-for:指定遇到什么异常时,无需回滚

    rollback-for:指定遇到什么异常时,需回滚

    <?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/tx
                    http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop
                    https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--配置注解扫描-->
        <context:component-scan base-package="com.zhanggen"></context:component-scan>
        <!--数据源-->
        <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
            <property name="username" value="zhanggen"></property>
            <property name="password" value="123.com"></property>
        </bean>
        <!--jdbcTemplate对象-->
        <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
        </bean>
        <!--事务管理器:这个id就写transactionManager-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="druidDataSource"></property>
        </bean>
        <!--配置事务管理(transactionManager)的参数,
            name=“方法名”:      指的是事务参数作用方法的名称,支持模糊匹配,只要匹配成功1个,下一条就不再向下继续匹配
            isolation="DEFAULT":  事务隔离级别,DEFAULT表示根据数据库来定
            propagation="REQUIRED":事务传播行为
            timeout="-1":事务超时时长,-1永远不超时
            read-only="false":事务是否只读
            no-rollback-for:指定遇到什么异常时,无需回滚
            rollback-for:指定遇到什么异常时,需回滚
    
        -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"
                           no-rollback-for=""/>
                <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"
                           no-rollback-for=""/>
            </tx:attributes>
        </tx:advice>
        <aop:config>
            <!--配置切点:com.zhanggen.service.impl包下的所有类的所有方法-->
            <aop:pointcut id="pt" expression="execution(* com.zhanggen.service.impl.*.*(..))"/>
            <!--
            配置切面:advisor:这是为声明式事务专门准备的1个切面
            advice-ref="带有参数的增强对象(事务管理器)”pointcut-ref="切点""
            -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
        </aop:config>
    
    </beans>
    applicationContext.xml

    3.配置事务管理器(注解)

    3.1.配置文件

    <?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/tx
                    http://www.springframework.org/schema/tx/spring-tx.xsd
                    http://www.springframework.org/schema/aop
                    https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--配置注解扫描-->
        <context:component-scan base-package="com.zhanggen"></context:component-scan>
        <!--数据源-->
        <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
            <property name="username" value="zhanggen"></property>
            <property name="password" value="123.com"></property>
        </bean>
        <!--jdbcTemplate对象-->
        <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
        </bean>
        <!--事务管理器:这个id就写transactionManager-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="druidDataSource"></property>
        </bean>
        <!--事务注解驱动-->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    </beans>
    applicationContext.xml

    3.2.注解

    @Transactional可以标注在类上,也可以标注在方法上,标注在方法上优先级高于类上

    @Transactional //支持事务控制

    4. 配置事务管理器(纯注解)

    纯注解就是去xml文件;

    package com.zhanggen.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    
    @Configuration
    @EnableTransactionManagement //开启事务管理
    public class TranscationConfig {
    // 注意方法名transactionManager固定
        @Bean
        public DataSourceTransactionManager transactionManager(DataSource dataSource) {
            DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
            dataSourceTransactionManager.setDataSource(dataSource);
            return dataSourceTransactionManager;
        }
    }
    TranscationConfig.java
  • 相关阅读:
    ipvsadm命令介绍
    转载-lvs-dr模式+keepalived双机
    Codeforces Round #426 (Div. 2)
    Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals)
    Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals)
    Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals)
    Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals)
    Codeforces Round #425 (Div. 2)
    Codeforces Round #425 (Div. 2)
    Codeforces Round #425 (Div. 2)
  • 原文地址:https://www.cnblogs.com/sss4/p/16291682.html
Copyright © 2020-2023  润新知