詳解java模板和回調(diào)機(jī)制
最近看spring的JDBCTemplete的模板方式調(diào)用時(shí),對模板和回調(diào)產(chǎn)生了濃厚興趣,查詢了一些資料,做一些總結(jié)。
回調(diào)函數(shù):
所謂回調(diào),就是客戶程序C調(diào)用服務(wù)程序S中的某個(gè)函數(shù)A,然后S又在某個(gè)時(shí)候反過來調(diào)用C中的某個(gè)函數(shù)B,對于C來說,這個(gè)B便叫做回調(diào)函數(shù)。回調(diào)函數(shù)只是一個(gè)功能片段,由用戶按照回調(diào)函數(shù)調(diào)用約定來實(shí)現(xiàn)的一個(gè)函數(shù)?;卣{(diào)函數(shù)是一個(gè)工作流的一部分,由工作流來決定函數(shù)的調(diào)用(回調(diào))時(shí)機(jī)。一般說來,C不會(huì)自己調(diào)用B,C提供B的目的就是讓S來調(diào)用它,而且是C不得不提供。由于S并不知道C提供的B姓甚名誰,所以S會(huì)約定B的接口規(guī)范(函數(shù)原型),然后由C提前通過S的一個(gè)函數(shù)R告訴S自己將要使用B函數(shù),這個(gè)過程稱為回調(diào)函數(shù)的注冊,R稱為注冊函數(shù)。Web Service以及Java 的RMI都用到回調(diào)機(jī)制,可以訪問遠(yuǎn)程服務(wù)器程序?;卣{(diào)函數(shù)包含下面幾個(gè)特性:
1、屬于工作流的一個(gè)部分;
2、必須按照工作流指定的調(diào)用約定來申明(定義);
3、他的調(diào)用時(shí)機(jī)由工作流決定,回調(diào)函數(shù)的實(shí)現(xiàn)者不能直接調(diào)用回調(diào)函數(shù)來實(shí)現(xiàn)工作流的功能;
回調(diào)機(jī)制:
回調(diào)機(jī)制是一種常見的設(shè)計(jì)模型,他把工作流內(nèi)的某個(gè)功能,按照約定的接口暴露給外部使用者,為外部使用者提供數(shù)據(jù),或要求外部使用者提供數(shù)據(jù)。
java回調(diào)機(jī)制:
軟件模塊之間總是存在著一定的接口,從調(diào)用方式上,可以把他們分為三類:同步調(diào)用、回調(diào)和異步調(diào)用。
同步調(diào)用:一種阻塞式調(diào)用,調(diào)用方要等待對方執(zhí)行完畢才返回,它是一種單向調(diào)用;
回 調(diào):一種雙向調(diào)用模式,也就是說,被調(diào)用方在接口被調(diào)用時(shí)也會(huì)調(diào)用對方的接口;
異步調(diào)用:一種類似消息或事件的機(jī)制,不過它的調(diào)用方向剛好相反,接口的服務(wù)在收到某種訊息或發(fā)生某種事件時(shí),會(huì)主動(dòng)通知客戶方(即調(diào)用客戶方的接口)。
回調(diào)和異步調(diào)用的關(guān)系非常緊密:使用回調(diào)來實(shí)現(xiàn)異步消息的注冊,通過異步調(diào)用來實(shí)現(xiàn)消息的通知。
回調(diào)實(shí)例
1、回調(diào)接口
public interface Callback {
String callBack();
}
2、調(diào)用者
public class Another {
private Callback callback;
//調(diào)用實(shí)現(xiàn)類的方法
public void setCallback(Callback callback) {
this.callback = callback;
}
//業(yè)務(wù)需要的時(shí)候,通過委派,來調(diào)用實(shí)現(xiàn)類的具體方法
public void doCallback(){
System.out.println(callback.callBack());
}
}
3、測試回調(diào)函數(shù)
public class TestCallcack {
public static void main(String[] args) {
//創(chuàng)建調(diào)用者的實(shí)現(xiàn)類
Another another = new Another();
//將回掉接口注冊到實(shí)現(xiàn)類中
another.setCallback(new Callback() {
@Override
public String callBack() {
return "you are a pig";
}
});
//執(zhí)行回調(diào)函數(shù)
another.doCallback();
}
}
回調(diào)方法的使用通常發(fā)生在“java接口”和“抽象類”的使用過程中。模板方法設(shè)計(jì)模式就使用方法回調(diào)的機(jī)制,該模式首先定義特定的步驟的算法骨架,而將一些步驟延遲到子類中去實(shí)現(xiàn)的設(shè)計(jì)模式。模板方法設(shè)計(jì)模式使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重新定義該算法的某些特定步驟。
模板方式設(shè)計(jì)模式的適用性:
1、一次性實(shí)現(xiàn)一個(gè)算法的不變部分,并將可變的算法留給子類來實(shí)現(xiàn)。
2、各子類中公共的行為應(yīng)該被提取出來并集中一個(gè)公共父類中以避免代碼重復(fù)。
3、可以控制子類擴(kuò)展。
模板實(shí)例:
抽象模板方法類:
public abstract class AbstractSup {
//需要子類實(shí)現(xiàn)的方法
public abstract void print();
//模板方法
public void doPrint(){
System.out.println("執(zhí)行模板方法");
for (int i = 0; i < 3; i++) {
print();
}
}
}
子類實(shí)現(xiàn)模板方式類:
public class SubClass extends AbstractSup{
@Override
public void print() {
System.out.println("子類的實(shí)現(xiàn)方法");
}
}
模板方法測試類:
public class TempleteTest {
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.print();
subClass.doPrint();
}
}
下面深入介紹下spring模板方法的使用,以JdbcTemplete為例,詳細(xì)說明模板模式和回調(diào)機(jī)制的使用。
首先看一下經(jīng)典的JDBC編程的例子:
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;
}
一個(gè)簡單的查詢,就要做這么一大堆事情,而且還要處理異常,我們不防來梳理一下:
1、獲取connection
2、獲取statement
3、獲取resultset
4、遍歷resultset并封裝成集合
5、依次關(guān)閉connection,statement,resultset,而且還要考慮各種異常等等。
如果是多個(gè)查詢會(huì)產(chǎn)生較多的重復(fù)代碼,這時(shí)候就可以使用模板機(jī)制,通過觀察我們發(fā)現(xiàn)上面步驟中大多數(shù)都是重復(fù)的,可復(fù)用的,只有在遍歷ResultSet并封裝成集合的這一步驟是可定制的,因?yàn)槊繌埍矶加成洳煌膉ava bean。這部分代碼是沒有辦法復(fù)用的,只能定制。
抽象類代碼:
public abstract class JdbcTemplate {
//模板方法
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);//抽象方法(定制方法,需要子類實(shí)現(xiàn))
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();
}
}
}
//抽象方法(定制方法)
protected abstract Object doInStatement(ResultSet rs);
}
這個(gè)抽象類中,封裝了SUN JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,由子類負(fù)責(zé)實(shí)現(xiàn)。
子類實(shí)現(xiàn)代碼:
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進(jìn)行了遍歷,最后并返回。
測試代碼:
String sql = "select * from User"; JdbcTemplate jt = new JdbcTemplateUserImpl(); List<User> userList = (List<User>) jt.execute(sql);
模板機(jī)制的使用到此為止,但是如果每次調(diào)用jdbcTemplate時(shí),都要繼承一下上面的父類,這樣挺不方便的,這樣回調(diào)機(jī)制就可以發(fā)揮作用了。
所謂回調(diào),就是方法參數(shù)中傳遞一個(gè)接口,父類在調(diào)用此方法時(shí),必須調(diào)用方法中傳遞的接口的實(shí)現(xiàn)類。
回調(diào)加模板模式實(shí)現(xiàn)
回調(diào)接口:
public interface StatementCallback {
Object doInStatement(Statement stmt) throws SQLException;
}
模板方法:
public class JdbcTemplate {
//模板方法
public final Object execute(StatementCallback action) throws SQLException{
Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
try {
stmt = con.createStatement();
Object result = action.doInStatement(rs);//回調(diào)方法
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();
}
}
}
}
public Object query(StatementCallback stmt) throws SQLException{
return execute(stmt);
}
}
測試的類:
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());
}
為什么spring不用傳統(tǒng)的模板方法,而加之以Callback進(jìn)行配合呢?
試想,如果父類中有10個(gè)抽象方法,而繼承它的所有子類則要將這10個(gè)抽象方法全部實(shí)現(xiàn),子類顯得非常臃腫。而有時(shí)候某個(gè)子類只需要定制父類中的某一個(gè)方法該怎么辦呢?這個(gè)時(shí)候就要用到Callback回調(diào)了。
另外,上面這種方式基本上實(shí)現(xiàn)了模板方法+回調(diào)模式。但離spring的jdbcTemplate還有些距離。 我們上面雖然實(shí)現(xiàn)了模板方法+回調(diào)模式,但相對于Spring的JdbcTemplate則顯得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。 RowMapper接口負(fù)責(zé)處理某一行的數(shù)據(jù),例如,我們可以在mapRow方法里對某一行記錄進(jìn)行操作,或封裝成entity。 ResultSetExtractor是數(shù)據(jù)集抽取器,負(fù)責(zé)遍歷ResultSet并根據(jù)RowMapper里的規(guī)則對數(shù)據(jù)進(jìn)行處理。 RowMapper和ResultSetExtractor區(qū)別是,RowMapper是處理某一行數(shù)據(jù),返回一個(gè)實(shí)體對象。而ResultSetExtractor是處理一個(gè)數(shù)據(jù)集合,返回一個(gè)對象集合。
當(dāng)然,上面所述僅僅是Spring JdbcTemplte實(shí)現(xiàn)的基本原理,Spring JdbcTemplate內(nèi)部還做了更多的事情,比如,把所有的基本操作都封裝到JdbcOperations接口內(nèi),以及采用JdbcAccessor來管理DataSource和轉(zhuǎn)換異常等。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Spring使用注解方式實(shí)現(xiàn)創(chuàng)建對象
這篇文章主要介紹了Spring使用注解方式實(shí)現(xiàn)創(chuàng)建對象,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2023-02-02
一篇文章教你使用枚舉來實(shí)現(xiàn)java單例模式
本篇文章主要介紹了Java實(shí)現(xiàn)單例的3種普遍的模式,餓漢式、懶漢式、枚舉式。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能給你帶來幫助2021-07-07
Spring Boot使用Spring的異步線程池的實(shí)現(xiàn)
這篇文章主要介紹了Spring Boot使用Spring的異步線程池的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Java設(shè)計(jì)模式之橋接模式詳解(Bridge Pattern)
橋接模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,旨在將抽象部分與其實(shí)現(xiàn)部分分離,從而使兩者可以獨(dú)立地變化,橋接模式通過組合關(guān)系代替繼承關(guān)系,將抽象和實(shí)現(xiàn)解耦,使代碼更具擴(kuò)展性和維護(hù)性2025-02-02

