`
cunzhangok
  • 浏览: 66333 次
  • 性别: Icon_minigender_1
  • 来自: 江苏
社区版块
存档分类
最新评论

Spring 模板+回调

 
阅读更多
话回正转,这两天在读spring的jdbc模板,对Spring源码的精妙真是佩服得五体投地,极为经典。
spring中真是集设计模式之大成,而且用得是炉火纯青。模板方法(template method)就在spring中被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包围的包装等都无疑使用了模板模式,但spring并不是单纯使用了模板方法,而是在此基础上做了创新,配合callback(回调)一起使用,用得极其灵活。

OK,为了防止文章再被拍砖,我写得更详细点吧,我们首先来回顾一下模板模式:
所谓模板板式,就是在父类中定义算法的主要流程,而把一些个性化的步骤延迟到子类中去实现,父类始终控制着整个流程的主动权,子类只是辅助父类实现某些可定制的步骤。

有些抽象???
好吧,我们用代码来说话吧:
首先,父类要是个抽象类:
Java代码 
public abstract class TemplatePattern { 
 
    //模板方法 
    public final void templateMethod(){ 
         
        method1(); 
        method2();//勾子方法 
        method3();//抽象方法 
    } 
    private void method1(){ 
        System.out.println("父类实现业务逻辑"); 
    } 
    public void method2(){ 
        System.out.println("父类默认实现,子类可覆盖"); 
    } 
    protected abstract void method3();//子类负责实现业务逻辑 



父类中有三个方法,分别是method1(),method2()和method3()。
method1()是私有方法,有且只能由父类实现逻辑,由于方法是private的,所以只能父类调用。
method2()是所谓的勾子方法。父类提供默认实现,如果子类觉得有必要定制,则可以覆盖父类的默认实现。
method3()是子类必须实现的方法,即制定的步骤。
由此可看出,算法的流程执行顺序是由父类掌控的,子类只能配合。

下面我们来写第一个子类:
Java代码 
public class TemplatePatternImpl extends TemplatePattern { 
 
    @Override 
    protected void method3() { 
        System.out.println("method3()在子类TemplatePatternImpl中实现了!!"); 
 
    } 
 


这个子类只覆盖了必须覆盖的方法,我们来测试一下:
Java代码 
TemplatePattern t1 = new TemplatePatternImpl(); 
t1.templateMethod(); 

在控制台中我们可以看到:
Java代码 
父类实现业务逻辑 
父类默认实现,子类可覆盖 
method3()在子类TemplatePatternImpl中实现了!! 


OK,我们来看看勾子方法的使用:
定义第2个子类,实现勾子方法:
Java代码 
public class TemplatePatternImpl2 extends TemplatePattern { 
 
    @Override 
    protected void method3() { 
        System.out.println("method3()在子类TemplatePatternImpl2中实现了!!"); 
 
    } 
 
    /* (non-Javadoc)
     * @see com.jak.pattern.template.example.TemplatePattern#method2()
     */ 
    @Override 
    public void method2() { 
        System.out.println("子类TemplatePatternImpl2覆盖了父类的method2()方法!!"); 
    } 
     



来测试一下:
Java代码 
TemplatePattern t2 = new TemplatePatternImpl2(); 
t2.templateMethod(); 

我们看控制台:
Java代码 
父类实现业务逻辑 
子类TemplatePatternImpl2覆盖了父类的method2()方法!! 
method3()在子类TemplatePatternImpl2中实现了!! 


OK,经典的模板模式回顾完了(大家不要拍砖哦~~~~~~~~~~)

接下来,我们回到正题,自己模仿spring动手写一个基于模板模式和回调的jdbcTemplate。

回顾一下,spring为什么要封装JDBC API,对外提供jdbcTemplate呢(不要仍鸡蛋啊¥·%¥#%)
话说SUN的JDBC API也算是经典了,曾经在某个年代折服了一批人。但随着历史的发展,纯粹的JDBC API已经过于底层,而且不易控制,由开发人员直接接触JDBC API,会造成不可预知的风险。还有,数据连接缓存池的发展,也不可能让开发人员去手工获取JDBC了。

好了,我们来看一段曾经堪称经典的JDBC API代码吧:
Java代码 
public List<User> query() { 
 
    List<User> userList = new ArrayList<User>(); 
    String sql = "select * from User"; 
 
    Connection con = null; 
    PreparedStatement pst = null; 
    ResultSet rs = null; 
    try { 
        con = HsqldbUtil.getConnection(); 
        pst = con.prepareStatement(sql); 
        rs = pst.executeQuery(); 
 
        User user = null; 
        while (rs.next()) { 
 
            user = new User(); 
            user.setId(rs.getInt("id")); 
            user.setUserName(rs.getString("user_name")); 
            user.setBirth(rs.getDate("birth")); 
            user.setCreateDate(rs.getDate("create_date")); 
            userList.add(user); 
        } 
 
 
    } catch (SQLException e) { 
        e.printStackTrace(); 
    }finally{ 
        if(rs != null){ 
            try { 
                rs.close(); 
            } catch (SQLException e) { 
                e.printStackTrace(); 
            } 
        } 
        try { 
            pst.close(); 
        } catch (SQLException e) { 
            e.printStackTrace(); 
        } 
        try { 
            if(!con.isClosed()){ 
                try { 
                    con.close(); 
                } catch (SQLException e) { 
                    e.printStackTrace(); 
                } 
            } 
        } catch (SQLException e) { 
            e.printStackTrace(); 
        } 
         
    } 
    return userList; 



上面的代码要若干年前可能是一段十分经典的,还可能被作为example被推广。但时过境迁,倘若哪位程序员现在再在自己的程序中出现以上代码,不是说明该公司的开发框架管理混乱,就说明这位程序员水平太“高”了。
我们试想,一个简单的查询,就要做这么一大堆事情,而且还要处理异常,我们不防来梳理一下:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常
6、.....
啊~~~~ 我快要晕了,在面向对象编程的年代里,这样的代码简直不能上人容忍。试想,上面我们只是做了一张表的查询,如果我们要做第2张表,第3张表呢,又是一堆重复的代码:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常
6、.....

这时候,使用模板模式的时机到了!!!

通过观察我们发现上面步骤中大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是可定制的,因为每张表都映射不同的java bean。这部分代码是没有办法复用的,只能定制。那就让我们用一个抽象的父类把它们封装一下吧:
Java代码 
public abstract class JdbcTemplate { 
 
    //template method 
    public final Object execute(String sql) throws SQLException{ 
         
        Connection con = HsqldbUtil.getConnection(); 
        Statement stmt = null; 
        try { 
  
            stmt = con.createStatement(); 
            ResultSet rs = stmt.executeQuery(sql); 
            Object result = doInStatement(rs);//abstract method  
            return result; 
        } 
        catch (SQLException ex) { 
             ex.printStackTrace(); 
             throw ex; 
        } 
        finally { 
  
            try { 
                stmt.close(); 
            } catch (SQLException e) { 
                e.printStackTrace(); 
            } 
            try { 
                if(!con.isClosed()){ 
                    try { 
                        con.close(); 
                    } catch (SQLException e) { 
                        e.printStackTrace(); 
                    } 
                } 
            } catch (SQLException e) { 
                e.printStackTrace(); 
            } 
             
        } 
    } 
     
    //implements in subclass 
    protected abstract Object doInStatement(ResultSet rs); 


在上面这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,由子类负责实现。
好,我们来定义一个子类,并继承上面的父类:
Java代码 
public class JdbcTemplateUserImpl extends JdbcTemplate { 
 
    @Override 
    protected Object doInStatement(ResultSet rs) { 
        List<User> userList = new ArrayList<User>(); 
         
        try { 
            User user = null; 
            while (rs.next()) { 
 
                user = new User(); 
                user.setId(rs.getInt("id")); 
                user.setUserName(rs.getString("user_name")); 
                user.setBirth(rs.getDate("birth")); 
                user.setCreateDate(rs.getDate("create_date")); 
                userList.add(user); 
            } 
            return userList; 
        } catch (SQLException e) { 
            e.printStackTrace(); 
            return null; 
        } 
    } 
 


由代码可见,我们在doInStatement()方法中,对ResultSet进行了遍历,最后并返回。
有人可能要问:我如何获取ResultSet 并传给doInStatement()方法啊??呵呵,问这个问题的大多是新手。因为此方法不是由子类调用的,而是由父类调用,并把ResultSet传递给子类的。我们来看一下测试代码:
Java代码 
String sql = "select * from User"; 
JdbcTemplate jt = new JdbcTemplateUserImpl(); 
List<User> userList = (List<User>) jt.execute(sql); 


就是这么简单!!

文章至此仿佛告一段落,莫急!不防让我们更深入一些...

试想,如果我每次用jdbcTemplate时,都要继承一下上面的父类,是不是有些不方面呢?
那就让我们甩掉abstract这顶帽子吧,这时,就该callback(回调)上场了


所谓回调,就是方法参数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。

那我们就来把上面的代码改造一下,改用回调实现吧:

首先,我们来定义一个回调接口:
Java代码 
public interface StatementCallback { 
    Object doInStatement(Statement stmt) throws SQLException; 



这时候,我们就要方法的签名改一下了:
Java代码 
private final Object execute(StatementCallback action) throws SQLException 


里面的获取数据方式也要做如下修改:
Java代码 
Object result = action.doInStatement(stmt);//abstract method  


为了看着顺眼,我们来给他封装一层吧:
Java代码 
public Object query(StatementCallback stmt) throws SQLException{ 
    return execute(stmt); 



OK,大功告成!
我们来写一个测试类Test.java测试一下吧:
这时候,访问有两种方式,一种是内部类的方式,一种是匿名方式。

先来看看内部类的方式:
Java代码 
//内部类方式 
    public Object query(final String sql) throws SQLException { 
        class QueryStatementCallback implements StatementCallback { 
 
            public Object doInStatement(Statement stmt) throws SQLException { 
                ResultSet rs = stmt.executeQuery(sql); 
                List<User> userList = new ArrayList<User>(); 
 
                User user = null; 
                while (rs.next()) { 
 
                    user = new User(); 
                    user.setId(rs.getInt("id")); 
                    user.setUserName(rs.getString("user_name")); 
                    user.setBirth(rs.getDate("birth")); 
                    user.setCreateDate(rs.getDate("create_date")); 
                    userList.add(user); 
                } 
                return userList; 
 
            } 
 
        } 
 
        JdbcTemplate jt = new JdbcTemplate(); 
        return jt.query(new QueryStatementCallback()); 
    } 


在调用jdbcTemplate.query()方法时,传一个StatementCallBack()的实例过去,也就是我们的内部类。

再来看看匿名方式:
Java代码 
//匿名类方式 
    public Object query2(final String sql) throws Exception{ 
         
        JdbcTemplate jt = new JdbcTemplate(); 
        return jt.query(new StatementCallback() { 
             
            public Object doInStatement(Statement stmt) throws SQLException { 
                ResultSet rs = stmt.executeQuery(sql); 
                List<User> userList = new ArrayList<User>(); 
 
                User user = null; 
                while (rs.next()) { 
 
                    user = new User(); 
                    user.setId(rs.getInt("id")); 
                    user.setUserName(rs.getString("user_name")); 
                    user.setBirth(rs.getDate("birth")); 
                    user.setCreateDate(rs.getDate("create_date")); 
                    userList.add(user); 
                } 
                return userList; 
 
            } 
        }); 
         
    } 

相比之下,这种方法更为简洁。
为什么spring不用传统的模板方法,而加之以Callback进行配合呢?
试想,如果父类中有10个抽象方法,而继承它的所有子类则要将这10个抽象方法全部实现,子类显得非常臃肿。而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了。

离spring jdbcTemplate再近一点
上面这种方式基本上实现了模板方法+回调模式。但离spring的jdbcTemplate还有些距离。
我们可以再深入一些。。。

我们上面虽然实现了模板方法+回调模式,但相对于Spring的JdbcTemplate则显得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。
RowMapper接口负责处理某一行的数据,例如,我们可以在mapRow方法里对某一行记录进行操作,或封装成entity。
ResultSetExtractor是数据集抽取器,负责遍历ResultSet并根据RowMapper里的规则对数据进行处理。
RowMapper和ResultSetExtractor区别是,RowMapper是处理某一行数据,返回一个实体对象。而ResultSetExtractor是处理一个数据集合,返回一个对象集合。

当然,上面所述仅仅是Spring JdbcTemplte实现的基本原理,Spring JdbcTemplate内部还做了更多的事情,比如,把所有的基本操作都封装到JdbcOperations接口内,以及采用JdbcAccessor来管理DataSource和转换异常等。
分享到:
评论
1 楼 thrillerzw 2013-06-08  
"试想,如果父类中有10个抽象方法,而继承它的所有子类则要将这10个抽象方法全部实现,子类显得非常臃肿。而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了。"
这个怎么就不需要实现10个抽象方法了?只实现一个excute流程,还怎么执行呢。

相关推荐

    Spring 学习 JdbcTemplate,模板模式,回调

    本主题将深入探讨Spring框架中的JdbcTemplate组件,以及模板模式和回调机制的概念。 **1. Spring JdbcTemplate** JdbcTemplate是Spring提供的一个用于简化数据库操作的API,它是Spring对JDBC(Java Database ...

    【转】Spring中模板模式和回调模式的讲解

    在Spring框架中,模板模式和回调模式是两种重要的设计模式,它们被广泛应用于处理各种不同的任务,如数据访问、远程调用等。这两种模式都旨在提高代码的可扩展性和复用性,使得开发者能够以更加灵活的方式处理业务...

    spring模板模代码

    下面将详细探讨Spring模板模式的原理、应用以及接口回调的概念。 **模板模式** 是一种行为设计模式,它定义了算法的骨架,并允许在特定步骤中延迟或定制某些行为。在Spring框架中,模板类通常负责执行网络请求、...

    spring + spring mvc + hibernate + mysql 整合开发任务流程后台管理系统

    Spring MVC通过DispatcherServlet、Controller、Model-View-Controller模式以及一系列的回调方法来组织和管理业务逻辑。 接着,Hibernate是一个强大的对象关系映射(ORM)框架,它可以将数据库中的表与Java类映射,...

    struts2+spring2.5+jdbc+ext+json实例用到的lib包

    Spring 的 JDBC 支持简化了数据库操作,通过模板方法和回调机制减少了代码量,同时提供了事务管理和数据源管理等高级功能。 JDBC(Java Database Connectivity)是 Java 与数据库交互的标准接口。尽管 Spring 提供...

    59丨模板模式(下):模板模式与Callback回调函数有何区别和联系?1

    例如,Spring框架中的`JdbcTemplate`虽然名字中含有“Template”,但它其实主要依赖于回调机制。`JdbcTemplate`执行SQL查询时,会调用用户提供的回调方法来处理结果集,这类似于同步回调,因为结果处理在查询完成后...

    Spring boot + thymeleaf 后端直接给onclick函数赋值的实现代码

    在上面的示例代码中,我们使用了 jQuery 的 AJAX 方法来发送数据到服务端,并在成功回调函数中处理返回的数据。 本篇文章介绍了如何在 Spring Boot 应用程序中使用 Thymeleaf 实现后端直接给 onclick 函数赋值的...

    springmvc+springjdbc+maven 后端架构

    它简化了JDBC的配置和使用,通过模板方法和回调机制减少了代码量。Spring JDBC还支持事务管理,使开发者能够轻松处理数据库事务的提交和回滚。 **Maven** Maven 是一个项目管理和综合工具,它帮助开发者管理依赖...

    spring boot + uploadifive 文件上传

    你需要指定文件上传的URL(对应Spring Boot的`/upload`接口),以及上传成功的回调函数。 ```html &lt;!DOCTYPE html&gt; &lt;script src="https://code.jquery.com/jquery-3.6.0.min.js"&gt;&lt;/script&gt; ...

    Spring+JMS+消息处理

    - **Session Callback(会话回调)**:另一个回调接口,用于处理更复杂的JMS操作,如事务管理等。 #### 三、Spring JMS配置示例 Spring JMS的配置通常是在Spring的XML配置文件中完成的。下面展示了一个简单的配置...

    maven+spring+mybatis+mysql整合

    如果在回调函数中发生异常,TransactionTemplate会自动回滚事务,否则,当回调函数正常结束时,事务会被提交。 整合上述技术栈的过程主要包括以下步骤: 1. 配置Maven的`pom.xml`文件,引入Spring、MyBatis、MySQL...

    spring+velocity+ajax带进度条上传文件

    使用Ajax,我们可以在后台监测上传进度,并通过回调函数更新前端的进度条。通常,服务器需要返回上传进度信息,客户端则根据这些信息动态更新进度条。在Java中,可以通过监听FileInputStream或使用第三方库如Apache ...

    Spring jdbctemplate + mysql 分页封装

    JdbcTemplate是Spring提供的一个模板类,用于执行SQL语句,它通过回调模式简化了数据访问层的编写。在使用JdbcTemplate时,我们需要配置数据库连接信息,创建JdbcTemplate实例,然后调用其提供的方法执行SQL。 在...

    spring+portlet+mvc

    Spring Portlet MVC在这些阶段中提供了相应的回调方法,如initPortlet、doRender和doDispatch。开发者可以在这些方法中执行初始化逻辑、渲染逻辑和处理用户请求。 六、portlet与Spring Portlet MVC集成 要将portlet...

    使用Spring的事务模板

    3. 使用`TransactionTemplate`执行事务:在事务操作中,你可以使用`execute`方法,它接受一个回调函数,所有在这个回调函数中的操作都在事务范围内执行。如果回调函数抛出异常,事务会自动回滚;否则,事务在回调...

    oss文件上传(带回调)

    【标题】"OSS文件上传(带回调)"指的是在Spring Boot应用中集成对象存储服务(Object Storage Service,简称OSS),并实现文件上传功能,同时具备回调机制,即在文件上传完成后,系统会自动执行预设的回调函数,通常...

    Spring持久化模板总结

    4. **回调机制**:HibernateTemplate提供了doInHibernate()和doInTransaction()等方法,允许用户自定义回调函数,进行更复杂的数据库操作。 **JdbcDaoSupport和HibernateDaoSupport** 这两个类是Spring提供的一般...

    spring2.0学习笔记+spring定时任务

    在Spring 2.0中,我们可以使用Bean的生命周期回调方法来初始化和清理Bean。@PostConstruct和@PreDestroy注解分别标记在初始化方法和清理方法上,使得在Bean的生命周期中能有条不紊地执行特定操作。 对于“源码”...

    Spring源代码解析(三):Spring_JDBC.doc

    总的来说,Spring JDBC的JdbcTemplate是Spring框架中用于数据库操作的关键组件,通过回调接口和模板方法设计,它极大地简化了数据库访问的代码,同时也提供了良好的异常处理和资源管理机制。理解和掌握JdbcTemplate...

Global site tag (gtag.js) - Google Analytics