• 手撸一个IOC容器


    IoC

    什么是IoC?

    IoC是Inversion of Control(控制反转)的简称,注意它是一个技术思想。描述的是对象创建、管理的事情。

    • 传统开发方式:比如类A依赖类B,往往会在类A里面new一个B的对象。

    • IoC开发方式:我们不用去new对象,由IoC容器帮我们实例化对象并进行管理。我们需要B对象,就问IoC容器要即可。

    控制反转就是说将对象创建、管理的权力交给了外部环境(IoC容器)。

    IoC的作用:解决了对象之间的耦合问题。

    什么是DI?

    DI是Dependancy Injection(依赖注入)的简称,指容器会把对象依赖的其他对象注入。比如A对象里声明了一个B的属性,那么就需要容器把B对象注入给A。

    什么是AOP?

    AOP是 Aspect oriented Programming(⾯向切⾯编程)的简称。

    在上面的代码中,多个方法都出现了相同的代码(可以称之为横切逻辑代码)。这部分代码不仅重复,而且跟业务逻辑没有关系但是混杂在一起。这时AOP出现了,它提供了横向抽取机制,将这部分横切代码和业务逻辑代码分开。

    AOP的作用:在不改变原有业务逻辑的情况下,增强横切逻辑代码,解耦合。

    手写IOC

    首先我们看一下在没有Spring之前,我们是怎么开发一个web程序的呢?

    那么针对上面的两个问题,我们如何进行解决呢?

    • 我们除了用new实例化对象外,还可以用反射的技术
    • 另外项目中往往有很多对象需要实例化,那么可以使用工厂模式来进行优化。

    综上,我们可以用工厂模式+反射技术把对象都实例化好,放在一个map里面,如果需要某个对象,就可以直接从这个map里面取。

    除此之外,我们还需要一个xml文件,里面来定义对象的全类名(反射需要),如果有依赖,还需要定义类与类之间的依赖关系。

    <beans>
    
        <bean id="accountDao" class="com.mmc.ioc.dao.impl.AccountDaoImpl"></bean>
        <bean id="transferService" class="com.mmc.ioc.service.impl.TransferServiceImpl">
            <!--这里的name默认为set+name就是方法名-->
            <property name="AccountDao" ref="accountDao"></property>
        </bean>
    </beans>
    

    核心代码:

    public class BeanFactory {
    
        private static Map<String,Object> beanMap=new HashMap<>();
    
    
        static {
            InputStream inputStream=BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
            SAXReader saxReader=new SAXReader();
            try {
                Document document = saxReader.read(inputStream);
                Element rootElement = document.getRootElement();
                List<Element> beans = rootElement.selectNodes("//bean");
                for (Element element:beans){
                    String id = element.attributeValue("id");
                    String clazz = element.attributeValue("class");
                    Object instance = Class.forName(clazz).newInstance();
                    beanMap.put(id,instance);
                }
    
                //实例完后填充对象的依赖
                List<Element> propertys = rootElement.selectNodes("//property");
                for (Element element:propertys){
                    String name = element.attributeValue("name");
                    String ref = element.attributeValue("ref");
                    Element parent = element.getParent();
                    String parentId = parent.attributeValue("id");
                    Object instance = beanMap.get(parentId);
                    Object refInstance = beanMap.get(ref);
                    Method setMethod = instance.getClass().getDeclaredMethod("set" + name,refInstance.getClass().getInterfaces());
                    setMethod.invoke(instance,beanMap.get(ref));
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static Object getBean(String name){
           return beanMap.get(name);
        }
    }
    

    那么接下来我们想要使用对象的时候,就不用new了,而是从beanFactory里面去拿。

    这样一个简易的AOP就完成了。

    手写AOP实现

    我们解决了上面的问题1,那么问题2事务控制如何解决呢?

    分析:数据库事务归根结底是Connection的事务,connection.commit()提交事务,connection.rollback()回滚事务。

    • 我们是想保证service里的方法里面执行的众多数据库操作要么都成功,要么都失败。
    • 同一个service方法里面的dao层必须要使用的是同一个connection,也就是说同一个线程内要是同一个connection,所以可以使用ThreadLocal实现

    改造完成:

      @Override
        public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
            //关闭自动提交
            connectionUtils.getThreadConn().setAutoCommit(false);
    
            try {
                Account from = accountDao.queryAccountByCardNo(fromCardNo);
                Account to = accountDao.queryAccountByCardNo(toCardNo);
    
                from.setMoney(from.getMoney()-money);
                to.setMoney(to.getMoney()+money);
    
                accountDao.updateAccountByCardNo(to);
                int i=10/0;
                accountDao.updateAccountByCardNo(from);
                //提交事务
                connectionUtils.getThreadConn().commit();
            } catch (Exception e) {
                //回滚事务            
                connectionUtils.getThreadConn().rollback();
                throw e;
            }
        }
    

    在两次update语句中间手动加了个异常,可以发现数据库两条数据都没变,说明事务控制成功。

    但是如果多个方法都需要加事务控制的话,我们需要给多个方法加上下面这一套重复的代码

    connectionUtils.getThreadConn().setAutoCommit(false);
    
    try {
      //省略部分代码
      // -----
      //提交事务
        connectionUtils.getThreadConn().commit();
    } catch (Exception e) {
        //回滚事务
        connectionUtils.getThreadConn().rollback();
        throw e;
    }
    

    怎么解决呢?
    我们可以通过代理模式给每个方法代理

    代码如下:

    public class ProxyFactory {
    
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        public Object getJdkProxy(Object object){
            return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //关闭自动提交
                    connectionUtils.getThreadConn().setAutoCommit(false);
                    Object result;
                    try {
                        result= method.invoke(object,args);
                        //提交事务
                        connectionUtils.getThreadConn().commit();
                    } catch (Exception e) {
                        //回滚事务
                        connectionUtils.getThreadConn().rollback();
                        throw e;
                    }
                    return result;
                }
            });
        }
    
    
    }
    

    每个需要加事务的对象,只要调用getJdkProxy方法获取到代理对象,再使用代理对象执行方法,就能实现事务控制了。
    使用方法如下:

    private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
    private TransferService transferService= (TransferService) proxyFactory.getJdkProxy(BeanFactory.getBean("transferService"));
    

    这样就相当于把横切逻辑代码提取出来了,如果把这套机制抽出来就是AOP的实现了。

    书山有路勤为径,学海无涯苦作舟
  • 相关阅读:
    程序员修炼之道:从小工到专家有感2
    3月13日
    第一次结对作业(2)
    3月12日
    3月11日
    第一次结对作业
    3月10日
    11月6日
    10月28日
    10月7日
  • 原文地址:https://www.cnblogs.com/javammc/p/15538365.html
Copyright © 2020-2023  润新知