文档章节

「框架」菜鸟简单模仿一下spring的ioc和aop思想,欢迎大家进来阅读指教

尾小戒1993
 尾小戒1993
发布于 2015/12/04 16:41
字数 2270
阅读 5536
收藏 173
点赞 6
评论 19

spring最核心的部分莫过于ioc和aop了,博主菜逼一枚,如果有哪里理解的不对或者代码上有瑕疵的地方欢迎大家指正,大家互相学习,还有就是这只是模仿一下spring思想,只是把事务管理和bean管理简单模仿一下,完全不代表spring,如果想深入理解请看spring源码,下面就开始我们简单的模仿,纯手打,觉得还行就赞一下吧~

这个项目不是web项目,只是一个简单的java项目,测试用junit,废话不多说了,下面上代码:

项目的目录结构:

说明:图中划红线的部分都是核心部分

红线部分说明:

① BeanFactory:所有bean的核心生成器(spring容器), ② ConnBean:jdbc连接生成器(没用连接池哦~)

③Transaction:事务管理的代理类, ④ beans.properties:配置文件

其余的没划线的就是domain、dao、service、controller这些web基本层次结构,待会会说

--------------------------------------------------------------------------------------------------------------

主要几个类的代码:

① BeanFactory:

package sun.juwin.factory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
/**
 * 本类用来读取配置文件中的信息对每个接口对象生成具体的实现
 * 主要是将接口作为key,实例作为value存储进去,这是个单例,
 * spring默认为每个层次生成实现也是单例,但可以通过@Scope
 * 来指定,我们简单模仿一下,只是单例
 */
public class BeanFactory {
   private static HashMap<String, Object> mapResult;
   public static HashMap<String, Object> getBean() {
      if (mapResult == null) {
         synchronized (BeanFactory.class) {//双重检查的单例,防止多线程访问时多次new对象
            if (mapResult == null) {
               BufferedReader bf = null;
               String line = null;
               try {
                 /**
                  *下面这句代码通过流来读取资源包下面的配置文件,为了省去不必要的麻烦,
                  * 我们没有用xml,而是用了properties
                  */
                  InputStreamReader inputStreamReader = new InputStreamReader(BeanFactory.class.getClassLoader().getResourceAsStream("beans.properties"));
                  bf = new BufferedReader(inputStreamReader);
                  mapResult = new HashMap<>();
                  while ((line = bf.readLine()) != null) {//每次仅读一行
                     if ("".equals(line)){//有可能读到换行时隔了一行(即只有一个换行符)
                        continue;
                     }
                     String[] point = line.trim().split("=");//按照等号拼接
                     if (point.length > 2) {
                        throw new Exception("beans文件格式不对!");
                     }
                     Object obj = Class.forName(point[1].trim()).newInstance();//反射实例化出目标对象
                     mapResult.put(point[0].trim(), obj);//然后以键值对的形式存入
                  }
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }
         }
      }
      return mapResult;
   }
}

上面的类可以通过配置文件来实例化不同的对象,符合ioc最基本的思想,下面让我们来看看配置文件beans.properties的内容吧:

userDao = sun.juwin.dao.impl.UserDaoImpl
userDetailDao = sun.juwin.dao.impl.UserDetailDaoImpl

这里面只有两句话,指定dao层接口对象的实现类的路径,其实已经很接近spring的xml里对bean的配置了,只不过这里是properties文件,简化了许多

② TransactionProxy代理类:

package sun.juwin.proxy.transctional;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
/**
 * 事务代理类,通过这个类可以为要执行的方法加上事务管理
 */
public class TransactionProxy implements InvocationHandler {
    private Object targetObj;
    public Object getTargetObj(Object targetObj){
        this.targetObj = targetObj;
        return Proxy.newProxyInstance(this.targetObj.getClass().getClassLoader(),
                this.targetObj.getClass().getInterfaces(), this);
    }
    /*下面这个方法会在被代理类执行方法时调用,拿到被代理类的要执行的method对象*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        Connection connection = (Connection)args[0];//要求第一个参数必须是conn
        try{
            connection.setAutoCommit(false);//开启事务
            result = method.invoke(this.targetObj, args);//执行目标方法
            connection.commit();//事务提交
            System.out.print("commit success!");
        }catch (Exception e){
            connection.rollback();//事务回滚
            System.err.println("rollback!");
            e.printStackTrace();
        }finally {
            connection.close();//关闭连接
            System.out.println("connection closed!");
        }
        return result;
    }
}

说明:java在1.3版本的时候就为我们提供了一个用作代理类实现的接口InvacationHandler,通过实现这个接口可以很随意的写一个耦合度特别低的动态代理类(即这一个代理类可以代理任何类)

③ ConnBean,用来生成一个数据库连接对象,在不用连接池的情况下,我们用ThreadLocal进行封装,代码如下:

package sun.juwin.db;
import java.sql.Connection;
import java.sql.DriverManager;
/*原始产生数据库连接的类*/
public class ConnBean {
    private static ThreadLocal<Connection> conn = new ThreadLocal<>();
    private ConnBean(){}
    public static Connection getConn(){
        Connection connection = conn.get();
        if(connection == null){
            synchronized (ConnBean.class){//由于用到了ThreadLocal,因此该单例仅仅相对于当前线程是单例的
                if(connection == null){
                    try{
                        Connection realConn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_useradd", "root", "");
                        conn.set(realConn);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
        return conn.get();//返回给当前线程一个Connection对象
    }
}

 

以上就是核心的一些实现代码,下面让我们来看一下我们的业务吧:

实体类:User,UserDetail,要求添加一个User的同时要添加一个UserDetail

User:

private Long id;
private String userName;
private String address;
private int money;

UserDetail:

private Long id;
private int age;
private String realname;

dao层的接口和实现:

UserDao:

public interface UserDao {
    public void save(User user, Connection conn)throws Exception;
}

UserDaoImpl:

public class UserDaoImpl implements UserDao{
    @Override
    public void save(User user, Connection conn) throws Exception {
        Statement statement = conn.createStatement();//为了省去不必要的麻烦,我们不用预编译语句
        String sql = "insert into tb_user (userName, address, money) values ('"
                + user.getUserName() + "', '"
                + user.getAddress() + "', "
                + user.getMoney() + ")";
        statement.executeUpdate(sql);
        statement.close();
    }
}

UserDetailDao:

public interface UserDetailDao {
    public void save(UserDetail userDetail, Connection connection) throws Exception;
}

UserDetailDaoImpl:

public class UserDetailDaoImpl implements UserDetailDao {
    @Override
    public void save(UserDetail userDetail, Connection connection) throws Exception {
        Statement statement = connection.createStatement();
        String sql = "insert into user_detail (age, realname) values ("
                +userDetail.getAge()+", '"
                +userDetail.getRealname()+"')";
        statement.executeUpdate(sql);
    }
}

UserService:

public interface UserService {
    public void saveService(Connection connection, User user) throws Exception;
}

UserServiceImpl

/**
 * 业务层
 * juwin
 * 2015-12-04
 */
public class UserServiceImpl implements UserService {
    //下面的dao层实例由BeanFactory通过properties配置文件帮我们生成对应的实例对象
    private UserDao userDao = (UserDao) BeanFactory.getBean().get("userDao");
    private UserDetailDao userDetailDao = (UserDetailDao) BeanFactory.getBean().get("userDetailDao");
    @Override
    public void saveService( Connection connection, User user)throws Exception {
        /**
         * 这个业务层方法执行了两个dao层方法,可以看做一个事务,
         * 任意一个dao层调用过程中如果发生异常,整个业务方法进行的所有dao层操作就会回滚
         */
        userDao.save(user, connection);
        /*要求在添加user的同时生产一个对应的detail,这里偷个懒,就自己new一个UserDetail对象吧*/
        UserDetail userDetail = new UserDetail();
        userDetail.setAge(22);
        userDetail.setRealname("juwin");
        userDetailDao.save(userDetail, connection);
        throw new Exception("拦-路-虎");//这个异常是用来测试事务会不会回滚的,正常情况下不加这个
    }
}

UserController:

/**
 * 程序入口,类似于controller层
 */
public class UserController {
    public void SaveUser(User user)throws Exception{
        /**
         * 这一步很关键,为每一个执行这个操作的线程分配一个connection连接对象
         * 说明:在实际web开发中客户端通过发送http请求到业务后台,这时候tomcat会为这次请求分配一个线程
         * 因此就出现了并发甚至并行的现象,假象一下,我们如果只是利用单例写一个生成connection对象的方法,
         * 那么多线程并发访问的时候就有可能出现:线程1利用完connection对象将其状态修改为close,而此时线程2
         * 也要用connection,这时候就会报“connection已经关闭”的异常
         * 因此我们采用ThreadLocal,为单独一个线程生成一个单例的connection对象
         */
        Connection connection = ConnBean.getConn();
        /**
         * 下面这个实例要加一层事务代理,就是让TransactionProxy这个代理类搅合一下,
         * 这样我们再利用service层对象调用任何方法时,都会加上事务管理了
         */
        UserService userService = (UserService) new TransactionProxy().getTargetObj(new UserServiceImpl());
        userService.saveService(connection,user);
    }
}

测试类:

public class UserAddTest {
    @Test
    public void Test1() throws Exception{
        User user = new User();
        user.setUserName("weixiaojie1993");
        user.setAddress("beijing");
        user.setMoney(1);
        UserController userController = new UserController();
        userController.SaveUser(user);
        System.out.print("Done !");
    }
}

ok,大功告成了,现在让我们用junit来测试一下吧:

service层不加

throw new Exception("拦-路-虎");

执行结果:

可以看出来事务已经提交了,我们来看看数据库里面的变化:

tb_user表:

user_detail表:

然后在业务层加上

throw new Exception("拦-路-虎");

运行结果:

仔细观察划绿色线的部分就能发现,事务已经回滚了,看数据库表也是没有记录的

我们主键id由于是递增的,因此我们还要确定一下事务是不是真的回滚了,我们把异常代码去掉,然后再往里面插入成功一次数据,运行后的数据库表记录如下:

tb_user:

user_detail:

大家仔细看id,已经是3了,说明原来事务成功回滚了

说明:其实connection对象不必每次都作为参数传递给方法,这里只是为了更清楚的展示connection的流向,其实我们用ThreadLocal封装成一个单例的时候就已经注定了本次访问(即当前线程从controller层调用到dao层)所有get到的connection对象都是同一个;

最后,个人感觉这个程序有个非常要命的地方,就是我要给service层加事务代理,这样就导致了sevice层的对象不能通过配置文件来实例化,正在纠结中。。以后还会优化,这只是简单实现以下,真正的spring要复杂的多得多,第一次发表博客,以后也会多发一些,大家互相学习~

© 著作权归作者所有

共有 人打赏支持
尾小戒1993
粉丝 11
博文 6
码字总数 5234
作品 0
浦东
程序员
加载中

评论(19)

yong9981
yong9981

引用来自“悠悠然然”的评论

可以看看tinyioc,那个也是个练习项目,最后木有使用,但是学习不错。
楼主能看一下jBeanBox项目吗? 发布在http://git.oschina.net/drinkjava2/jBeanBox和github上,千行源码实现了IOC/AOP完整功能,特点是用JAVA配置代替XML,其实现方式比Spring和Guice的简单实用。此项目不是学习用,正向实用化进军,目标是小项目中代替Spring和Guice, 欢迎加入此项目。
yong9981
yong9981
楼主能看一下jBeanBox项目吗? 发布在http://git.oschina.net/drinkjava2/jBeanBox和github上,千行源码实现了IOC/AOP完整功能,特点是用JAVA配置代替XML,其实现方式比Spring和Guice的简单实用。此项目不是学习用,正向实用化进军,目标是小项目中代替Spring和Guice, 欢迎加入此项目。
单行道
单行道
学习了
_Jacey
_Jacey
值得学习 博主辛苦了。
紫电清霜
紫电清霜
带我飞
Br00k
Br00k
0撸主不错。共勉。
simplehpt
simplehpt
0
快乐的时光
快乐的时光
nice
嘻哈开发者
嘻哈开发者
看看,好像很厉害的样子
山雨欲来
山雨欲来
intellij 大法好
轻松理解AOP(面向切面编程)

本文主要介绍AOP思想,而不是Spring,Spring在本文只做为理解AOP的工具和例子,所以也不打算介绍Spring的Aspect、Join point、Advice、AOP proxy等概念,那样初学者会很难理解,如果你懂了A...

爱捣鼓
2014/02/26
0
0
Spring MVC 原理探秘 - 容器的创建过程

1.简介 在上一篇文章中,我向大家介绍了 Spring MVC 是如何处理 HTTP 请求的。Spring MVC 可对外提供服务时,说明其已经处于了就绪状态。再次之前,Spring MVC 需要进行一系列的初始化操作。...

coolblog.xyz
07/03
0
0
Spring IOC 容器源码分析系列文章导读

1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已经非常成熟了。Spring 包含了众多模块,包括但不限于...

coolblog
05/30
0
0
Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的。我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反射创建一个原始的 ...

coolblog.xyz
06/11
0
0
Spring AOP 源码分析系列文章导读

简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解。在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅读了 AOP ...

java高级架构牛人
06/21
0
0
Spring MVC 原理探秘 - 一个请求的旅行过程

1.简介 在前面的文章中,我较为详细的分析了 Spring IOC 和 AOP 部分的源码,并写成了文章。为了让我的 Spring 源码分析系列文章更为丰富一些,所以从本篇文章开始,我将来向大家介绍一下 Sp...

coolblog.xyz
07/02
0
0
向Spring大佬低头——大量源码流出解析

用Spring框架做了几年的开发,只停留在会用的阶段上,然而Spring的设计思想和原理确实一个巨大的宝库。大部分人仅仅知道怎么去配,或着加上什么属性就能达到什么效果,这些东西都可以通过查文...

Java团长17
07/11
0
0
Spring IOC 容器源码分析 - 创建原始 bean 对象

1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续。在上一篇文章中,我们从战略层面上领略了方法的全过程。本篇文章,我们就从战术的层面上,详细分析方法中的一个重要的调用,即...

coolblog.xyz
06/06
0
0
【step by step构建轻量级web框架】-何为轻量级web框架

本系列博文,将会一步一步介绍如何构建一个轻量级的web框架jbeer git地址:http://git.oschina.net/bieber/jbeer 在SSH/SSI充实着我们每个项目的开发过程中,我们所做的事情就是将他们一次组...

Bieber
2014/06/14
0
4
Spring AOP 源码分析 - 筛选合适的通知器

1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析。本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出合适的通知器(Advisor...

java高级架构牛人
06/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Win10专业版安装GIT后使用Git Bash闪退解决办法

百度后把过程和最终解决办法记录下来: 百度首先出来的解决办法如下: 来自:https://segmentfault.com/q/1010000012722511?sort=created 重启电脑 重新安装 安装到C盘 尝试网上的教程 \Git...

特拉仔
2分钟前
0
0
设计模式

1.装饰器模式 概念 允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰者可以在所委托被装饰者的行为之前或之后加上自己的行为,以达到特定的目的。 实现 增加一个修饰类包裹原来的...

EasyProgramming
16分钟前
1
0
用python2和opencv进行人脸识别

一、安装cv2 sudo apt-get install python-opencv opencv-data 二、 Haar特征分类器 Haar特征分类器就是一个XML文件,该文件中会描述人体各个部位的Haar特征值。包括人脸、眼睛、嘴唇等等。 ...

wangxuwei
16分钟前
0
0
python模板中循环字典

{% for k,v in user.items %} {{ k}} {{ v}} {% endfor %}

南桥北木
45分钟前
0
0
Java8系列之重新认识HashMap

简介 Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap,类继承关系如下图所示: 下面针对各个实现类...

HOT_POT
48分钟前
0
0
获取调用方的className

/** * 获取调用方的class * @return */private static String getInvoke() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); S......

iborder
今天
0
0
深入了解一下Redis的内存模型!

一前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分。 我们使用Redis时,会接触Redis的5种对象类型(字符...

Java填坑之路
今天
1
0
从实践出发:微服务布道师告诉你Spring Cloud与Spring Boot他如何选择

背景 随着公司业务量的飞速发展,平台面临的挑战已经远远大于业务,需求量不断增加,技术人员数量增加,面临的复杂度也大大增加。在这个背景下,平台的技术架构也完成了从传统的单体应用到微...

老道士
今天
1
0
大数据学习的各个阶段

第一阶段:Linux课程讲解Linux基础操作,讲的是在命令行下进行文件系统的操作,这是Hadoop学习的基础,后面的所有视频都是基于linux操作的。鉴于很多学员没有linux基础,特增加该内容,保证零linux...

董黎明
今天
0
0
CVE-2013-0077 堆溢出分析

找了很久才发现这个环境比较容易搭建分析... 环境: 系统---Win XP SP3 漏洞程序:QQPlayer 3.7.892.400 出错DLL:quartz.dll 6.5.2600.5512 调试工具:x32db+gflag.exe 过程: 首先gflag设置...

Explorer0
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部