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封装了Advice和Pointcut,Advice和Pointcut是1对1地封装到advisor里面。
Aspect(切面类)
Aspect是一个切面类,由Pointcut和Advice组成,如:
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;
}
浙公网安备 33010602011771号