手写一个简化版的Spring

简化版Spring框架

!!!

这只是我为了学习Spring而写的内容,和真正的Spring比起来少了很多很多,只是使用了Spring的一些思想去模拟他得到一些功能,大家可以看懂我的实现方式,然后再去看Spring的源码会简单很多。

 

 

IOC容器

我们先写一个配置文件,定义两个实体类,其中一个类中引用了另外一个类作为属性

public class MySpring {
   
   private Myreference reference;
   
   public void setReference(Myreference reference) {
       this.reference = reference;
  }
}
public class Myreference {
   public void show(){
       System.out.println("我是注入进来的依赖");
  }
}
<?xml version = "1.0" encoding="UTF-8" ?>
<beans>
   <bean id="myspring" class="com.william.entity.MySpring">
       <property name ="Reference" ref="myreference"></property>
   </bean>
   <bean id="myreference" class="com.william.entity.Myreference"></bean>
</beans>

好了我们现在开始写工厂类,工厂类中会实现xml文件的读取和解析,bean的实例化以及存储,还有bean中的依赖注入

第一步,读取xml文件,为我们需要导入两个工具类,jaxen和dom4j

        <dependency>
           <groupId>dom4j</groupId>
           <artifactId>dom4j</artifactId>
           <version>1.6.1</version>
       </dependency>
       <dependency>
           <groupId>jaxen</groupId>
           <artifactId>jaxen</artifactId>
           <version>1.1.6</version>
       </dependency>

创建一个工厂包,建立一个BeanFactory类

/**
* 工厂类 生产对象 使用反射技术
*/
public class BeanFactory {
   private static Map<String, Object> map = new HashMap<String, Object>();//存储对象

   /*
   类加载的时候就执行
    */
   static {
       //将配置文件读取成流
       InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
       //开始解析xml
       SAXReader saxReadeer = new SAXReader();
       try {
           Document document = saxReadeer.read(inputStream);
           Element rootElement = document.getRootElement();//获取根元素
           List<Element> beanList = rootElement.selectNodes("//bean");//获取所有bean标签

           for (Element element : beanList) {
               String id = element.attributeValue("id"); //myspring
               String claszz = element.attributeValue("class"); //com.william.entity.MySpring
               Class<?> aClass = Class.forName(claszz);
               Object o = aClass.newInstance(); //获取一个实例

               map.put(id,o);//将这个id和类对象放到对象池中,类似于spring中的单例池
          }

           //实例化完成之后维护对象依赖
           List<Element> propertyList = rootElement.selectNodes("//property");
           //解析property,获取父元素
           for (int i = 0; i < propertyList.size(); i++) {
               Element element = propertyList.get(i); //<property name ="Reference" ref="myreference"></property>
               String name = element.attributeValue("name");
               String ref  = element.attributeValue("ref");

               //找到这个元素的上级,即需要注入依赖的元素
               Element parent = element.getParent();
               //调用父元素反射
               String parentId = parent.attributeValue("id");
               Object parentObject = map.getOrDefault(parentId, null);//获取到这个父类
               Method[] methods = parentObject.getClass().getMethods();
               for (Method method : methods) {//定位到set方法,使用set注入
                   if(method.getName().equalsIgnoreCase("set"+name)){
                       method.invoke(parentObject,map.get(ref));
                  }
              }

               //把父元素更新
               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.getOrDefault(id,null);//获取bean的静态方法
  }

}

 

好,我们接来下来试一试有没有注入进去,注意我的MySpring类中的属性是没有实例化的,如果没有注入,在我调用这个方法的时候肯定会有空指针异常

public class test {
   public static void main(String[] args) {
       MySpring mySpring = (MySpring) BeanFactory.getBean("myspring");
       mySpring.getReference().show();
  }
}

输出结果

手写一个简化版的Spring

注入成功!一个简单的IOC容器就实现了,这是最简单的版本,之后会进行升级

 

事务管理

我们先来简单的模拟一个场景,两个顾客之间进行转账

我们先建立一个实体类

public class Customer {
   private int money;

   public Customer() {
       this.money = 100;
  }

   public int getMoney() {
       return money;
  }

   public void setMoney(int money) {
       this.money = money;
  }
}

在写一个转钱方法

public class transfer {
   public void transferMoney(Customer cus1, Customer cus2, int money) {
       cus1.setMoney(cus1.getMoney() - money);
       cus2.setMoney(cus2.getMoney() + money);
  }
}

正常情况下这样的逻辑没有问题,但是如果在cus1减完前后发生了异常导致系统中断,那么cus1的钱就不见了,我们来看看,使用1/0来模拟异常

public class transfer {
   public void transferMoney(Customer cus1, Customer cus2, int money) {
       cus1.setMoney(cus1.getMoney()-money);
       int i = 1/0;
       cus2.setMoney(cus2.getMoney()+money);
  }
}

我们来写一个测试类

public class test {
   public static void main(String[] args) {
       Customer a = new Customer();
       Customer b = new Customer();

       transfer transfer = new transfer();

       transfer.transferMoney(a,b,50);

       System.out.println(b.getMoney());
       System.out.println(a.getMoney());
  }
}

这样子的执行结果是直接报异常,我们看不到输出的每个顾客的前,我们通过调试来看

手写一个简化版的Spring

手写一个简化版的Spring

我们到这步的时候,一切正常,往下走一步

手写一个简化版的Spring

这一步1号顾客钱已经减了,按理说2号顾客接下来要加钱了,可是这里出现了异常

导致了系统奔溃

然后我们就需要去写事务管理,一般来说最终还是使用jdbc事务进行管理,在dao层中执行,如果我们同时使用了两个transferMoney的话,就算开启了事务,也会有问题,因为两个transfer用的不是同一个connection,因此我们需要让他们绑定同一个connection,让他们能够在service层中进行事务控制

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
   /**
    * 从当前线程获取connection链接
    */
   public Connection getCurrentThreadConn() throws SQLException {
       Connection connection = threadLocal.get();
       if(connection == null){
           connection= (Connection) DruidUtils.getInstance().getConnection();
           threadLocal.set(connection);
      }
       return connection;
  }

这样子同一个线程使用的就是同一个connection对象了。

然后我们在service层中加入事务

public class transfer {
   public void transferMoney(Customer cus1, Customer cus2, int money) throws SQLException {
       try {
           ConnectionUtils.getInstance().getCurrentThreadConn().setAutoCommit(false);
           cus1.setMoney(cus1.getMoney() - money);
           int i = 1 / 0;
           cus2.setMoney(cus2.getMoney() + money);
           ConnectionUtils.getInstance().getCurrentThreadConn().commit();
      }catch (Exception e){
           e.printStackTrace();
           ConnectionUtils.getInstance().getCurrentThreadConn().rollback();
      }

  }
}

我们把这些方法抽取出来,放到一个专门的事务管理器类中, 这里最好用单例模式去写,但我懒得改了

public class transactionManager {
   public void beginTransaction() throws SQLException {
       ConnectionUtils.getInstance().getCurrentThreadConn().setAutoCommit(false);//开启事务

  }

   public void commit() throws SQLException {
       ConnectionUtils.getInstance().getCurrentThreadConn().commit();//提交事务
  }


   public void rollback() throws SQLException {
       ConnectionUtils.getInstance().getCurrentThreadConn().rollback();//回滚事务
  }
}

AOP动态代理

在上面事务管理的代码中我们可以看到,每一个service方法我们都需要写这个控制语句,而且这些事务语句和我们的业务逻辑混合在了一起,这就是我们为什么要使用AOP动态代理来处理这个问题

我们先写一个通用的JDK动态代理类

public class JDKProxy {
   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;

               result = method.invoke(obj, args);

               return result;
          }
      });
  }

}

我们把事务管理加到这里来

public class JDKProxy {
   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 {
                   ConnectionUtils.getInstance().getCurrentThreadConn().setAutoCommit(false);
                   result = method.invoke(obj, args); //执行原有业务逻辑
                   ConnectionUtils.getInstance().getCurrentThreadConn().commit();
              }catch (Exception e){
                   e.printStackTrace();
                   ConnectionUtils.getInstance().getCurrentThreadConn().rollback();
              }

               return result;
          }
      });
  }

}

这时候我们就可以把原来业务逻辑中的事务代码给删了,当我们在servlet层使用service代码时,我们调用的时这个service类的代理类对象,这样就能实现业务增强了,注意如果使用的是JDK的动态代理,那么被代理类需要实现接口,CGlib的就不用。

 

上一篇:C++ 模板多继承


下一篇:从一次买车中引发的思考 -- 关于汽车消费贷你需要知道的事情