一.基础案例:
起因:使用spring框架整合DbUtils技术,实现用户转账功能
Account:
public class Account {
private Integer id;
private String name;
private Double money;
public Account() {
}
public Account(Integer id, String name, Double money) {
this.id = id;
this.name = name;
this.money = money;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
编写AccountDao
import com.wsl.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Override
public void outUser(String outUser, Double money) {
String sql = "update account set money= money-? where name=?";
try {
queryRunner.update(sql,money,outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void inUser(String inUser, Double money) {
String sql = "update account set money= money+? where name=?";
try {
queryRunner.update(sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
编写AccountService
import com.wsl.dao.AccountDao;
import com.wsl.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 outUser, String inUser, Double money) {
accountDao.outUser(outUser,money);
accountDao.inUser(inUser,money);
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.wsl"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
</beans>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db
jdbc.username=root
jdbc.password=root
测试案例:
import com.wsl.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.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AServiceTest {
@Autowired
AccountService accountService;
@Test
public void test01(){
accountService.transfer("tom","jerry",200.0);
}
}
上述代码会出现一个问题:如下:
(1)事务控制:
解决方案:将service方法的多个dao层代码,看做一个事务,要么成功,要么都失败
版本一:
修改AccountService:
// 依赖dataSource
@Autowired
private DataSource dataSource;
// 版本一
@Override
public void transfer(String outUser, String inUser, Double money) {
Connection connection = null;
try {
// 获取conn手动控制事务
connection = dataSource.getConnection();
connection.setAutoCommit(false);
// 业务代码-------- start
// 转出
accountDao.outUser(connection,outUser, money);
// 模拟故障
int i = 1 / 0;
// 转入
accountDao.inUser(connection,inUser, money);
// 业务代码-------- end
// 提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
// 回滚事务
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
// 释放资源
try {
connection.setAutoCommit(true);// 再改为自动提交
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
修改AccountDao
// 方法重载
@Override
public void outUser(Connection connection, String outUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money - ? where name = ?";
// 2.执行sql(使用service连接对象)
queryRunner.update(connection,sql,money,outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
// 方法重载
@Override
public void inUser(Connection connection, String inUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money + ? where name = ?";
// 2.执行sql(使用service连接对象)
queryRunner.update(connection,sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
测试:
问题二:
我们不应该将service的conn对象传递到dao层,这种方式,就产生了dao层与service的耦合性问题
(2)ThreadLocal:解决方案:
解决方案
ThreadLocal是一个线程的局部变量
编写ConnectionUtils
/*
ThreadLocal操作的工具类
1. 从当前线程内绑定并获取conn对象
2. 移除当前线程的conn对象
*/
@Component // 交给ioc容器
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
// 储物柜(线程隔离)
private static final ThreadLocal<Connection> tl = new ThreadLocal<>();
// 1.从当前线程内绑定并获取conn对象
public Connection getThreadConnection(){
// 第一次执行get,肯定获取不到的...
Connection connection = tl.get();
if (connection == null){
try {
connection = dataSource.getConnection();
tl.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
// 2.移除当前线程的conn对象
public void removeThreadConnection(){
tl.remove();
}
}
修改AccountService
@Autowired
private ConnectionUtils connectionUtils;
// 版本二
@Override
public void transfer(String outUser, String inUser, Double money) {
Connection connection = null;
try {
// 获取conn手动控制事务
connection = connectionUtils.getThreadConnection();
connection.setAutoCommit(false);
// 业务代码-------- start
// 转出
accountDao.outUser(outUser, money);
// 模拟故障
int i = 1 / 0;
// 转入
accountDao.inUser(inUser, money);
// 业务代码-------- end
// 提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
// 回滚事务
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
// 释放资源
try {
connection.setAutoCommit(true);// 再改为自动提交
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
修改AccountDao
@Autowired
private ConnectionUtils connectionUtils;
@Override
public void outUser(String outUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money - ? where name = ?";
// 获取当前线程内的 conn
Connection threadConnection = connectionUtils.getThreadConnection();
// 2.执行sql
queryRunner.update(threadConnection,sql,money,outUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void inUser(String inUser, Double money) {
try {
// 1.编写sql
String sql = "update account set money = money + ? where name = ?";
// 获取当前线程内的 conn
Connection threadConnection = connectionUtils.getThreadConnection();
// 2.执行sql
queryRunner.update(threadConnection,sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
测试:
在企业开发时,我们基础每一个业务层方法都需要进行事务的控制,这部分代码属于公共业务且重复的,出现了大量的代码冗余
(3)事务管理器
解决方案
这时候我们可以把这部分代码抽取到工具类(事务管理器)
编写TransactionManager
/*
自定义事务管理器工具类
1.开启事务
2.提交事务
3.回滚事务
4.释放资源
*/
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
// 1.开启事务
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
// 2.提交事务
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 3.回滚事务
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 4.释放资源
public void release(){
try {
connectionUtils.getThreadConnection().setAutoCommit(true);// 恢复 自动提交
connectionUtils.getThreadConnection().close();// 归还到连接池
connectionUtils.removeThreadConnection();// 从当前线程删除conn对象
} catch (SQLException e) {
e.printStackTrace();
}
}
}
修改AccountService
//版本三
@Override
public void transfer(String outUser, String inUser, Double money) {
try {
// 开启事务
transactionManager.beginTransaction();
// 业务代码-------- start
// 转出
accountDao.outUser(outUser, money);
// 模拟故障
int i = 1 / 0;
// 转入
accountDao.inUser(inUser, money);
// 业务代码-------- end
// 提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
} finally {
// 释放资源
transactionManager.release();;
}
}
测试:
问题:
二.转账案例进阶:(AOP)
我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样就不会对业务层产生影响,解决了耦合性的问题啦!
修改AccountService代码
企业开发时业务层,只有核心业务代码,不会出现事务相关代码
// 版本四(开发者只需要编写核心业务....)
public void transfer(String outUser, String inUser, Double money) {
// 业务代码-------- start
// 转出
accountDao.outUser(outUser, money);
// 模拟故障
int i = 1/0;
// 转入
accountDao.inUser(inUser, money);
// 业务代码-------- end
}
(1)JDK动态代理
目标对象:
编写JdkProxyFactory
/*
基于jdk,实现对目标对象,进行事务的增强
*/
@Component
public class JdkProxyFactory {
@Autowired
private TransactionManager transactionManager;
public Object createJdkProxyTx(Object target) {
Object proxy = null;
// 使用sun公司提供的jdk代理工具类
/*
1.目标对象类加器
2.目标对象接口数组
3.实现增强的业务逻辑(匿名内部类、lambda)
*/
ClassLoader classLoader = target.getClass().getClassLoader();// 目标对象类加载器
Class<?>[] interfaces = target.getClass().getInterfaces(); // 目标对象实现的接口数组
proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
/*
invoke 方法是代理对象的入口
proxy:jdk工具类生产的代理对象
method:当前用户执行的某个具体方法
args:当前用户执行的某个具体方法传递的实际参数列表
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 开启事务
transactionManager.beginTransaction();
// 执行目标对象原有的功能
result = method.invoke(target, args);
// 提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
}finally {
// 释放资源
transactionManager.release();
}
return result;
}
});
// 返回代理对象(增强后的.....)
return proxy;
}
}
测试:
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {
@Autowired
private AccountService accountService; // 目标对象
@Autowired
private JdkProxyFactory jdkProxyFactory;// jdk生产代理对象工厂
// 测试转账案例
@Test
public void test02() throws Exception {
// 目标对象没有事务增强
// accountService.transfer("tom", "jerry", 100d);
// 使用jdk对目标对象事务增强
AccountService jdkProxy = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
jdkProxy.transfer("tom", "jerry", 100d);
}
}
(2)CGLib动态代理
但是有些代码没有接口,但又需要动态代理进行增强,这时候就需要用CGLib了
目标对象
编写CglibProxyFactory
/*
基于cglib,实现对目标对象,进行事务的增强
*/
@Component
public class CglibProxyFactory {
@Autowired
private TransactionManager transactionManager;
public Object createCglibProxyTx(Object target) {
Object proxy = null;
// 使用cglib提供的工具类
/*
1. 目标对象class类
2. 实现增强的业务逻辑(匿名内部类、lambda)
*/
proxy = Enhancer.create(target.getClass(), new MethodInterceptor() {
/*
intercept 代理对象方法入口
1.o cglib生产出来的代理对象
2.method 执行代理对象(子),被拦截的方法
3.objects 执行代理对象,被拦截的方法的参数列表
4.methodProxy 目标对象(父),被拦截的方法,功能与method一样的
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
// 开启事务
transactionManager.beginTransaction();
// 调用目标对象原有的共
result = method.invoke(target, objects);
// 提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
} finally {
// 释放资源
transactionManager.release();
}
return result;
}
});
return proxy;
}
}
测试:
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {
@Autowired
private AccountService accountService; // 目标对象
@Autowired
private JdkProxyFactory jdkProxyFactory;// jdk生产代理对象工厂
@Autowired
private CglibProxyFactory cglibProxyFactory;// cglib生产代理对象工厂
// 测试转账案例
@Test
public void test02() throws Exception {
// 目标对象没有事务增强
// accountService.transfer("tom", "jerry", 100d);
// 使用jdk对目标对象事务增强
// AccountService jdkProxy = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
// jdkProxy.transfer("tom", "jerry", 100d);
// 使用cglib对目标对象事务增强
AccountService cglibProxy = (AccountService) cglibProxyFactory.createCglibProxyTx(accountService);
cglibProxy.transfer("tom", "jerry", 100d);
}
}
总结:
jdk和cglib两种代理方式的选择?
优先使用jdk,性能高于cglib
1.如果目标对象有接口,一定使用jdk创建代理对象
2.如果目标对象没有接口,没办法只能使用cglib创建代理对象
当核心业务(转账)和通用业务(事务、日志.....)同时出现?
三.SpringAOP简介:
(1)概述
AOP( 面向切面编程 )是一种思想, 它的目的就是在不修改源代码的基础上,对原有功能进行增强。
Spring AOP是对AOP思想的一种实现, Spring底层同时支持jdk和cglib动态代理。
这样做的好处是:
-
在程序运行期间,在不修改源码的情况下对方法进行功能增强
-
逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
-
减少重复代码,提高开发效率,便于后期维护
Spring会根据被代理的类是否有接口自动选择代理方式:
-
如果有接口,就采用jdk动态代理( 当然,也可以强制使用cglib )
-
没有接口就采用cglib的方式
(2)相关术语
在讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:
* Target:目标对象
service层的核心业务(AccountServiceImpl)
* JoinPoint:连接点
目标对象中的所有方法
transfer()
findAll()
* Pointcut:切点
目标对象需要增强的方法
transfer()
* Advice:通知(增强)
实现增强的功能的(TransactionManager)
beginTransaction()
commit()
rollback()
release()
* Weaving:织入
将通知和切点进行织入(动作)
* Aspect:切面(spring术语)
通知 + 切点 = 切面
* Proxy:代理对象(底层实现)
通知 + 切点 = 代理对象
(4)基于xml的AOP开发:
创建maven工程
依赖管理:
<!--依赖管理-->
<dependencies>
<!--spring的核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!--aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
编写目标类AccountService
public interface AccountService {
void transfer();
void save();
}
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账...");
}
@Override
public void save() {
System.out.println("保存...");
}
}
编写通知类 增强的
/*
通知类(增强)
*/
public class MyAdvice {
// 在目标对象方法,执行之前增强....
public void before() {
System.out.println("前置通知....");
}
}
配置spring的aop(切点+通知)
如果spring要使用aop,我们需要再引入schema约束+命名空间
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--目标对象,交给ioc-->
<bean id="accountService" class="cn.wsl.service.impl.AccountServiceImpl"></bean>
<!--通知对象,交给ioc-->
<bean id="myAdvice" class="cn.wsl.advice.MyAdvice"></bean>
<!--aop配置-->
<aop:config>
<!--切面 -->
<aop:aspect ref="myAdvice">
<!--织入(通知+方法)
<aop:before> 前置通知,特点在目标方法执行之前,进行增强
具体执行的增强方法 method="通知的具体方法"
切点:pointcut="切点表达式"
-->
<aop:before method="before" pointcut="execution(public void cn.wsl.service.impl.AccountServiceImpl.transfer())" ></aop:before>
</aop:aspect>
</aop:config>
</beans>
测试:
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
@Autowired
private AccountService accountService; // 配置了aop之后,这就是代理对象了....
@Test
public void test01()throws Exception{
// accountService.save();
accountService.transfer();
}
}