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

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部分,我们已经对IoCAOP思想有了大致的了解,但是这都是基于概念和思想上的。下面我们通过一个《银行转账》的基础案例分析其中的问题,然后基于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"));
}    

总结

​ 截止目前,我们已经完成了《银行转账》案例基础到问题分析,然后基于IoCAOP思想的改造。从而实现了对象统一管理和依赖注入、事务管理控制的问题。其实这些就是Spring框架的IoCAOP原理。

挑战

  • 上诉的银行转账案例是基于XML配置方式实现的,你可以挑战一下将其改造成基于注解方式实现。
  • 你了解了Spring的IoC和AOP核心思想,那么是否可以读懂了Spring框架源码呢?

1.5 Spring学习思维导图(提供参考)

知春秋 CSDN认证博客专家 博客专家 Java高级研发
不忘初心,方得始终。初心易得,始终难守。
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页