文章目录
前言
spring是全栈的轻量级的开源框架,本文就通过一个简单的案例帮助大家理解其ioc和aop大致是如何实现的。
一、转账案例
首先我们设定一个需求:简单转账功能比如A向B转账
步骤:
- 首先需要先查询一下 A的余额 判断是否能够转钱,如果能够转钱执行下面 如果不能就返回转账失败 余额不足。
- 当余额足够时 查询B的余额将需要转账的钱加上之前的余额 再通过修改语句改变B的余额。
- A的余额为 A原来的余额减去需要转账的钱 然后再调用修改语句改变A的余额。
- 出现异常事务回滚,两句sql要进行事务控制必须用同一个数据库连接对象,要想使用同一个连接对象,因为两个sql在同一个线程中,由此可以得出 线程必须与连接绑定。
流程图
代码结构图
数据库脚本及数据
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for bank
-- ----------------------------
DROP TABLE IF EXISTS `bank`;
CREATE TABLE `bank` (
`card` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '卡号',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`money` decimal(8, 2) NULL DEFAULT NULL COMMENT '余额',
`id` int(0) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of bank
-- ----------------------------
INSERT INTO `bank` VALUES ('6029621011001', '韩梅梅', 13000.00, 1);
INSERT INTO `bank` VALUES ('6029621011000', '李大雷', 7000.00, 2);
SET FOREIGN_KEY_CHECKS = 1;
二、代码结构分析与实现
1.control层 一个接口用于实现转账
代码如下(示例):
package com.my.test.servlet;
import com.my.test.factory.BeanFactory;
import com.my.test.factory.ProxyFactory;
import com.my.test.pojo.Result;
import com.my.test.service.TransferService;
import com.my.test.util.JsonUtils;
import lombok.SneakyThrows;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Administrator
*/
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
// private TransferService transferService = new TransferServiceImpl();
// private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");
// 从工厂获取委托对象(委托对象是增强了事务控制的功能)
// // 首先从BeanFactory获取到proxyFactory代理工厂的实例化对象
private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
// private TransferService transferService = (TransferService) proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;
private TransferService transferService = (TransferService) proxyFactory.getCglibProxy(BeanFactory.getBean("transferService")) ;
@SneakyThrows
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 设置请求体的字符编码
req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
double money = Double.parseDouble(moneyStr);
Result result = new Result();
try {
// 2. 调用service层方法
transferService.transfer(fromCardNo,toCardNo, money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
}
2.server层用于实现具体逻辑
代码如下(示例):
package com.my.test.service.impl;
import com.my.test.dao.AccountDao;
import com.my.test.service.TransferService;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author Administrator
*/
@Data
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao;
// private TransactionManager transactionManager;
/**
* 转账交易
* @param senderCard
* @param receiveCard
* @param money
*/
@Override
public void transfer(String senderCard, String receiveCard, Double money) throws Exception {
// try {
// // 开启事务
// transactionManager.beginTransaction();
// 查询出需要发起转账人的钱
String querySql = "select money from bank where card = ?";
List list = new ArrayList<Object>();
list.add(senderCard);
Double sendMoney = accountDao.queryMoney(querySql, list);
// 修改卡的余额
String updateSql = "update bank set money = ? where card = ?";
list.clear();
list.add(receiveCard);
Double receiveMoney = accountDao.queryMoney(querySql, list);
sendMoney -= money;
receiveMoney += money;
list.clear();
list.add(receiveMoney);
list.add(receiveCard);
// 接收者加钱
accountDao.update(updateSql, list);
// int c = 4/0;
list.clear();
list.add(sendMoney);
list.add(senderCard);
// 发送者减钱
accountDao.update(updateSql, list);
// 事务提交
// transactionManager.commit();
// } catch (Exception e) {
// 事务回滚
// transactionManager.rollback();
// throw e;
// }
}
}
3.dao层用于实现与数据库的交互逻辑(由于没有使用mysql因此在dao层的实现类进行了数据库的操作)
代码如下(示例):
package com.my.test.dao;
import com.my.test.util.ConnectionUtils;
import lombok.Data;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* @author Administrator
*/
@Data
public class AccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
@Override
public Double queryMoney(String sql, List<Object> list) {
return (Double)execute(sql, list);
}
@Override
public Integer update(String sql, List<Object> list) {
return (Integer)execute(sql, list);
}
/**
* 执行操作
* @param sql
* @param list
* @return
*/
private Object execute(String sql, List<Object> list){
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = connectionUtils.getCurrentThreadConn();
stmt = conn.prepareStatement(sql);
// 执行查询
stmt= conn.prepareStatement(sql);
for (int i = 0; i < list.size(); i++) {
stmt.setObject(i+1,list.get(i));
}
if (sql.contains("update")) {
return stmt.executeUpdate();
}
rs = stmt.executeQuery();
// 展开结果集数据库
while(rs.next()){
// 通过字段检索
return rs.getDouble("money");
}
// 完成后关闭
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
// 处理 JDBC 错误
se.printStackTrace();
}catch(Exception e){
// 处理 Class.forName 错误
e.printStackTrace();
}finally{
// 关闭资源
try{
if(rs!=null) {
rs.close();
}
if(stmt!=null) {
stmt.close();
}
}catch(SQLException se2){
}// 什么都不做
}
return null;
}
}
4.dao层用于实现与数据库的交互逻辑(由于没有使用mysql因此在dao层的实现类进行了数据库的操作)
代码如下(示例):
package com.my.test.dao;
import com.my.test.util.ConnectionUtils;
import lombok.Data;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* @author Administrator
*/
@Data
public class AccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
@Override
public Double queryMoney(String sql, List<Object> list) {
return (Double)execute(sql, list);
}
@Override
public Integer update(String sql, List<Object> list) {
return (Integer)execute(sql, list);
}
/**
* 执行操作
* @param sql
* @param list
* @return
*/
private Object execute(String sql, List<Object> list){
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = connectionUtils.getCurrentThreadConn();
stmt = conn.prepareStatement(sql);
// 执行查询
stmt= conn.prepareStatement(sql);
for (int i = 0; i < list.size(); i++) {
stmt.setObject(i+1,list.get(i));
}
if (sql.contains("update")) {
return stmt.executeUpdate();
}
rs = stmt.executeQuery();
// 展开结果集数据库
while(rs.next()){
// 通过字段检索
return rs.getDouble("money");
}
// 完成后关闭
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
// 处理 JDBC 错误
se.printStackTrace();
}catch(Exception e){
// 处理 Class.forName 错误
e.printStackTrace();
}finally{
// 关闭资源
try{
if(rs!=null) {
rs.close();
}
if(stmt!=null) {
stmt.close();
}
}catch(SQLException se2){
}// 什么都不做
}
return null;
}
}
核心解析beanFactory工厂 用于生产bean
package com.my.test.factory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 工厂用于创建对象
* @author Administrator
*/
public class BeanFactory {
private volatile static Map<String, Object> map = new ConcurrentHashMap();
static {
try {
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 创建SAXReader对象
SAXReader reader = new SAXReader();
// 调用read方法
Document doc = null;
doc = reader.read(resourceAsStream);
// 获取根元素
Element root = doc.getRootElement();
// 获取bean标签
List<Element> list = root.selectNodes("//bean");
// <bean id ="accountDao" class="com.my.test.service.impl.TransactionServiceImpl"></bean>
for (Element element : list) {
// accountDao
String id = element.attributeValue("id");
String classPath = element.attributeValue("class");
// 开始利用反射生成对象
Class<?> aClass = Class.forName(classPath);
Object o = aClass.newInstance();
map.put(id, o);
}
// 获取所有baen标签中的property 来实现属性注入 由于简单实现这里只考虑的ref标签的情况
List<Element> propertyList = root.selectNodes("//property");
// <bean id ="accountDao" class="com.my.test.service.impl.TransactionServiceImpl"></bean>
for (Element element : propertyList) {
// accountDao
String name = element.attributeValue("name");
String refName = element.attributeValue("ref");
// 获取当前map中已生成好的对象 需要设置的属性对象
Object o1 = map.get(refName);
String parentId = element.getParent().attributeValue("id");
// 需要设置的类
Object o2 = map.get(parentId);
// 遍历父对象中的所有方法,找到"set" + name
Method[] methods = o2.getClass().getDeclaredMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
// 该方法就是 setAccountDao(AccountDao accountDao)
if (method.getName().equalsIgnoreCase("set" + name)) {
method.setAccessible(true);
method.invoke(o2, o1);
}
}
map.put(parentId, o2);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getBean(String key) {
return map.get(key);
}
}
代理工厂 用于生产具有事务的代理对象
package com.my.test.factory;
import com.my.test.stereotype.Autowired;
import com.my.test.stereotype.Component;
import com.my.test.util.TransactionManager;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理工厂生产代理对象
* @author Administrator
*/
@Component("proxyFactory")
public class ProxyFactory {
@Autowired
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 将传入对象改造成代理对象并实现其事务的逻辑 利用aop思路来实现横向解决代理重复问题
* 通过jdk动态代理 只能代理接口的接口方法
* @param o
* @return
*/
public Object getJdkProxy(Object o) {
return Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
Object result;
try {
// 开启事务 关闭自动提交改成手动提交
transactionManager.beginTransaction();
// 执行接口方法
result = method.invoke(o, objects);
// 执行完毕提交事务
transactionManager.commit();
} catch (Exception e) {
// 发生异常回滚 其实就是将undo.log日志中的记录删除
transactionManager.rollback();
throw e;
}
return result;
}
});
}
/**
* 将传入对象改造成代理对象并实现其事务的逻辑 利用aop思路来实现横向解决代理重复问题
* 通过cglib动态代理 可以代理所有方法
* @param o
* @return
*/
public Object getCglibProxy(Object o) {
return Enhancer.create(o.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
// 开启事务 关闭自动提交改成手动提交
transactionManager.beginTransaction();
// 执行接口方法
result = method.invoke(o, objects);
// 执行完毕提交事务
transactionManager.commit();
} catch (Exception e) {
// 发生异常回滚 其实就是将undo.log日志中的记录删除
transactionManager.rollback();
throw e;
}
return result;
}
});
}
}
ConnectionUtils 连接工具 用于线程与连接的绑定 从而进行事务控制
package com.my.test.util;
import com.my.test.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 事务控制类 分析 因为转账和扣钱是两个操作 会使用不同两次连接 因此我们无法控制事务
* 也就是需要想办法让两次执行都使用同一个数据库连接也就是同一个sqlSession 我们只知道这两个操作在同一个线程中 因此我们可以通过线程与连接的绑定来实现
* 同一个线程要保存变量使用threadLock
* @author Administrator
*/
@Component
public class ConnectionUtils {
private static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
public Connection getCurrentThreadConn() {
// 先从线程中获取
Connection connection = connectionThreadLocal.get();
// 当前线程没有绑定任何连接
if (connection ==null) {
// 先从线程池中获取一个连接
try {
connection = DruidUtils.getInstance().getConnection();
connectionThreadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
}
DruidUtils 数据库连接工具类
package com.my.test.util;
import com.alibaba.druid.pool.DruidDataSource;
/**
* @author Administrator
*/
public class DruidUtils {
private DruidUtils(){
}
private static DruidDataSource druidDataSource = new DruidDataSource();
static {
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/study");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
}
public static DruidDataSource getInstance() {
return druidDataSource;
}
}
TransactionManager 自定义事务管理类
package com.my.test.util;
import com.my.test.stereotype.Autowired;
import com.my.test.stereotype.Component;
import java.sql.SQLException;
/**
* 事务控制处理类 用于事务的开启 事务提交和回滚
* @author Administrator
*/
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启手动事务控制
* @throws SQLException
*/
public void beginTransaction() throws SQLException {
// 将自动提交设置为false
connectionUtils.getCurrentThreadConn().setAutoCommit(false);
}
/**
* 手动提交事务
* @throws SQLException
*/
public void commit() throws SQLException {
// 如果没有报错就设置为提交
connectionUtils.getCurrentThreadConn().commit();
}
/**
* 回滚事务
* @throws SQLException
*/
public void rollback() throws SQLException {
// 报错之后就回滚
connectionUtils.getCurrentThreadConn().rollback();
}
}
启动tomcat 访问http://localhost:8080/index.html 就能进入转账页面 开始转账。也可以进行事务控制。
优化 通过注解注入对象
自定义注解 Component Repository Service 来实现类注入 Autowired实现属性注入 Transactional实现事务控制
添加注解类
添加注解解析工厂
package com.my.test.factory;
import com.my.test.stereotype.*;
import com.mysql.jdbc.StringUtils;
import org.reflections.Reflections;
import org.reflections.scanners.FieldAnnotationsScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ConfigurationBuilder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 注解 beanFactory
* @author Administrator
*/
public class AnnotationBeanFactory {
/**
* 存放对象 key为bean的名称 value为实例对象
*/
private volatile static Map<String, Object> map = new ConcurrentHashMap();
/**
* 存放别名
*/
private volatile static Map<String, String> AliasMap = new ConcurrentHashMap();
static {
try {
//扫描包含com.my.test,包括'com.my.test'开头的包路径,使用默认扫描器
Reflections reflections = new Reflections(new ConfigurationBuilder().forPackages("com.my.test").addScanners(new SubTypesScanner()).addScanners(new FieldAnnotationsScanner()));
// 获取某个包下类型注解为Component对应的类 因为Service和Repository 是Component 演变而来的因此也能扫描到
Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(Component.class);
// 先将所有类放入集合中 实例化
for (Class<?> aClass : typesAnnotatedWith) {
// 不需要注入Service和Repository
if (aClass.isInterface()) {
continue;
}
Object o = aClass.getDeclaredConstructor().newInstance();
// 获取当前类对象的全路径名称
String simpleName = aClass.getSimpleName();
if (simpleName.contains("Impl")) {
simpleName = simpleName.substring(0,aClass.getSimpleName().lastIndexOf("Impl"));
}
String newName = getNewName(aClass);
if (!StringUtils.isNullOrEmpty(newName)) {
// 封装全路径类名与别名的关联
AliasMap.put(simpleName,newName);
simpleName = newName;
}
map.put(simpleName,o);
}
// 属性注入
for (Object object : map.values()) {
// 得到类的Class对象
Class<?> aClass = object.getClass();
// 获取类的类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
// 判断属性是否包含@Autowired注解并且注解值是true表示需要注入
if (declaredField.isAnnotationPresent(Autowired.class) && declaredField.getAnnotation(Autowired.class).required()) {
// 获取到当前属性类的简称
String simpleName = declaredField.getType().getSimpleName();
Method method = getMethod(simpleName,aClass);
String className = AliasMap.get(simpleName);
if (!StringUtils.isNullOrEmpty(className)) {
// 将别作为需要去map中取值的key
simpleName = className;
}
Object o = map.get(simpleName);
if (o==null) {
throw new RuntimeException("注入:"+simpleName+"--失败!");
}
method.invoke(object,o);
}
}
}
// 属性注入完成 开始判断是否需要生成代理对象
for (String key : map.keySet()) {
Object object =map.get(key);
// 得到类的Class对象
Class<?> aClass = object.getClass();
ProxyFactory proxyFactory = (ProxyFactory) map.get("proxyFactory");
// 判断该类是否具有@Transactional注解 这样就需要将该类生成为动态代理的类
if (aClass.isAnnotationPresent(Transactional.class)) {
// 获取类实现的所有接口
Class<?>[] interfaces = aClass.getInterfaces();
// 一个接口都没有 则使用cglib 反之用jdk动态代理
if(interfaces==null || interfaces.length==0) {
object = proxyFactory.getCglibProxy(object);
} else {
object = proxyFactory.getJdkProxy(object);
}
map.put(key,object);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取执行方法
* @param name
* @param aClass
* @return
*/
private static Method getMethod(String name, Class<?> aClass) throws NoSuchMethodException {
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getName().equals("set" + name)) {
return declaredMethod;
}
}
return null;
}
/**
* 返回自定义的名称
* @param aClass
* @return
*/
private static String getNewName(Class<?> aClass) {
Component component = aClass.getAnnotation(Component.class);
Service service = aClass.getAnnotation(Service.class);
Repository repository = aClass.getAnnotation(Repository.class);
if (component!=null) {
if (!StringUtils.isNullOrEmpty(component.value())) {
return component.value();
}
}else if (service!=null) {
if (!StringUtils.isNullOrEmpty(service.value())) {
return service.value();
}
}else if (repository!=null) {
if (!StringUtils.isNullOrEmpty(repository.value())) {
return repository.value();
}
}
return null;
}
public static Object getBean(String key) {
return map.get(key);
}
}
给类加上对应注解
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private ConnectionUtils connectionUtils;
@Component("proxyFactory")
public class ProxyFactory {
@Autowired
private TransactionManager transactionManager;
@Service("transferService")
@Transactional
public class TransferServiceImpl implements TransferService {
@Autowired
private AccountDao accountDao;
*/
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
// private TransferService transferService = new TransferServiceImpl();
// private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");
// 从工厂获取委托对象(委托对象是增强了事务控制的功能)
// // 首先从BeanFactory获取到proxyFactory代理工厂的实例化对象
// private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
// private TransferService transferService = (TransferService) proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;
// private TransferService transferService = (TransferService) proxyFactory.getCglibProxy(BeanFactory.getBean("transferService")) ;
// 从自定义注解解析类获取对象
private TransferService transferService = (TransferService) AnnotationBeanFactory.getBean("transferService") ;
@Component
public class ConnectionUtils
@Component
public class TransactionManager
然后启动运行 任然能进行事务控制
转账前
报错
转账后
转账报错 事务回滚 事务得到了控制