手写实现自定义简易版spring(实现ioc和aop)(代码片段)

丿涛哥哥 丿涛哥哥     2022-12-07     488

关键词:

手写实现自定义简易版Spring (实现IoC 和 AOP)

源码地址点这里

1、 银行转账案例界面

2、 银行转账案例表结构

3、 银行转账案例代码调用关系

4、 银行转账案例关键代码

TransferServlet

package com.tao.servlet;

import com.tao.service.impl.TransferServiceImpl;
import com.tao.utils.JsonUtils;
import com.tao.pojo.Result;
import com.tao.service.TransferService;
import javax.servlet.ServletException;
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 tao
 */
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet 
    // 1. 实例化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. 调用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));
    

TransferService接口及实现类

package com.tao.service;
/**
* @author tao
*/
public interface TransferService 
	void transfer(String fromCardNo,String toCardNo,int money) throws Exception;

package com.tao.service.impl;

import com.tao.dao.AccountDao;
import com.tao.dao.impl.JdbcAccountDaoImpl;
import com.tao.pojo.Account;
import com.tao.service.TransferService;

/**
 * @author tao
 */
public class TransferServiceImpl implements TransferService 
    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(from);
        accountDao.updateAccountByCardNo(to);
    

AccountDao层接口及基于Jdbc的实现类

package com.tao.dao;

import com.tao.pojo.Account;

/**
* @author tao
*/
public interface AccountDao 
    
	Account queryAccountByCardNo(String cardNo) throws Exception;
    
	int updateAccountByCardNo(Account account) throws Exception;

JdbcAccountDaoImpl(Jdbc技术实现Dao层接口)

package com.tao.dao.impl;

import com.tao.pojo.Account;
import com.tao.dao.AccountDao;
import com.tao.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * @author tao
 */
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;
    

5、 银行转账案例代码问题分析

问题一:在上述案例实现中,service 层实现类在使用 dao 层对象时,直接在 TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对象,然而一个 new 关键字却将 TransferServiceImpl 和 dao 层具体的一个实现类 JdbcAccountDaoImpl 耦合在了一起,如果说技术架构发生一些变动,dao 层的实现要使用其它技术, 比如 Mybatis,思考切换起来的成本?每一个 new 的地方都需要修改源代码,重新编译,面向接口开发的意义将大打折扣?

问题二:service 层代码没有竟然还没有进行事务控制 ?!如果转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重,尤其在⾦融业务。

6、 问题解决思路

针对问题一思考:

实例化对象的方式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在xml中)

考虑使用设计模式中的工厂模式解耦合,另外项⽬中往往有很多对象需要实例化,那就在工厂中使用反射技术实例化对象,工厂模式很合适

更进一步,代码中能否只声明所需实例的接口类型,不出现 new 也不出现工厂类的字眼,如下图? 能!声明一个变量并提供 set 方法,在反射的时候将所需要的对象注⼊进去吧

针对问题二思考:

service 层没有添加事务控制,怎么办?没有事务就添加上事务控制,⼿动控制 JDBC 的 Connection 事务,但要注意将Connection和当前线程绑定(即保证一个线程只有一个 Connection,这样操作才针对的是同一个 Connection,进而控制的是同一个事务)

7、 案例代码改造

针对问题一的代码改造

beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="transferService" class="com.tao.service.impl.TransferServiceImpl">
    	<property name="AccountDao" ref="accountDao"></property>
    </bean>
    	<bean id="accountDao" class="com.tao.dao.impl.JdbcAccountDaoImpl">
    </bean>
</beans>

增加 BeanFactory.java

package com.tao.factory;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author tao
 */
public class BeanFactory 
    /**
     * 工厂类的两个任务
     * 任务一:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放⼊map待用
     * 任务二:提供接口方法根据id从map中获取bean(静态方法)
     */
    private static Map<String,Object> map = new HashMap<>();
        static 
            InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
            SAXReader saxReader = new SAXReader();
        try 
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> list = rootElement.selectNodes("//bean");
            // 实例化bean对象
            for (int i = 0; i < list.size(); i++) 
                Element element = list.get(i);
                String id = element.attributeValue("id");
                String clazz = element.attributeValue("class");
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();
                map.put(id,o);
            
            // 维护bean之间的依赖关系
            List<Element> propertyNodes = rootElement.selectNodes("//property");
            for (int i = 0; i < propertyNodes.size(); i++) 
            	Element element = propertyNodes.get(i);
                // 处理property元素
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                String parentId = element.getParent().attributeValue("id");
                Object parentObject = map.get(parentId);
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) 
                    Method method = methods[j];
                    if(("set" + name).equalsIgnoreCase(method.getName()))
                        // bean之间的依赖关系(注⼊bean)
                        Object propertyObject = map.get(ref);
                        method.invoke(parentObject,propertyObject);
                    
                
                // 维护依赖关系后重新将bean放⼊map中
                map.put(parentId,parentObject);
            
         catch (DocumentException e) 
        e.printStackTrace();
         catch (ClassNotFoundException e) 
        e.printStackTrace();
         catch (IllegalAccessException e) 
        e.printStackTrace();
         catch (InstantiationException e) 
        e.printStackTrace();
         catch (InvocationTargetException e) 
        e.printStackTrace();
        
    
    
    public static Object getBean(String id) 
    	return map.get(id);
    

修改 TransferServlet

修改 TransferServiceImpl

针对问题二的改造

增加 ConnectionUtils

package com.tao.utils;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author tao
 */
public class ConnectionUtils 
    
    /*private ConnectionUtils() 
    
    
    private static ConnectionUtils connectionUtils = new ConnectionUtils();
    
    public static ConnectionUtils getInstance() 
    	return 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.tao.utils;

import java.sql.SQLException;

/**
 * @author tao
 */
public class TransactionManager 
    private ConnectionUtils connectionUtils;
    
    public void setConnectionUtils(ConnectionUtils connectionUtils) 
    	this.connectionUtils = connectionUtils;
    
    
    // 开启事务
    public void beginTransaction() throws SQLException 
    	connectionUtils.getCurrentThreadConn().setAutoCommit(false);
    
    
    // 提交事务
    public void commit() throws SQLException 
    	connectionUtils.getCurrentThreadConn().commit();
    
    
    // 回滚事务
    public void 查看详情  

springioc:相关接口分析手写简易springioc(代码片段)

文章目录SpringIOC相关接口分析1.BeanFactory2.BeanDefinition3.BeanDefinitionReader4.BeanDefinitionRegistry5.创建容器自定义SpringIOC1.定义Bean相关的pojo类2.定义注册表相关类3.定义解析器相关类4.IOC容器相关类TestSpringIOC相关接口分析1.BeanFactorySpring中B... 查看详情

关于手写实现spring注解实现自定义配置功能(代码片段)

文章目录前言配置准备配置application.properties配置web.xml自定义注解@GPController@GPService@GPAutowired@GPRequestMapping@GPRequestParam应用代码容器初始化声明全局变量init中定义调用方法的步骤加载配置文件扫描相关的类初始化扫... 查看详情

spring系列之手写一个springmvc(代码片段)

...理及手动实现Spring系列之AOP的原理及手动实现Spring系列之手写注解与配置文件的解析引言在前面的几个章节中我们已经简单的完成了一个简易版的spring,已经包括容器,依赖注入,AOP和配置文件解析等功能。这一节我们来实现一... 查看详情

从零开始实现一个简易的javamvc框架--实现ioc(代码片段)

...计思想。而DI(DependencyInjection),即依赖注入就是Ioc的一种实现方式。关于Ioc和DI的具体定义和优缺点等大家可以自行查找资料了解一下,这里就不详细赘述,总之spring的IoC功能很大程度上便捷了我们的开发工作。在实现 查看详情

01spring源码-手写篇-手写ioc实现(代码片段)

Spring源码手写篇-手写IoC一、IoC分析1.Spring的核心  在Spring中非常核心的内容是IOC和AOP.2.IoC的几个疑问?2.1IoC是什么?  IoC:InversionofControl控制反转,简单理解就是:依赖对象的获得被反转了。2.2IoC有什么好处?IoC带来... 查看详情

01spring源码-手写篇-手写ioc实现(代码片段)

Spring源码手写篇-手写IoC一、IoC分析1.Spring的核心  在Spring中非常核心的内容是IOC和AOP.2.IoC的几个疑问?2.1IoC是什么?  IoC:InversionofControl控制反转,简单理解就是:依赖对象的获得被反转了。2.2IoC有什么好处?IoC带来... 查看详情

关于手写实现spring注解实现自定义配置功能(代码片段)

文章目录前言配置准备配置application.properties配置web.xml自定义注解@GPController@GPService@GPAutowired@GPRequestMapping@GPRequestParam应用代码容器初始化声明全局变量init中定义调用方法的步骤加载配置文件扫描相关的类初始化扫... 查看详情

如何实现一个简易版的spring

前言本文是「如何实现一个简易版的Spring系列」的第五篇,在之前介绍了Spring中的核心技术之一IoC,从这篇开始我们再来看看Spring的另一个重要的技术——AOP。用过Spring框架进行开发的朋友们相信或多或少应该接触过AOP,用中文... 查看详情

03spring源码-手写篇-手写aop实现(上)(代码片段)

Spring源码手写篇-手写AOP【上】  手写IoC和DI后已经实现的类图结构。一、AOP分析1.AOP是什么?  AOP[AspectOrientedProgramming]面向切面编程,在不改变类的代码的情况下,对类方法进行功能的增强。2.我们要做什么?  ... 查看详情

如何实现一个简易版的spring

前言前面两篇如何实现AOP(上)、如何实现AOP(中)做了一些AOP的核心基础知识简要介绍,本文进入到了实战环节了,去实现一个基于XML配置的简易版AOP,虽然是简易版的但是麻雀虽小五脏俱全,一些核心的功能都会实现,通过... 查看详情

手写springmvc框架实现简易版mvc框架

前言前面几篇文章中,我们讲解了SpringMVC执⾏的⼤致原理及关键组件的源码解析,今天,我们来模仿它⼿写⾃⼰的mvc框架。先梳理一下需要实现的功能点:tomcat加载配置文件web.xml;调用web.xml中指定的前端控制器DispatcherServlet加... 查看详情

手写简易版promise(代码片段)

实现一个简易版Promise在完成符合Promise/A+规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise 基本可以过关了。那么我们先来搭建构建函数的大体框架constPENDING=... 查看详情

03spring源码-手写篇-手写aop实现(上)(代码片段)

Spring源码手写篇-手写AOP【上】  手写IoC和DI后已经实现的类图结构。一、AOP分析1.AOP是什么?  AOP[AspectOrientedProgramming]面向切面编程,在不改变类的代码的情况下,对类方法进行功能的增强。2.我们要做什么?  ... 查看详情

springioc:相关接口分析手写简易springioc

文章目录SpringIOC相关接口分析1.BeanFactory2.BeanDefinition3.BeanDefinitionReader4.BeanDefinitionRegistry5.创建容器自定义SpringIOC1.定义Bean相关的pojo类2.定义注册表相关类3.定义解析器相关类4.IOC容器相关类TestSpringIOC相关接口分析1.BeanFactorySpring中B... 查看详情

如何实现一个简易版的spring(代码片段)

前言在上篇实现了判断一个类的方式是符合配置的pointcut表达式、根据一个Bean的名称和方法名,获取Method对象、实现了BeforeAdvice、AfterReturningAdvice以及AfterThrowingAdvice并按照指定次序调用等功能,这篇再来看看剩下的代理对象如何... 查看详情

跟老杜手撕spring6教程spring对ioc的实现

...ng6教程采用难度逐步递进的方式,从入门的第一个程序到手写Spring框架,真正的能够让小白成为老手。如果你是老程序员不妨看看手写Spring框架,也会让你受益颇多。1. IoC控制反转控制反转是一种思想。控制反转是为了降低... 查看详情

05spring源码-手写篇-手写bean配置

Spring源码手写篇-Bean定义配置化一、Bean定义配置分析  我们前面实现了手写IoC和AOP的功能,但是我们在使用的时候发现我们的调用代码还是非常的繁琐,会给应用者很不好的体验。  上面的代码很直观的看到重复代码很多,... 查看详情