Spring 笔记分享
整体架构
1.1 概述
Spring框架可在任何类型的部署平台上为基于Java的企业应用程序提供全面的编程和配置模型。
Spring的一个关键元素是在应用程序级别的基础框架支持:Spring专注于企业应用程序的“探索”,以便于团队可以专注于应用程序级别的业务逻辑,而不必处理特定的部署环境问题。
Spring可以理解为框架粘合剂,大部分的框架都是不可以相互作用的。Spring提供了这样一个平台,框架只需要向Spring靠拢即可。最终结果就是基于Spring实现了不同框架技术的整合。而且Spring提供了对各大框架的支持,我们可以更加方便的使用。
1.2 优点
Spring的核心是IOC和AOP功能,基于IOC实现对象的依赖和管理,基于AOP实现了事务的控制和管理。
- 简化开发、功能解耦
通过IOC容器,那么对象直接的依赖关系交给Spring管理与控制,而且基于面向接口编程可以使得代码解耦,改变实现类不需要修改引用。 - AOP编码支持
通过AOP功能,可以面向切面编程,传统的基于OOP实现的功能可以通过AOP轻松搞定。 - 声明式事务
@Transactional
事务注解可以帮助我们实现事务控制与管理,而不需要手动进行事务管理。是的事务与代码解耦,声明式事务可以非常灵活的配置,提供开发的效率和质量。 - 方便程序测试
Spring提供了对测试的支持,可以非常方便的构建测试。 - 方便集成框架
前面说过Spring其实是一个粘合剂,可以非常轻松的将各种框架整合到项目中,而且还提供了更加简便的操作方式。 - 降低JavaEE API的使用难度
Spring内部其实对很多的JavaEE API进行了封装,是的我们面的JavaEE编程的时候更加方便。如JDBC、JavaMail等功能。
1.3 核心思想功能
1.3.1 IoC
IOC:Inversion of Control(控制反转),这是一种技术思想。主要是解决Java开发领域的对象创建和管理问题。
传统对象管理(手动创建对象和依赖)
IoC管理(由IoC创建并管理对象)
IoC主要解决对象之间的耦合问题,我们不关心对象的创建和如何依赖,只需要在使用的地方@Autowired
注入即可。IoC会帮助我们注入和管理所需要的Bean
IoC和DI的关系
DI:Dependancy Injection(依赖注入)
IoC是控制反转,DI是依赖注入。它们共同完成了对象管理这一件事情。
1.3.2 AOP
AOP:Aspect oriented Programming面向切面编程
AOP其实是OOP思想的延续和扩展。OOP是一种垂直结构,AOP是一种横向结构。
OOP的体系开发模式
AOP切面编程
如何理解切面
1.4 基于IOC和AOP代码实现
在2.2.3部分,我们已经对IoC和AOP思想有了大致的了解,但是这都是基于概念和思想上的。下面我们通过一个《银行转账》的基础案例分析其中的问题,然后基于IoC和AOP思想慢慢的改造解决痛点问题并加深我们的理解和认识。
开发过程:转账操作可以写html页面发起请求,也可以使用功能http模拟请求。
银行转账:
- A用户发起向B用户转账100元人民币请求
- A用户账户扣款100元(当然要校验账户余额,这里省略)
- B用户账户加上100元
- 最终A账户少100元,而B用户多100元
数据库SQL准备
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`cardNo` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(5) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`money` int(11) NULL DEFAULT 0
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES ('1006029621011001', '张三', 100000);
INSERT INTO `account` VALUES ('2006029621011000', '李四', 100000);
SET FOREIGN_KEY_CHECKS = 1;
Maven依赖
<dependencies>
<!-- 单元测试Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- mysql数据库驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jackson依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<!--dom4j依赖-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--xpath表达式依赖-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<!--引入cglib依赖包-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
</dependencies>
1.4.1 调用流程
1.4.2 基础版本
- 基础实体
// 数据库实体映射
public class Account {
private String cardNo;
private String name;
private int money;
// getter setter
@Override
public String toString() {
return "Account{" +
"cardNo='" + cardNo + '\'' +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
// 处理结果响应
public class Result {
private String status;
private String message;
// getter setter
@Override
public String toString() {
return "Result{" +
"status='" + status + '\'' +
", message='" + message + '\'' +
'}';
}
}
- 基础工具类
// 封装数据库连接池
public class DruidUtils {
private DruidUtils() {}
private static DruidDataSource druidDataSource = new DruidDataSource();
static {
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/lagou_spring");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
}
public static DruidDataSource getInstance() {
return druidDataSource;
}
}
- TransferServlet
@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 实例化service层对象
private TransferService transferService = new TransferServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
// 设置请求体的字符编码,避免编码错误
req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
// 2. 调用transferService实现转账
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));
}
}
- TransferService接口及实现
public interface TransferService {
void transfer(String fromCardNo, String toCardNo, int money) throws Exception;
}
public class TransferServiceImpl implements TransferService {
// 实例化Jdbc操作数据库对象
private AccountDao accountDao = new JdbcAccountDaoImpl();
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney() - money);
to.setMoney(to.getMoney() + money);
accountDao.updateAccountByCardNo(to);
// 模拟转账异常,现在暂时不开启,记住后面需要开启模拟异常
// int c = 1 / 0;
accountDao.updateAccountByCardNo(from);
}
}
- AccountDao接口和实现类
public interface AccountDao {
Account queryAccountByCardNo(String cardNo) throws Exception;
int updateAccountByCardNo(Account account) throws Exception;
}
public class JdbcAccountDaoImpl implements AccountDao {
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1, cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
// 结果封装
while (resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1, account.getMoney());
preparedStatement.setString(2, account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
con.close();
return i;
}
}
问题分析
问题解决
1)new关键字代码耦合问题?对象的依赖关系如何确定?什么时候实例化对象?
解决方案:
- 使用反射技术实例化对象。
- 对象的依赖关系可以通过xml配置文件的方式确定。
- 对象的实例化可以使用工厂模式,在项目启动的时候实例化对象并解决依赖的Bean注入问题(使用set方式注入)。
2)没有事务控制,无法保证数据库操作的原子性?
解决方案:
- 基于JDBC实现事务控制,事务配置在Service层。
- 使用本地线程绑定Connection事务,这样就可以保证数据库都是同一个事务。
1.4.3 解决问题一
- beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--托管:创建Dao接口-->
<bean id="accountDao" class="com.zyj.dao.impl.JdbcAccountDaoImpl"></bean>
<bean id="transferService" class="com.zyj.service.impl.TransferServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
- 增加BeanFactory解析beans.xml创建对象
/**
* 任务一:解析xml配置文件,利用反射技术生产对应的实例对象。同时管理对象的注入问题
* 任务二:提供静态方法根据ID获取类对象
*
* @Author zhichunqiu
* @time 2020/6/3 14:56
*/
public class BeanFactory {
// 存储实例化的Bean
private static Map<String, Object> beans = new HashMap<>();
public static Object getBean(String name) {
return beans.get(name);
}
static {
try {
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 使用dom4j技术解析xml配置文件
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
// 读取bean标签
List<Element> elementList = rootElement.selectNodes("//beans/bean");
// 实例化所有对象,并且放到Map中
for (Element element : elementList) {
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
Class<?> aClass = Class.forName(clazz);
Object instance = aClass.newInstance();
beans.put(id, instance);
}
// 维护bean之间的依赖关系
List<Element> propertyList = rootElement.selectNodes("//bean/property");
for (Element element : propertyList) {
// 属性名称
String name = element.attributeValue("name");
// 应用类型的值ID
String ref = element.attributeValue("ref");
// 获取父标签的属性
Element parentElement = element.getParent();
String parentId = parentElement.attributeValue("id");
Object o = beans.get(parentId);
Method[] methods = o.getClass().getMethods();
for (Method method : methods) {
// 使用set方法注入
if (method.getName().equalsIgnoreCase("set" + name)) {
method.invoke(o, beans.get(ref));
}
}
}
System.out.println(beans);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 修改TransferServlet、TransferServiceImpl从BeanFactory中获取对象
@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 获取TransferService对象
private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");
}
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao;
// set方式注入对象
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
1.4.4 解决问题二
- 增加ConnectionUtils管理数据库连接
/**
* Connecion获取类,与本地线程绑定
* @Author zhichunqiu
* @time 2020/6/3 18:55
*/
public class ConnectionUtils {
// 本地线程,存储连接
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
// 从本地线程中获取连接,如果没有就从数据库连接池中获取并设置到本地线程
public Connection getCurrentThreadConn() throws SQLException {
Connection connection = threadLocal.get();
if (connection == null) {
connection = DruidUtils.getInstance().getConnection();
threadLocal.set(connection);
}
return connection;
}
}
- 增加TransactionManager类管理事务
package com.zyj.utils;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 事务控制管理器
*
* @Author zhang yong jun
* @time 2020/6/3 19:02
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
// 开启事务
public void start() throws SQLException {
connectionUtils.getCurrentThreadConn().setAutoCommit(false);
}
// 提交事务
public void commit() throws SQLException {
connectionUtils.getCurrentThreadConn().commit();
}
// 回滚事务
public void rollback() throws SQLException {
connectionUtils.getCurrentThreadConn().rollback();
}
}
- 增加ProxyFactory代理工厂类,生成Service代理实现事务控制
package com.zyj.factory;
import com.zyj.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理工厂,负责控制事务的开启,提交与回滚
*
* @Author zhang yong jun
* @time 2020/6/3 19:30
*/
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public Object getJdkProxy(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 代理对象开启事务
transactionManager.start();
// 执行原方法逻辑
method.invoke(obj, args);
// 提交事务
transactionManager.commit();
} catch (Exception e) {
// 异常回滚事务
transactionManager.rollback();
// 异常由上层处理
throw e;
}
return result;
}
});
}
}
- 修改beans.xml,增加事务管理器、代理对象、数据库连接工具对象
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--将ConnectionUtils工具类托管-->
<bean id="connectionUtils" class="com.zyj.utils.ConnectionUtils"></bean>
<!--托管:创建Dao接口-->
<bean id="accountDao" class="com.zyj.dao.impl.JdbcAccountDaoImpl">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<bean id="transferService" class="com.zyj.service.impl.TransferServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--托管:创建事务管理对象-->
<bean id="transactionManager" class="com.zyj.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<!--托管:创建代理工厂对象-->
<bean id="proxyFactory" class="com.zyj.factory.ProxyFactory">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
- 修改JdbcAccountDaoImpl类
/**
* @author zhichunqiu
*/
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
// 注入对象
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
// Connection con = DruidUtils.getInstance().getConnection();
// 改为注入
// Connection con = ConnectionUtils.getCurrentThreadConn();
Connection con = connectionUtils.getCurrentThreadConn();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1, cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while (resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
// 从本地线程获取连接,不可以释放,否则就解除了绑定
// con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
// Connection con = DruidUtils.getInstance().getConnection();
// 改为注入
// Connection con = ConnectionUtils.getCurrentThreadConn();
Connection con = connectionUtils.getCurrentThreadConn();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1, account.getMoney());
preparedStatement.setString(2, account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
// con.close();
return i;
}
}
- 修改TransferServlet从代理工厂获取有事务控制的Service
@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 获取代理工厂
private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
// 从代理工厂中使用JDK代理返回TransferService对象
private TransferService transferService = (TransferService) proxyFactory.getJdkProxy(BeanFactory.getBean("transferService"));
}
总结
截止目前,我们已经完成了《银行转账》案例基础到问题分析,然后基于IoC和AOP思想的改造。从而实现了对象统一管理和依赖注入、事务管理控制的问题。其实这些就是Spring框架的IoC和AOP原理。
挑战
- 上诉的银行转账案例是基于XML配置方式实现的,你可以挑战一下将其改造成基于注解方式实现。
- 你了解了Spring的IoC和AOP核心思想,那么是否可以读懂了Spring框架源码呢?
1.5 Spring学习思维导图(提供参考)