java面试之Spring IOC AOP

Java 面试之Spring AOP IOC

Spring优点

  • 方便解耦,简化开发 Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给Spring管理
  • 方便集成各种框架 Spring可以整合很多框架,比如Mybatis
  • 方便程序的测试 Spring支持JUnit4,可以通过注解很方便的测试Spring程序
  • 支持AOP(面向切面编程) Spring提供了面向切面编程
  • 声明式事务 只需要配置就可以完成对事务的管理,无需手动编程

使用Spring需要导入的jar

  • Spring-core:包含Spring框架基本的核心工具类,Spring的其他组件都要用到这个包中的类,是其他组件的基本核心
  • Spring-beans:包含访问配置文件、创建和管理bean以及进行IOC或者DI操作相关的所有类
  • Spring-context:Spring提供在基础IOC功能上的扩展服务,此外还提供许多企业级服务的支持,例如邮件服务、任务调度等
  • Spring-expression:定义了Spring的表达式语句
  • commons-logging:处理日志信息
  • 使用SPring框架,只需要倒入Spring的四个基础jar和处理日志信息的commons-logging即可

IOC

IOC是指在程序开发过程中,对象实例的创建不再由调用者管理,而是由Spring容器创建,Spring容器会负责控制程序之间的关系,而不是由代码直接控制,因此,控制权由程序代码转移到了Spring容器,控制权发生了反转,即控制反转。 Spring IOC提供了两种IOC容器,分别是BeanFactory和ApplicationContext。

BeanFactory

BeanFactory是基础的IOC容器,是一个接口,提供了完整的IOC服务,BeanFactory是一个管理Bean的工厂,他主要负责初始化各种bean,并调用它们的生命周期方法 BeanFactory接口有多个实现类,最常见的是XmlBeanFactory,它根据Xml配置文件中的定义装配Bean。 创建XmlBeanFactory对象,传入xml文件

1
2
BeanFactory beanFactory = new XmlBeanFactory(newFileSystemResource("D://applicationContext.xml"));
复制代码

ApplicationContext

ApplicationContext是BeanFactory的字接口,也被称为应用上下文,不仅提供了BeanFactory的所有功能,还添加了对国际化、资源访问、事件传播等方面的支持。

ApplicationContext提供了两个实现类

  • ClassPathXmlApplicationContext 该类从类路径ClassPath中寻找制定的xml文件
1
2
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
复制代码

configLocation参数用来指定Spring配置文件的名称和位置

  • FileSystemXmlApplicationContext 该类从指定的文件系统路径中寻找指定的xml文件
1
2
3
* ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

复制代码

ClassPathXmlApplicationContext和FileSystemXmlApplicationContext的区别 在读取Spring的配置文件时,ClassPathXmlApplicationContext从ClassPath中读取配置文件,而FileSystemXmlApplicationContext通过configLocated参数的值从文件系统中读取xml文件。

在Java项目中,通常使用ClassPathXmlApplicationContext类实例化ApplicationContext容器,而在web项目中,ApplicationContext容器的实例化工作交给web服务器完成,web服务器实例化ApplicationContext容器通常使用基于ContextLoaderListener实现的方式,只需要编辑web。xml文件即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--spring将加载spring目录下的applicationContext.xml文件-->
<param-value>
classpath:spring/applicationContext.xml
</param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
复制代码

BeanFactory和ApplicationContext的区别 如果Bean的某一个属性没有注入,则使用BeanFactory加载后,在第一次调用getBean方法时会抛出异常,而ApplicationContext会在初始化时检查,有利于检查所依赖的属性是否注入。

依赖注入(DI)

依赖注入和控制反转含义相同,他们是从两个角度描述同一个概念。 当某个对象实例需要另外一个对象实例时,传统的方法是由调用者创建被调用者的实例,比如使用new,而使用Spring框架后,被调用者的实例不再有调用者创建,而是交给了Spring容器,者称为控制反转。 Spring容器在创建被调用实例时,会自动将调用者需要的对象实例注入为调用者,这样,通过 Spring容器获得被调用者实例,成为依赖注入。

依赖注入的实现方式

  • 属性setter注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//创建类A
public class A {
public A() {
System.out.println("正在初始化类A,调用无参构造器A。。。");
}
public void print() {
System.out.println("调用了类A的print方法。。。");
}
}
//创建类B
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
public void print() {
a.print();
}
}
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.A"/>
<bean id="b" class="com.ssm.ioc.B">
<property name="a" ref="a"/>
</bean>
//测试
@Test
public void test() {
B b = (B) context.getBean("b");
b.print();
}
复制代码
  • 构造构造器注入
1
2
3
4
5
6
7
8
9
10
11
12
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.A"/>
<bean id="b" class="com.ssm.ioc.B">
<constructor-arg name="a" ref="a"/>
</bean>
//测试
@Test
public void test() {
B b = (B) context.getBean("b");
b.print();
}
复制代码
  • 静态工厂方式实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建工厂类
public class MyBeanFactory {
public static A createCoffer() {
return new A();
}
}
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.MyBeanFactory" factory-method="createA"/>
//测试
@Test
public void test() {
A a = (A) context.getBean("a");
}
复制代码
  • 实例工厂实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建工厂类
public class MyBeanFactory {
public MyBeanFactory() {
System.out.println("A工厂实例化中。。。");
}
public A createBean() {
return new A();
}
}
//配置Beans.xml
<bean id="myBeanFactory" class="com.ssm.ioc.MyBeanFactory"/>
<bean id="a" factory-bean="myBeanFactory" factory-method="createBean"/>
//测试
@Test
public void test() {
A a = (A) context.getBean("a");
}
复制代码

Bean的会话状态

  • 有状态会话的Bean 每个用户有自己特有的一个实例,在用户的生存期内,bean保持可用户的信息,即”有状态”,一旦用户灭亡(调用结束或实例结束),bean的生命周期也会结束。
  • 无状态会话的Bean bean一旦被实例化就被加到会话池中,各个用户可以公用,即使用户死亡,bean的生命周期也不一定结束,他可能依然存在于会话池中,供其他用户使用,由于没有特定的用户,也就没办法保存用户的状态,所以叫无状态Bean,但无状态Bean并非没有状态,如果它有自己的属性,那么这些属性就会受到所有调用它的用户的影响。

Servlet的线程安全问题

Servlet体系结构是建立在Java多线程机制上的,它的生命周期是由web容器负责的,一个Servlet类Application中只有一个实例存在,也就是有多个线程在使用这个实例,这是单例模式的使用。Servlet本身是无状态的,无状态的单例是线程安全的,但是如果在Servlet中使用了实例变量,那么就会变成有状态了,是非线程安全的。

如何解决Servlet的线程安全问题

  • 避免使用实例变量
  • 避免使用非线程安全集合
  • 在多个Servlet中对某个外部对象的修改进行加锁操作

Spring中Bean的作用域

  • 在Spring配置文件中,使用的scope属性设置Bean的作用域
  • singleton 单例模式,使用singleton定义的Bean在Spring容器中只有一个实例,这也是Bean的默认作用域,所有的Bean请求,只要id与该Bean定义相匹配,就只会返回Bean的同一个实例。适用于无回话状态的Bean,例如(DAO层、Service层)。
  • prototype 原型模式,每次通过Spring容器获取prototype定义的Bean时,容器都会创建一个新的Bean实例,适用于需要需要保持会话状态的Bean(比如Struts2的Action类)。
  • request 在一次HTTP请求中,容器会返回该Bean的同一个实例,而对于不同的HTTP请求,会返回不同的实例,该作用域仅在当前HttpRequest内有效
  • session 在一次HttpSession中,容器会返回该Bean的同一个实例,而对于不同的HTTP请求,会返回不同的实例,该作用域仅在当前HttpSession内有效
  • global Session 在一个全局的session中,容器会返回该Bean的同一个实例,该作用域仅在使用portlet context时有效。

Spring Bean的生命周期

  • Spring容器可以管理singleton作用域Bean的生命周期,在此作用域下,Spring能够精确的知道该Bean何时被创建,何时初始化完成,以及何时被销毁。
  • 而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给客户端代码管理,Spring容器不再跟踪其生命周期,每次客户端请求prototype作用域的bean时,Spring容器都会创建一个新的Bean,并且不会管那些被配置成prototype作用域的Bean的生命周期。

当一个Bean被加载到Spring容器时,就具有了生命周期,而Spring容器在保证一个Bean能够使用之前,会进行很多工作

01

Bean的生命周期的执行过程
  • 根据配置文件的配置调用Bean的构造方法或者工厂方法实例化Bean
  • 利用依赖注入完成Bean中所有属性值的配置注入
  • 如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName方法传入当前Bean的id值
  • 如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory方法传入当前工厂实例的引用
  • 如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext方法传入当前ApplicationContext实例的引用
  • 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessBeforeInitialization对Bean进行加工,Spring AOP就是利用它实现的
  • 如果Bean实现了InitializingBean接口,则Spring调用afterPropertiesSet方法
  • 如果在配置文件中通过init-method属性指定了初始化方法,则调用该初始化方法
  • 如果BeanPostProcessor 和Bean关联,则Spring将调用该接口的初始化方法postPrecessAfterInitialization,此时,Bean已经可以使用
  • 如果在中指定了该Bean的作用范围为scope=”singleton”,则将该Bean放入Spring IOC的缓存池中,将触发Spring对该Bean的生命周期管理,若在中指定了该Bean的作用范围为scope=”prototype”,则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不再管理该Bean
  • 如果Bean实现了DisposableBean接口,则Spring会调用destroy方法将Spring中的Bean销毁,如果在配置文件中通过destroy-method属性指定了Bean的销毁方法,则Spring将调用该方法对Bean进行销毁

Spring基于XML装配Bean

设值注入和构造器注入

在实例化Bean的过程中,首先调用默认的构造方法实例Bean对象,然后通过java的反射机制调用setter方法对属性进行注入,因此设置注入要求一个Bean的对应类必须满足以下两点要求

  • 必须提供一个默认的无参构造方法
  • 必须为需要注入的属性提供setter方法

使用设值注入时,在Spring配置文件中,需要使用元素的子元素元素为每个属性注入值,而使用构造注入时,在配置文件中,主要使用标签定义构造方法的参数,可以使用value属性设置该参数的值。

Annotation(注解)注入

JDK1.5之后,提供了Annotation功能,Spring也提供了对Annotation的全面支持,Spring3中定义了一系列注解

  • @Component:可以使用此注解描述Spring中的Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以用在任何层次
  • @Repository:用于将数据访问层(DAO层)的类标示为Spring中的Bean
  • @Service:通常用作在业务层(Service层),用于将业务层的类标示为Spring中的Bean
  • @Controlle:通常作用在控制层,用于将控制层的类标示为Spring中的Bean
  • @Autowired:用于Bean的属性变量、属性的Set方法及构造函数进行标注,配合对应的注解处理器完成Bean的自动配置工作,默认按照Bean的类型进行装配
  • @Resource:作用与Autowired一样,区别是@Autowired默认按照Bean的类型装配,@而Resource默认按照Bean的实例类型进行装配,@Resource有name、type属性,Spring将name属性解析为Bean实例名称,将type属性解析为Bean的梳理类型,如果指定name属性,则按照实例名称进行装配,如果指定type属性,按照Bean类型进行装配,如果都不指定,则先按照Bean实例名称装配,如果不能装配,则按照Bean的类型进行装配,如果都不能匹配,抛出NoSuchBeanDefinitionException异常
  • @Qualifier:与@Autowired配合使用,会将默认的按照Bean配型装配修改为按Bean的实例名称装配,Bean的实例名称由@qualifier注解的参数指定

@Resource和@Autowired的区别

  • @Resource和@Autowired都是做bean的注入时使用
  • @Resource不是Spring中的注解,但是Spring支持该注解,而@Autowired是Spring的注解
  • @Autowired是按照类型(byType)来装配Bean的,不回去匹配name,默认情况下他要求依赖对象必须存在,如果需允许null,可以设置它的required属性为false,如果想让@Autowired按照名称(byName)来装配,则需要配合@Qualifier一起使用

@Resource注解装配步骤

  • 如果同时指定了name和type属性,则从Spring上下文中找到唯一匹配的Bean进行装配,找不到抛出异常
  • 如果指定了name,则从上下文中查找名称(id)匹配的Bean进行装配,找不到抛出异常
  • 如果指定了type,则从上下文查找类型匹配的唯一Bean进行装载,找不到或者是找到了多个,抛出异常
  • 如果即没有指定name,也没有指定type,则默认按照byName进行查找并装载,如果没有匹配,则按照byType进行匹配并装载

自动装配

自动装配是指Spring容器可以自动装配(autowire)相互协作的Bean之间的关系,将一个Bean注入其他Bean的Property中。 要使用自动装配,就需要配置元素的autowire值,autowire属性有五个

  • byName:根据Property的name自动装配,如果一个Bean的name和另外一个Bean中的Property相同,则会自动装配这个Bean到Property中
  • byType:根据Property的数据类型自动装配,如果一个Bean的数据类型兼容另外一个Bean中Property的数据类型,则自动装配
  • constructor:根据构造方法的参数的数据类型,进行byType模式的自动装配
  • autodetect:如果发现默认的构造方法,则用constructor,否则有byType
  • no:默认情况下,不实用自动装配,Bean依赖必须通过ref元素定义

AOP面向切面编程

AOP概述

  • AOP和OOP类似,也是一种编程模式,Spring AOP是基于AOP编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间解耦的作用。
  • AOP的全程是”Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各部分进行隔离,使开发人员在编写业务逻辑时可以专心核心业务,从而提高开发基础。
  • AOP采取横向抽取机制,取代了传统的纵向继承体系,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
  • 目前最流行的AOP有Spring AOP和AspectJ
  • Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
  • AspectJ是一个基于Java语言的AOP框架,从Spring2.0开始,Spring AOP引入了对AspectJ的支持。

AOP相关术语

名称 说明
Joinpoint(连接点) 指那些被拦截到的点,在Spring中,可以被动态代理拦截目标类的方法
Pointcut(切入点) 指要对哪些Joinpoint进行拦截,即被拦截的连接点
Advice(通知) 指拦截到Joinpoint之后要做的事情,即对切入点增强的内容
Target(目标) 指代理的目标对象
Weaving(植入) 指把增强代码应用到目标上,生成代理对象的过程
Proxy(代理) 指生成的代理对象
Aspect(切面) 切入点和通知的结合

JDK动态代理是通过JDK中的Java.lang.reflect.Proxy实现的

使用JDK中的Proxy实现动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//创建接口
public interface CustomerDao {
public void add(); // 添加
public void update(); // 修改
public void delete(); // 删除
public void find(); // 查询
}

//创建接口实现类
public class CustomerDaoImpl implements CustomerDao {
public void add() {
System.out.println("添加客户...");
}
public void update() {
System.out.println("修改客户...");
}
public void delete() {
System.out.println("删除客户...");
}
public void find() {
System.out.println("修改客户...");
}
}

//创建切面类
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}

//创建代理类
import com.mengma.dao.CustomerDao;
import com.mengma.dao.CustomerDaoImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyBeanFactory {
public static CustomerDao getBean() {
//准备目标类
final CustomerDao customerDao = new CustomerDaoImpl();
//创建切面类实例
final MyAspect myAspect = new MyAspect();
//使用代理类,进行增强
return (CustomerDao) Proxy.newProxyInstance(
//当前类加载器
MyBeanFactory.class.getClassLoader(),
//所创建实例的实现类接口
new Class[]{CustomerDao.class},
//需要增强的方法
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myAspect.myBefore();
Object obj = method.invoke(customerDao, args);
myAspect.myBefore();
return obj;
}
}
);
}
}

//创建测试类
import com.mengma.dao.CustomerDao;
import com.mengma.jdk.MyBeanFactory;
import org.junit.Test;
public class TestAOP {
@Test
public void test() {
CustomerDao customerDao = MyBeanFactory.getBean();
customerDao.add();
customerDao.delete();
customerDao.find();
customerDao.update();
}
}
复制代码

输出结果 evernotecid://73D3823C-134C-4C14-AE11-0F871EA0D1DE/appyinxiangcom/27476071/ENResource/p4

02

在调用目标类的方法前后,成功调用了增强的代码

使用JDK自带的Proxy实现动态代理的局限性

  • JDK动态代理必须要实现一个或多个接口,如果不希望实现接口,可以使用CGLIB代理
  • CGLIB代理底层是通过使用一个小而快的字节码处理框架ASM(Java 字节码操控框架)转换字节码并生成新的类。因此CGLIB要依赖于ASM的jar包
  • Spring的core核心包中已经集成了CGLIB所需要的包

CGLIB实现动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//创建目标类
public class GoodsDao {
public void add() {
System.out.println("添加商品...");
}
public void update() {
System.out.println("修改商品...");
}
public void delete() {
System.out.println("删除商品...");
}
public void find() {
System.out.println("修改商品...");
}
}

//创建切面类
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}

//创建代理类
import com.mengma.dao.GoodsDao;
import com.mengma.jdk.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyBeanFactory {
public static GoodsDao getBean() {
//创建目标类
final GoodsDao goodsDao = new GoodsDao();
//创建切面类实例
final MyAspect myAspect = new MyAspect();
//生成代理类,CGLIB在运行时,生成指定对象的子类,增强
Enhancer enhancer = new Enhancer();
//确定需要增强的类
enhancer.setSuperclass(goodsDao.getClass());
//添加回调函数
enhancer.setCallback(new MethodInterceptor() {
//intercept相当于jdk invoke
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
myAspect.myBefore();
Object obj = method.invoke(goodsDao, args);
myAspect.myAfter();
return obj;
}
});
GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
return goodsDaoProxy;
}
}

//创建测试类
@Test
public void testCGLIB() {
GoodsDao goodsDao = com.mengma.cglib.MyBeanFactory.getBean();
goodsDao.add();
goodsDao.delete();
goodsDao.find();
goodsDao.update();
}
复制代码

运行结果

03

Spring通知类型

通知(Advice)是对目标切入点进行增强的内容。Spring为通知(Advice)提供了Advice接口,Spring通知按照在目标类方法的连接点位置,可分为五种类型

名称 说明
MethodBeforeAdvice(前置通知) 在方法之前自动执行的通知成为前置通知,可以应用于权限管理功能
AfterReturningAdvice(后置通知) 在方法执行之后自动执行的通知成为后置通知,可用于关闭流、上传文件、删除临时文件等功能
MethodInterceptor(环绕通道) 在方法前后自动执行的通知成为环绕通知,可以应用于日志、事务管理等功能
ThrowsAdvice(异常通知) 在方法抛出异常时自动执行的通知成为异常通知,可以应用于处理异常记录日志等功能
IntroductionInterceptor(引介通知) 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)

生命Spring AOP

Spring创建了一个AOP代理的基本方法是使用ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容

ProxyFactoryBean类中的常用可配置属性
属性名称 描述
target 代理的目标对象
proxyInterfaces 代理要实现的接口,如果有多个接口,则可以使用list标签赋值
proxyTargetClass 是否对类代理而不是接口,设置为true时,使用CGLIB代理
interceptorNames 需要植入目标的Advice
singleton 返回的代理是否为单例,默认为true(单例)
optimize 设置为true时,强制使用CGLIB

环绕通知案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//倒入Spring-aop依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
复制代码
//创建切面类
//需要实现接口,确定哪个通知,及告诉Spring应该执行哪个方法
public class MyAspect implements MethodInterceptor {
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("方法执行之前");
// 执行目标方法
Object obj = mi.proceed();
System.out.println("方法执行之后");
return obj;
}
}
复制代码

在上述代码中,MyAspect实现了MethodInterceptor接口,并重写了invoke方法,MethodInterceptor是Spring AOP的jar包提供的,而invoke方法用于确定目标方法mi,并告诉Spring要在目标方法前后执行哪些方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//创建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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--目标类 -->
<bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl"/>
<!-- 通知 advice -->
<bean id="myAspect" class="com.mengma.factorybean.MyAspect"/>
<!--生成代理对象 -->
<bean id="customerDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!--代理实现的接口 -->
<property name="proxyInterfaces" value="com.mengma.dao.CustomerDao"/>
<!--代理的目标对象 -->
<property name="target" ref="customerDao"/>
<!--用通知增强目标 -->
<property name="interceptorNames" value="myAspect"/>
<!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
复制代码

首先配置了目标类和通知,然后使用ProxyFactoryBean类生成代理对象。

1
2
3
4
5
6
7
8
9
10
11
//创建测试类
@Test
public void testAOP() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDaoProxy");
customerDao.update();
customerDao.find();
customerDao.delete();
customerDao.add();
}
复制代码

执行结果

04

Spring使用AspectJ开发AOP,基于XML和Annotation

使用AspectJ开发AOP的两种方式
  • 基于XML的声明式
  • 基于Annotation的声明式
基于XML的声明式开发

基于XML的声明式是指通过Spring配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在aop:config元素中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//倒入spring-aspects依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
复制代码
//创建切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
//切面类
public class MyAspect {
// 前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知,目标:");
System.out.print(joinPoint.getTarget() + "方法名称:");
System.out.println(joinPoint.getSignature().getName());
}
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
}
// 环绕通知
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
System.out.println("环绕开始"); // 开始
Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
System.out.println("环绕结束"); // 结束
return obj;
}
// 异常通知
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知" + "出错了" + e.getMessage());
}
// 最终通知
public void myAfter() {
System.out.println("最终通知");
}
}
复制代码
//创建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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--目标类 -->
<bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl"/>
<!--切面类 -->
<bean id="myAspect" class="com.mengma.aspectJ.xml.MyAspect"/>
<!--AOP 编程 -->
<aop:config>
<aop:aspect ref="myAspect">
<!--配置切入点,通知最后增强哪些方法-->
<aop:pointcut expression="execution ( * com.mengma.dao.*.* (..))" id="myPointCut"/>
<!--前置通知,关联通知Advice和切入点PointCut-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!--后置通知,在方法返回后执行,就可以获得返回值returning属性-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="joinPoint"/>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
<!-- *注意:如果程序没有异常,则不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!--最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
复制代码
//创建测试类
@Test
public void testAspectJForXml() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
CustomerDao customerBean = (CustomerDao) applicationContext.getBean("customerDao");
customerBean.add();
}
复制代码

执行结果

05

基于Annotation的声明式

AspectJ允许使用注解定义切面、切入点和增强处理,而Spring框架可以识别并根据注解生成AOP代理

名称 说明
@Aspect 用于定义一个切面
@Before 用于定义前置通知,相当于BeforeAdvice
@AfterReturning 用于定义后置通知,相当于AfterReturningAdvice
@Around 用于定义环绕通知,相当于MethodInterceptor
@AfterThrowing 用于定义抛出异常,相当于ThrowAdvice
@After 用于定义最终final通知,不管是否异常,该通知都会执行
DeclareParents 用于定义引介通知,相当于IntroductionInterceptor
开发案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//创建切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//切面类
@Aspect
@Component
public class MyAspect {
// 用于取代:<aop:pointcut
// expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
// 要求:方法必须是private,没有值,名称自定义,没有参数
@Pointcut("execution(*com.mengma.dao..*.*(..))")
private void myPointCut() {
}
// 前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知,目标:");
System.out.print(joinPoint.getTarget() + "方法名称:");
System.out.println(joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning(value = "myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
}
// 环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
System.out.println("环绕开始"); // 开始
Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
System.out.println("环绕结束"); // 结束
return obj;
}
// 异常通知
@AfterThrowing(value = "myPointCut()", throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知" + "出错了" + e.getMessage());
}
// 最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知");
}
}
复制代码
//在CustomerDaoImpl目标类中添加注解@Repository("customerDao")
复制代码
//创建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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描含com.mengma包下的所有注解-->
<context:component-scan base-package="com.mengma"/>
<!-- 使切面开启自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
复制代码
/。创建测试类
@Test
public void testAspectJForAnnottion() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDao");
customerDao.add();
}
复制代码

执行结果

06

Spring JDBCTemplate

Spring JDBCTemplate概述

  • Spring框架针对数据库开发中的应用提供了JDBCTemplate类,该类是Spring对JDBC支持的核心,提供了对所有数据库操作功能的支持。
  • Spring框架提供的JDBC支持主要由四个包组成,分别是core(核心包)、object(对象包)、dataSource(数据源包)和support(支持包),JdbcTemplate类在核心包中,作为Spring JDBC的核心,JdbcTemplate类包含了所有数据库操作的基本方法。
  • JdbcTemplate类继承自抽象类JdbcAccessor,同时实现了JdbcOperations接口,直接父类JdbcAccessor为子类提供了一些访问数据库时使用的公共属性

JdbcAccessor提供的公共属性

  • DataSource:获取数据库连接,具体实现时还可以引入对数据库连接的缓冲池和分布式事务的支持,它可以作为访问数据库资源的标准接口
  • SQLExceptionTranslator:SQLExceptiontranslator接口负责对SQLException进行转译工作,通过必要的设置或者获取SQLExceptionTranslator中的方法,可以使JdbcTemplate在需要处理SQLException时,委托SQLExceptionTranslator的实现类完成相关的转译工作。
  • JdbcOperations接口定义了在JdbcTemplate类中可以使用的操作集合,包括添加、修改、查询和删除工作

Spring中JDBC的相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.dataSource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--连接数据库的url-->
<property name= "url" value="jdbc:mysql://localhost/spring" />
<!--连接数据库的用户名-->
<property name="username" value="root" />
<!--连接数据库的密码-->
<property name="password" value="root" />
</bean>
<!--配置JDBC模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.jdbcTemplate">
<!--默认必须使用数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置注入类-->
<bean id="xxx" class="xxx">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
...
</beans>
复制代码

Spring事务管理

Spring的事务管理是基于AOP实现的,而AOP是以方法为单位的,Spring的事务属性分别为传播行为、隔离界别、只读和超时属性,这些属性提供了事务的方法和描述策略

Spring事务管理的三个核心接口
  • PlatformTransactionManager:Spring提供的平台事务管理器,用于管理事务,提供了三个方法:getTransaction用于获取事务状态信息,commit用于提交事务,rollback用于事务的回滚,在项目中,Spring将xml中配置的事务详细信息封装到对象TransactionDefinition中,然后通过事务管理器的getTransaction方法获取事务的状态(TransactionStatus),并对事务进行下一步的操作
  • TransactionDefinition接口是事务定义的接口,提供了获取事务相关信息的方法
方法 描述
getName 获取事务对象名称
getIsolationLevel 获取事务的隔离级别
getPropagationBehavior 获取事务的传播行为
getTimeout 获取事务的超时时间
isReadOnly 获取事务是否只读

事务的传播行为是指在同一个方法中,不同操作前后所使用的事务

属性名称 描述
PROPAGATION_REQUIRED required 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将创建新事务
PROPAGATION_SUPPORTS supports 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将以非事务状态执行
PROPAGATION_MANDATORY mandatory 支持当前事务。如果 A 方法没有事务,则抛出异常
PROPAGATION_REQUIRES_NEW requires_new 将创建新的事务,如果 A 方法已经在事务中,则将 A 事务挂起
PROPAGATION_NOT_SUPPORTED not_supported 不支持当前事务,总是以非事务状态执行。如果 A 方法已经在事务中,则将其挂起
PROPAGATION_NEVER never 不支持当前事务,如果 A 方法在事务中,则抛出异常
PROPAGATION.NESTED nested 嵌套事务,底层将使用 Savepoint 形成嵌套事务

在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会改变事务的传播行为,所以不需要进行事务管理,而对于数据的增加、修改和删除等操作,必须进行事务管理,如果没有指定事务的传播行为,Spring默认的传播行为是required。

  • TransactionStatus,描述事务的状态,它描述了某一时间点上事务的状态信息,包含六个操作
名称 说明
flush 刷新事务
hasSavepoint 获取事务是否在保存点
isCompleted 获取事务是否完成
isNewTransaction 获取是否是新事务
isRollbackOnly 获取是否回滚
setRollbackonly 设置事务回滚
Spring的事务管理由两种方式
  • 传统的编程式事务管理,通过编写代码实现的事务管理
  • 基于AOP技术实现声明式的事务
基于AOP的声明式事务的实现方式
  • 基于XML方式的声明式事务
  • 通过Annotation注解方式的事务管理

基于XML方式的声明式事务

以银行转帐为例展示Spring的声明式事务

1
2
3
4
5
6
7
8
9
10
11
//新建一个数据库
CREATE DATABASE spring;
USE spring;
CREATE TABLE account (
id INT (11) PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
money INT DEFAULT NULL
);
INSERT INTO account VALUES (1,'zhangsan',1000);
INSERT INTO account VALUES (2,'lisi',1000);
复制代码

07

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//创建 c3p0-db.properties
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.jdbcUrl = jdbc:mysql://localhost:3306/spring
jdbc.user = root
jdbc.password = root
复制代码
//实现 DAO
//创建 AccountDao 接口
//创建一个名为 com.mengma.dao 的包,在该包下创建一个接口 AccountDao
public interface AccountDao {
// 汇款
public void out(String outUser, int money);
// 收款
public void in(String inUser, int money);
}
//创建DAO层接口实现类
//创建一个名为 com.mengma.dao.impl 的包,在该包下创建实现类 AccountDaoImpl
import org.springframework.jdbc.core.JdbcTemplate;
import com.mengma.dao.AccountDao;
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 汇款的实现方法
public void out(String outUser, int money) {
this.jdbcTemplate.update("update account set money =money-? where username =?", money, outUser);
}
// 收款的实现方法
public void in(String inUser, int money) {
this.jdbcTemplate.update("update account set money =money+? where username =?", money, inUser);
}
}
复制代码
//实现 Service
//创建 Service 层接口
//创建一个名为 com.mengma.service 的包,在该包下创建接口 AccountService
public interface AccountService {
// 转账
public void transfer(String outUser, String inUser, int money);
}
//创建 Service 层接口实现类
//创建一个名为 com.mengma.service.impl 的包,在该包下创建实现类AccountServiceImpl

import com.mengma.dao.AccountDao;
public class AccountServiceImpl {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
this.accountDao.in(inUser, money);
}
}
复制代码
//创建 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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:c3p0-db.properties" />
<!-- 配置数据源,读取properties文件信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.mengma.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.mengma.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 事务管理器,依赖于数据源 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
<tx:method name="find*" propagation="SUPPORTS"
rollback-for="Exception" />
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
read-only="false" />
</tx:attributes>
</tx:advice>
<!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.mengma.service.*.*(..))"
id="txPointCut" />
<!-- 切面:将切入点与通知整合 -->
<aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
</aop:config>
</beans>
复制代码
// 创建测试类
public class AccountTest {
@Test
public void test() {
// 获得Spring容器,并操作
String xmlPath = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
AccountService accountService = (AccountService) applicationContext
.getBean("accountService");
accountService.transfer("zhangsan", "lisi", 100);
}
}
复制代码

执行结果

08

模拟转账失败

1
2
3
4
5
6
7
8
//将transfer方法修改为如下
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
//模拟断电
int i = 1/0;
this.accountDao.in(inUser, money);
}
复制代码

执行结果

09

由此可知,表中数据并没有发生改变,则表示事务不能正常提交,Spring事务管理生效

基于Annotation实现声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//修改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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.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-2.5.xsd
">
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:c3p0-db.properties"/>
<!--扫描含com.mengma包下的所有注解-->
<context:component-scan base-package="com.mengma"/>
<!-- 配置数据源,读取properties文件信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 事务管理器,依赖于数据源 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注册事务管理驱动 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
复制代码
//加注解
import com.mengma.dao.AccountDao;
import com.mengma.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;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
this.accountDao.in(inUser, money);
}
}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import com.mengma.dao.AccountDao;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 汇款的实现方法
public void out(String outUser, int money) {
this.jdbcTemplate.update("update account set money =money-? where username =?", money, outUser);
}
// 收款的实现方法
public void in(String inUser, int money) {
this.jdbcTemplate.update("update account set money =money+? where username =?", money, inUser);
}
}
复制代码

执行结果和使用XML实现结果相同


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!