zoukankan      html  css  js  c++  java
  • 「框架」菜鸟简单模仿一下spring的ioc和aop思想,欢迎大家进来阅读指教

     博客搬家:初版发布于 2015/12/04 16:41

     原博客地址:https://my.oschina.net/sunqinwen/blog/539397

    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 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要复杂的多得多,第一次发表博客,以后也会多发一些,大家互相学习~

  • 相关阅读:
    spring cloud 专题二(spring cloud 入门搭建 之 微服务搭建和注册)
    spring cloud 专题一 (spring cloud 入门搭建 之 Eureka注册中心搭建)
    mysql存储过程查询结果循环遍历 判断 赋值 游标等基本操作
    Jquery datatable 动态隐藏列(根据有无值)
    spring boot无法启动,或者正常启动之后无法访问报404的解决办法
    通过js给网页加上水印背景
    jdk动态代理原理
    关于loadrunner使用web_add_header添加HTTP信息头(比如Content-Type,token等)和使用
    IP路由及静态路由配置
    安装ie时,报:此安装不支持您的操作系统的当前语言
  • 原文地址:https://www.cnblogs.com/hama1993/p/10331637.html
Copyright © 2011-2022 走看看