Spring AOP原理分析

横切性问题

实际应用中,可能会临时增加一些公共的功能,例如在每个方法执行前都增加一个安全检查的代码。如果在每个方法里去增加调用的代码,会增加很大工作量,而且不易维护,每个方法都需要去改,例如:

这样违背了ocp原则,增加功能应该尽量不改动原来的代码。

这种问题也叫横切性问题,安全性检查的逻辑跟业务是独立的,"横切"到了业务方法里。

静态代理

要解决这个问题,必然需要改变add和delete方法原来的行为,让其先调用安全检查的代码,再去执行业务逻辑。而如果要遵循ocp原则,不在原来的方法里面改动,可以另外定义一个类继承原来的类,在这个子类里面去添加安全性检查的逻辑。并且原来所有创建父类对象的地方都要改成创建子类对象。

如:

这种方式创建的子类也叫代理类,这种方式需要为每个类都建一个新类,也叫静态代理类,如果需要代理的类很多就会有很大工作量。

 

也可以考虑新建一个类,这个类中有指向原类对象的引用,然后在新建类中增加安全性检查代码,要调用原类方法都通过新建这个类来调用,并且在调用前先调用安全检查方法,这样就不会改动原来的代码。也是一种静态代理方式。例如:

public class ManagerNew {

 

    private Manager manager;

 

    public ManagerNew(Manager manager) {

        this.manager = manager;

    }

 

    public String addM(String str1, String str2) {

        checkSecurity();

        this.manager.addM(str1, str2);

    }

 

    public void delete() {

        checkSecurity();

        this.manager.delete();

    }

 

    private void checkSecurity() {

    System.out.println("---------------------checkSecurity()----------------);

    }

}

这种静态代理方式也有同样问题,如果需要代理的类很多就会有很大工作量。

jdk动态代理

jdk动态代理原理

jdk动态代理只能自动为接口创建实现类,不能为类创建子类。因此目标类必须实现接口,也只有接口中定义的方法可以被jdk动态代理改变行为。目标类中接口定义以外的方法,在生成的代理对象中不会存在,因此目标类中的方法最好全都是接口中定义的方法。

用以下类和接口做示例:

接口类:

实现类:

 

jdk提供了一个Proxy类,它提供了一个newInstance方法。

传入1个接口类(或多个)、1个实现了InvocationHandler接口的类,即可返回1个实现了该接口类(或多个接口类)的对象。

InvocationHandler接口只有一个方法invoke。

Proxy. newProxyInstance创建的对象中会持有一个传入的InvocationHandler的实现类的成员变量,所有接口方法的行为就只是去调用它的invoke方法。

如:

相当于自动为目标接口ManagerInterface创建实现对象,这个对象并不是Manager类的对象,而是jdk生成的一个实现类。并且把实现对象所有方法的行为都变成调用InvocationHandler对象的invoke方法,相当于把所有方法的行为进行了统一。并且invoke的参数中还有目标方法名字和目标对象,这样也可以区分出调用invoke的来源是哪个目标对象的哪个方法。

 

因此可以利用它能够统一目标方法行为的这一特性,在这个统一调用的invoke方法中加入横切性逻辑(如安全性检查),然后根据传入的目标方法名字和目标对象,利用反射去回调目标方法,执行目标方法逻辑。也就是说invoke方法虽然是执行统一的逻辑,但它能区分出来源的不同,执行目标方法逻辑。

 

而目标方法逻辑必然放在用户自己实现的接口的类里(Manager类),因此InvocationHandler对象需要调用Manager类的对象去执行目标方法逻辑。可以在每次调用invoke方法时都new一个Manager类对象,也可以new一个放到它的成员变量里,如:

或者

显然第二种方式要好一些,只用创建一个Manager对象。

 

调用Proxy.newProxyInstance创建代理对象后执行结果如下:

 

这样的InvocationHandler实现类SecurityHandler只能用在ManagerInterface这个接口上,不太通用,而实际上invoke里面回调目标方法不需要知道目标类的类型,因此可以声明成Object类型,具体创建InvocationHandler对象的时候动态传入目标类对象,如:

调用的时候动态传入:

jdk动态代理生成的代理类都是接口类型的,因此目标类在接口定义之外的方法不会创建代理方法,而且代理类中只会有接口定义的方法。代理类也不能强转为目标类类型,只能强转为接口类型。

jdk动态代理源代码分析

分析Proxy的newProxyInstance方法:

这里生成了代理类的Class,然后通过Constructor的newInstance创建的对象。

 

最终调用了ProxyGenerator.generateProxyClass方法,该方法可以生成代理Class的字节码文件。然后调用native方法defineClass0将字节码文件转换成Class。

 

generateProxyClass的具体实现都在gen. generateClassFile里面,里面的实现很复杂,有很多io流操作,生成字节码文件。

 

手动创建代理类的class文件

generateProxyClass传入一个类名和接口数组生产字节码文件流,因此可以手动创建出代理的class文件,如:

 

import java.io.FileOutputStream;

import java.io.IOException;

 

import sun.misc.ProxyGenerator;

 

public class Test {

    public static void main(String[] args) {

          

byte[] classFile = ProxyGenerator.generateProxyClass("abc", UserServiceImpl.class.getInterfaces());

 

FileOutputStream out = null;

 

try {

out = new FileOutputStream("D:/abc.class");

out.write(classFile);

out.flush();

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

    }

}

会在D盘生成abc.class文件

 

打开文件可以看到生成的代码:

可以看出其实就是调用了InvocationHandler的invoke方法。

 

另外还能看出代理类继承了Proxy类,而java是单继承,而代理类必须是目标接口或者类的子类,才能进行强转。因此只能基于接口来代理。

JDK动态代理创建的类名分析

代理对象的toString()方法返回:目标对象类全路径+@+hashCode

Object默认的toString方法返回类的全路径+@+hashCode,如:

使用jdk动态代理时,从Object继承来的toString()、hashCode()、equals(Object obj)方法也会被代理。而invoke回调时,toString()方法的返回值通常就是目标对象的toString()方法返回值,因此代理对象的toString()方法返回的通常就是目标对象类全路径名字+@+hashCode,而不是代理类名字。如:

这里断点查看对象时,默认调用了对象的toString()方法显示。

 

代理类的Class名字是com.sun.proxy.$Proxy+数字

由于invoke代理了toString的缘故,代理类的toString输出的是目标类名字而不是代理类本身的名字。

代理类的名字通常是$Proxy+数字,可以通过目标对象的.getClass()获取,或者通过断点看到,如:

 

类型转换失败时,提示的错误信息是类的名字而不是toString的名字,如:

 

这里将jdk动态代理对象直接转换成目标实现显然不正确,错误提示也是com.sun.proxy.$Proxy0不能转。

 

 

 

Cglib代理

 

与jdk动态代理不同,cglib代理是直接生成目标类的子类,采用的是字节码技术,因此可以不基于接口代理。

与jdk动态代理原理类似,enhancer创建代理类对象时,需要指定一个目标类,一个实现了CglibProxy接口的对象,它会自动创建目标类的子类,并且把继承的方法的行为都改成回调CglibProxy接口对象的intercept方法。在intercept这个统一行为的方法中,可以根据方法名和目标对象的不同回调目标方法。

因为是创建子类,所以final方法是无法代理的。

使用:

CglibProxy.java:

import java.lang.reflect.Method;

 

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

 

public class CglibProxy implements MethodInterceptor {

    

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        System.out.println("前置代理");

        Object result = proxy.invokeSuper(obj, args);

        System.out.println("后置代理");

        return result;

    }

}

 

import net.sf.cglib.proxy.Enhancer;

 

public class Test2 {

    public static void main(String[] args) {

        CglibProxy cglibProxy = new CglibProxy();

        

        Enhancer enhancer = new Enhancer();

        // 设置需要创建子类的类

        enhancer.setSuperclass(UserServiceImpl.class);

        enhancer.setCallback(cglibProxy);

        

        UserServiceImpl useServiceImpl = (UserServiceImpl)enhancer.create();

        useServiceImpl.queryUser();

    }

}

 

cglib代理类创建速度比jdk动态代理慢,但方法调用速度快,适用于不频繁创建对象的场景,如单例类。

 

与jdk动态代理不同,cglib代理类的toString和getClass都是输出目标类路径+$$+CGlib标志,如:

spring AOP

springaop对代理进行了封装,不需要手动创建InvocationHandler或者MethodInterceptor,它会被springaop自动创建,只需要告诉springaop目标类、目标方法执行前后需要执行哪些代码,对哪些方法进行拦截即可。这样的好处是用户只需要更关注于业务,不需要关注具体的代理实现细节(如invoke方法的调用等)。

springaop对横切性问题进行了抽象。如将方法前后执行的代码封装成了Advice接口,只要将它的实现传给springaop,它会在目标方法前后自动执行回调方法。将需要拦截的方法做成了一套正则表达式,可以动态配置需要拦截哪些类的哪些方法(Pointcut)。将Pointcut和Advice封装到了Advisor。

 

Advice(通知)

Advice是AOP联盟定义的概念,描述连接点做什么(横切性关注点),spring中将它定义为一个接口,同时做了细分,它的子接口主要有BeforeAdvice、AfterAdvice、ThrowsAdvice(它是AfterAdvice的子接口),它们都是空接口。

 

 

 

 

 

BeforeAdvice下的接口是MethodBeforeAdvice,它定义了一个方法:

AfterAdvice下的接口是AfterReturningAdvice,它也定义了一个方法:

 

进行aop拦截的时候,代理类自动回调BeforeAdvice、AfterAdvice和Throws Advice的方法。

Pointcut(切点)

用于表示Advice作用于哪些方法,支持一套表达式。Pointcut有一个getMethodMatcher()方法返回MethodMatcher,所有的匹配判断都依赖于MethodMatcher的matches方法

 

 

Pointcut的种类很多,

如NameMatchMethodPointcut通过方法名匹配,JdkRegexpMethodPointcut通过正则表达式匹配。

 

 

ps:

Pointcut.TURE返回一个Pointcut的单例实现,它内部的MethodMatcher也是一个单例,并matches返回常量true,也就是匹配所有情况。(饿汉单例模式)

 

 

 

Advisor

Advisor用于封装Advice,它的子接口PointcutAdvisor封装了AdvicePointcutAdvicePointcut11地封装到advisor里面。

 

Aspect(切面类)

Aspect是一个切面类,由PointcutAdvice组成,如:

JoinPoint

应用的目标方法就叫"JoinPoint"(连接点)。Spring的AOP中JoinPoint只能是方法。其他的AOP实现(如AspectJ)可能是成员变量,例如修改成员变量时调用advice,。

 

 

 

编程式使用springAOP(使用ProxyFactory)

编程式使用springaop不依赖ioc容器,全都通过手动编码实现。使用springaop需要将增强代码封装到Advice对象里,然后传给springaop,它会自动创建代理类。它通过ProxyFactory创建代理类。

 

如:

Advice:

 

目标类:

 

测试代码:

 

输出:

这里默认使用了CGlib代理创建的Manager的子类。

创建了2个advisor分别封装了BeforAdvice和AfterAdvice。

配置式使用springAOP(使用ProxyFactoryBean)

与springioc容器整合,如果将创建代理的工厂ProxyFactory直接配置到spring容器中,则每次取出的bean对象类型都是ProxyFactory,需要再调用ProxyFactory.getProxy获取目标对象。

可以利用FactoryBean.getBean时根据不同参数可以返回指定类型的特性,将ProxyFactory做成FactoryBean,而springaop中使用的就是ProxyFacotryBean,它实现了FactoryBean接口。

 

跟spring ioc容器整合的aop代理的目标类都必须是受spring容器管理bean对象。

 

使用ProxyFacotryBean配置:

创建Advice:

 

创建Advisor:

 

目标类Manager和目标接口ManagerInterface:

 

spring配置:

 

测试代码:

由于目标类实现了接口,spring默认会使用jdk动态代理方式,因此生成的代理类是接口类型的ManagerInterface。

 

输出:

 

 

使用aop标签配置springAOP

配置使用ProxyFactoryBean时,需要对每一个目标bean都配置一个ProxyFactoryBean,很不方便,spring提供了aop标签,可以通过正则表达式指定哪些bean的哪些方法需要代理,使用哪些advice。

 

配置:

pom.xml需要引入的依赖:

 

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-context</artifactId>

            <version>4.3.9.RELEASE</version>

        </dependency>

 

        <dependency>

            <groupId>org.aspectj</groupId>

            <artifactId>aspectjweaver</artifactId>

            <version>1.8.4</version>

        </dependency>

 

 

 

HelloWorld.java

package com.test.springaop;

 

public interface HelloWorld {

    void printHelloWorld();

}

 

HelloWorldImpl.java

package com.test.springaop;

 

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

public class HelloWorldImpl implements HelloWorld {

    public void printHelloWorld() {

        System.out.println("printHelloWorld()");

    }

 

    public static void main(String[] args) {

        ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext("a.xml");

        HelloWorldImpl helloWorldImpl1 = (HelloWorldImpl)factory.getBean("helloWorldImpl");

        helloWorldImpl1.printHelloWorld();

        factory.close();

    }

}

 

TimeHandler.java

package com.test.springaop;

 

public class TimeHandler {

    public void printTime() {

        System.out.println("CurrentTime = " + System.currentTimeMillis());

    }

}

 

a.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:mvc="http://www.springframework.org/schema/mvc"

    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util"

    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

http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd

http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop.xsd"

    default-autowire="byName">

    

    <bean id="helloWorldImpl" class="com.test.springaop.HelloWorldImpl" />

    <bean id="timeHandler" class="com.test.springaop.TimeHandler" />

 

    <aop:config proxy-target-class="true">

        <aop:aspect id="time" ref="timeHandler">

            <aop:pointcut id="addAllMethod" expression="execution(* com.test.springaop.HelloWorldImpl.*(..))" />

            <aop:before method="printTime" pointcut-ref="addAllMethod" />

            <aop:after method="printTime" pointcut-ref="addAllMethod" />

        </aop:aspect>

    </aop:config>

</beans>

 

执行main方法,输出:

 

上面通过配置aop标签实现了代理,并且用代理类对象代替原来的对象创建bean对象。不需要手动编程创建代理类了。

 

如果目标类实现了接口,spring默认采用jdk动态代理,如果没有实现接口默认采用cglib代理,如果配置了proxy-target-class="true",则强制使用cglib代理。

 

如果没有配置proxy-target-class="true"而目标类又实现了接口,会采用jdk动态代理,因此

HelloWorldImpl helloWorldImpl1 = (HelloWorldImpl)factory.getBean("helloWorldImpl");

这个地方强转时会报错,因为jdk生成的代理对象是接口的实现,而不是子类。

只能改成强转为接口。

(HelloWorld)factory.getBean("helloWorldImpl");

使用注解配置springAOP

使用注解需要创建一个切面类,将它纳入spring容器管理:

如:

其中:

@Aspect表示这个类是切面类。

@Pointcut表示这个方法是切入点,即一套表达式,因为annotation标签需要依附与方法,所以定义了allAddMethod()方法让其依附,这个方法没有返回值,只是一个标记。其中execution(* add*(..))表示无论有无返回值,只要以add开头的方法,无论有无参数都被织入。Pointcut还可以指定到具体的类。

@Before("allAddMethod()")表示allAddMethod方法所在的Pointcut织入该advice,且是before advice,即方法前执行。

 

spring配置:

 

测试:

输出:

 

 

 

Advice使用within表达式,可以将Pointcut表达式写到Advice的注解上,不用定义Pointcut的空方法,如:

 

 

Advice使用@Around表示自定义方法前后执行代码,如:

其中pjp.proceed()是执行目标方法。

使用示例

使用自定义spring aop做http权限校验

定义一个权限注解,controller上加了该注解的方法需要校验权限,没加的不校验。

校验权限的方式是判断request中的参数token是否是指定值123456,如果不是,校验失败,如果是校验通过。

 

注解接口定义:

 

切面类:

其中:

    @Pointcut("@annotation(com.f3.annotation.AuthChecker)")表示加了AuthChecker注解的才匹配。

    通过RequestContextHolder获得request,取出token参数校验。

 

Controller:

只有add方法加了@AuthChecker注解,因此只有它会被拦截校验。

 

校验通过:

 

校验不通过:

 

后台打印:

SpringAop实现原理分析

AopPrxoy创建代理对象的过程

ProxyFactory和ProxyFactoryBean都类似,都是自动创建InvocationHandler或者MethodInterceptor,返回代理对象,只不过一个是手动去调用,一个是在getBean的时候调用。这里以ProxyFactoryBean为例。

ProxyFactoryBean在getBean时会调用它的getObject()方法返回代理类,它内部会根据用户配置去创建InvocationHandler(JDK动态代理)或者MethodInterceptor(CGlib代理),然后调用Proxy.newProxyInstance或者enhancer.create()创建代理对象返回。

 

创建代理对象的类需要实现AopProxy接口,它有一个getProxy方法:

 

它的实现有JdkDynamicAopProxy和CglibAopProxy,分别用jdk动态代理和cglib代理生成代理对象。

 

 

 

 

ProxyFactoryBean的getObject()是调用入口,它是由getBean()触发的。

 

 

 

可以看到,创建AopProxy是由AopProxyFactory完成的。

 

使用的默认实现DefaultAopProxyFactory

这个方法里会判断优先使用用户配置,如果没有,就判断目标类是否实现了接口,如果实现了接口就用jdk动态代理,如果没有,就用cglib代理。

回调方法的过程

以JdkDynamicAopProxy为例,它实现了InvocationHandler接口,springaop在创建这个对象时,会将配置的Advisor转换成拦截器MethodInterceptor的形式,放入一个list,JdkDynamicAopProxy的invoke方法调用时,会遍历这个拦截器链的list,依次回调每个拦截器的方法,拦截器跟Advice一样,也分目标方法执行前、执行后、异常时等。

 

invoke获取拦截器链:

 

交给advisorChainFactory去执行:

 

 

 

 

 

invoke调用拦截器链:

 

 

这里是递归回调拦截器链

这里的proceed()类似于filter的doFilterChain方法,而interceptor的invoke方法里面也必定会调用proceed()方法,如:

 

MethodBeforeAdviceInterceptor,先调用增强逻辑,再调用proceed()

 

 

AfterReturningAdviceInterceptor,先调用proceed(),再调用增强逻辑。

 

ThrowsAdviceInterceptor,将proceed放到try块中,产生异常时,在catch中执行增强逻辑,然后抛出异常。

 

 

 

 

 

 

 

 

 

 

数据库事务

数据库事务可以保证数据一致性,比如一次操作2张表的情况。jdbc中默认每次sql执行后都自动提交,相当于没有事务,自动提交是connection的autoCommit决定的,可以通过connection.getAutoCommit()查看。

 

设置jdbc的事务只需要设置connection.setAutoCommit(false);,然后在sql执行成功后执行connection.commit();,失败执行connection.rollback();即可。

 

通常格式如下:

Class.forName("com.mysql.jdbc.Driver");

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "123456");

connection.setAutoCommit(false);

try {

    PreparedStatement ps = connection.prepareStatement("update users set name='kkk666' where id=15");

    ps.executeUpdate();

            

    connection.commit();

} catch (Exception e) {

    connection.rollback();

}

connection.close();

 

在开启事务执行了update等锁记录的操作时,commit或者rollback之前不会释放锁。

虽然mysql在连接断开的时候mysql会自动回滚,但使用了连接池的应用中连接不会自动断开,因此需要手动rollback来释放锁。

如:

程序中开启了事务且执行了sql锁住了1条记录,在navicat中操作该记录就一直等待锁。

 

spring aop集成声明式事务

编程式事务需要手动提交、回滚事务,将对事务的操作提取出来作为横切性问题处理,使用代理可以做到,使用spring的aop可以也做到,spring对声明式事务做了集成,连切面类里面的提交、回滚事务方法都不用写了,只需要配置上就行了。

 

DataSource的说明

使用spring aop声明式事务需要配置事务管理器transactionManager,而它依赖于DataSource,DataSource是一个接口,主要用于数据库连接池,它的常用实现有c3p0、dbcp、阿里的druid等,也可以自己实现,只需要实现getConnection方法返回连接即可。

 

 

JdbcTemplete的说明

由于需要在目标方法前后开启、提交、回滚事务,而这些操作需要用到connection对象,因此connection对象的创建不能交给用户自己创建,需要交给spring,以往基于jdk动态代理等自己实现的声明式事务中,常常将connection放到线程ThreadLocal中。

因此用户使用connection的获取也应该使用spring包装好的方法,而spring提供了jdbcTemplate,可以直接调用它的update(String sql)等方法执行sql,不需要关心connection。(如果用mybatis,它也提供类似的sqlTemplate)。

而jdbcTemplate依赖了dataSource,同时transactionManager也依赖了dataSource,它们需要使用同一个dataSource。因此需要将jdbcTemplate和transactionManager都放到spring容器中使用。

 

配置

a.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:mvc="http://www.springframework.org/schema/mvc"

    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util"

    xmlns:context="http://www.springframework.org/schema/context" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd

http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.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"

    default-autowire="byName">

 

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"

        init-method="init" destroy-method="close">

        <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />

        <property name="username" value="root" />

        <property name="password" value="123456" />

        <property name="driverClassName" value="com.mysql.jdbc.Driver" />

    </bean>

 

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

        <property name="dataSource" ref="dataSource" />

    </bean>

 

    <bean id="helloWorld" class="com.test.springaop.HelloWorldImpl" />

 

    <!-- 以下3个是事务相关配置 -->

    <!-- 事务的transactionManager -->

    <bean id="transactionManager"

        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <property name="dataSource" ref="dataSource" />

    </bean>

 

<!—如果使用@Transactional注解,以下2个配置可以去掉 -->

    <!-- 配置事务传播特性 -->

    <tx:advice id="txAdvice" transaction-manager="transactionManager">

        <tx:attributes>

            <tx:method name="opt*" propagation="REQUIRED" />

        </tx:attributes>

    </tx:advice>

 

    <!-- 哪些类哪些方法参与事务 -->

    <aop:config>

        <aop:pointcut id="allMethod" expression="execution(* com.test.springaop.*.*(..))" />

        <aop:advisor pointcut-ref="allMethod" advice-ref="txAdvice" />

    </aop:config>

 

</beans>

 

pom.xml

    <dependencies>

        <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>3.8.1</version>

            <scope>test</scope>

        </dependency>

 

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-context</artifactId>

            <version>4.3.9.RELEASE</version>

        </dependency>

 

        <dependency>

            <groupId>org.aspectj</groupId>

            <artifactId>aspectjweaver</artifactId>

            <version>1.8.4</version>

        </dependency>

 

        <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

            <version>5.1.39</version>

        </dependency>

 

        <dependency>

            <groupId>com.alibaba</groupId>

            <artifactId>druid</artifactId>

            <version>1.0.9</version>

        </dependency>

 

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-jdbc</artifactId>

            <version>4.3.9.RELEASE</version>

        </dependency>

    </dependencies>

 

HelloWorldImpl.java:

package com.test.springaop;

 

import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.springframework.jdbc.core.JdbcTemplate;

 

public class HelloWorldImpl implements HelloWorld {

 

    static ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext("a.xml");

    

    public static void main(String[] args) throws Exception {

        HelloWorld helloWorld = (HelloWorld)factory.getBean("helloWorld");

        helloWorld.optUser();

        

        factory.close();

    }

 

    public void optUser() throws Exception {

        Class.forName("com.mysql.jdbc.Driver");

        JdbcTemplate jdbcTemplate = (JdbcTemplate)factory.getBean("jdbcTemplate");

        jdbcTemplate.update("update users set name='kkk233' where id=15");

        

        // 测试抛出异常后事务回滚

        throw new RuntimeException();

    }

}

 

HelloWorld.java:

package com.test.springaop;

 

public interface HelloWorld {

    

    void optUser() throws Exception;

}

posted @ 2020-12-16 10:47  吴克兢  阅读(154)  评论(0)    收藏  举报