在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

菜鸟简单模仿一下spring的ioc和aop思想

2017-2-7 13:41| 发布者: zhangjf| 查看: 508| 评论: 0

摘要: 一直以来对spring充满着敬意,它有着完美的解耦功能,还有很多自己对许多工具の封装,例如 jdbcTemplate、redisTemplate、hibernateTemplate等等,还有强大的切面编程(aop),集这些优点于一身的 spring到目前依然 ...
一直以来对spring充满着敬意,它有着完美的解耦功能,还有很多自己对许多工具の封装,例如 jdbcTemplate、redisTemplate、hibernateTemplate等等,还有强大的切面编程(aop),集这些优点于一身的 spring到目前依然是很多企业乃至大企业光顾的对象

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

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

项目の目录结构:

菜鸟简单模仿一下spring的ioc和aop思想

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

红线部分说明:

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

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

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

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

主要几个类的代码:

① BeanFactory:

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

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

  1. userDao = sun.juwin.dao.impl.UserDaoImpl
  2. userDetailDao = sun.juwin.dao.impl.UserDetailDaoImpl
复制代码

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

② TransactionProxy代理类:

  1. package sun.juwin.proxy.transctional;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. import java.sql.Connection;
  6. /**
  7. * 事务代理类,通过这个类可以为要执行的方法加上事务管理
  8. */
  9. public class TransactionProxy implements InvocationHandler {
  10. private Object targetObj;
  11. public Object getTargetObj(Object targetObj){
  12. this.targetObj = targetObj;
  13. return Proxy.newProxyInstance(this.targetObj.getClass().getClassLoader(),
  14. this.targetObj.getClass().getInterfaces(), this);
  15. }
  16. /*下面这个方法会在被代理类执行方法时调用,拿到被代理类的要执行的method对象*/
  17. @Override
  18. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  19. Object result = null;
  20. Connection connection = (Connection)args[0];//要求第一个参数必须是conn
  21. try{
  22. connection.setAutoCommit(false);//开启事务
  23. result = method.invoke(this.targetObj, args);//执行目标方法
  24. connection.commit();//事务提交
  25. System.out.print("commit success!");
  26. }catch (Exception e){
  27. connection.rollback();//事务回滚
  28. System.err.println("rollback!");
  29. e.printStackTrace();
  30. }finally {
  31. connection.close();//关闭连接
  32. System.out.println("connection closed!");
  33. }
  34. return result;
  35. }
  36. }
复制代码

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

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

  1. package sun.juwin.db;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. /*原始产生数据库连接的类*/
  5. public class ConnBean {
  6. private static ThreadLocal<Connection> conn = new ThreadLocal<>();
  7. private ConnBean(){}
  8. public static Connection getConn(){
  9. Connection connection = conn.get();
  10. if(connection == null){
  11. synchronized (ConnBean.class){//由于用到了ThreadLocal,因此该单例仅仅相对于当前线程是单例的
  12. if(connection == null){
  13. try{
  14. Connection realConn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_useradd", "root", "");
  15. conn.set(realConn);
  16. }catch (Exception e){
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. }
  22. return conn.get();//返回给当前线程一个Connection对象
  23. }
  24. }
复制代码

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

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

User:

  1. private Long id;
  2. private String userName;
  3. private String address;
  4. private int money;
复制代码

UserDetail:

  1. private Long id;
  2. private int age;
  3. private String realname;
复制代码

dao层の接口和实现:

UserDao:

  1. public interface UserDao {
  2. public void save(User user, Connection conn)throws Exception;
  3. }
复制代码

UserDaoImpl:

  1. public class UserDaoImpl implements UserDao{
  2. @Override
  3. public void save(User user, Connection conn) throws Exception {
  4. Statement statement = conn.createStatement();//为了省去不必要的麻烦,我们不用预编译语句
  5. String sql = "insert into tb_user (userName, address, money) values ('"
  6. + user.getUserName() + "', '"
  7. + user.getAddress() + "', "
  8. + user.getMoney() + ")";
  9. statement.executeUpdate(sql);
  10. statement.close();
  11. }
  12. }
复制代码

UserDetailDao:

  1. public interface UserDetailDao {
  2. public void save(UserDetail userDetail, Connection connection) throws Exception;
  3. }
复制代码

UserDetailDaoImpl:

  1. public class UserDetailDaoImpl implements UserDetailDao {
  2. @Override
  3. public void save(UserDetail userDetail, Connection connection) throws Exception {
  4. Statement statement = connection.createStatement();
  5. String sql = "insert into user_detail (age, realname) values ("
  6. +userDetail.getAge()+", '"
  7. +userDetail.getRealname()+"')";
  8. statement.executeUpdate(sql);
  9. }
  10. }
复制代码

UserService:

  1. public interface UserService {
  2. public void saveService(Connection connection, User user) throws Exception;
  3. }
复制代码

UserServiceImpl

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

UserController:

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

测试类:

  1. public class UserAddTest {
  2. @Test
  3. public void Test1() throws Exception{
  4. User user = new User();
  5. user.setUserName("weixiaojie1993");
  6. user.setAddress("beijing");
  7. user.setMoney(1);
  8. UserController userController = new UserController();
  9. userController.SaveUser(user);
  10. System.out.print("Done !");
  11. }
  12. }
复制代码

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

service层不加

  1. throw new Exception("拦-路-虎");
复制代码

执行结果:

菜鸟简单模仿一下spring的ioc和aop思想

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

tb_user表:

菜鸟简单模仿一下spring的ioc和aop思想

user_detail表:

菜鸟简单模仿一下spring的ioc和aop思想

然后在业务层加上

  1. throw new Exception("拦-路-虎");
复制代码

运行结果:

菜鸟简单模仿一下spring的ioc和aop思想

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

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

tb_user:

菜鸟简单模仿一下spring的ioc和aop思想

user_detail:

菜鸟简单模仿一下spring的ioc和aop思想

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

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

来自:http://my.oschina.net/sunqinwen/blog/539397

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-9 10:49

Copyright 2015-2025 djqfx

返回顶部