Java学习笔记分享之Spring篇(原理)

    科技2022-07-12  127

    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学习思维导图(提供参考)

    知春秋 认证博客专家 博客专家 Java高级研发 不忘初心,方得始终。初心易得,始终难守。
    Processed: 0.011, SQL: 8