1.代理模式概述
學習內容
1)概述
為什么要有 “代理” ?
- 生活中就有很多例子,比如委托業務,黃牛(票販子)等等
- 代理就是被代理者沒有能力或者不愿意去完成某件事情,需要找個人代替自己去完成這件事,這才是“代理”存在的原因。
- 例如要租房子,房產中介可以在我們住房前代理我們找房子。中介就是代理,而自己就是被代理了。
在代碼設計中,代理模式作用主要就是讓 “被代理對象” 的某個方法執行之前或者執行之后加入其他增強邏輯。
前增強 : 例如獲取當前時間被代理對象調用方法后增強 : 例如獲取當前時間計算方法執行的時間
2)代理的前提條件 : 掌握 !
- 抽象角色 :聲明功能
- 代理角色 :實現抽象功能 , 完成代理邏輯
- 被代理角色 :實現抽象功能
意味著被代理角色和代理角色有著共同的父類型(既抽象角色) , 例如我要租房子, 我只能找房產中介, 不能找票販子
-
代理模式存在兩種實現方式:
- 靜態代理
- 動態代理
知識小結
-
請說出代碼中代理模式的作用?
代理角色對 被代理就角色某個方法執行的前或者后進行 功能增強
-
請說出代理模式中的三個角色?
抽象角色 代理角色 被代理角色
==============================================================================================================
1.1 靜態代理
學習目標
- 能夠寫出靜態代理模式代碼
內容講解
-
靜態代理是由程序員創建 或 工具生成代理類的源碼,再編譯代理類。
在程序運行前就已經存在代理類的字節碼文件,代理類和被代理類的關系在運行前就確定了。
簡單理解 : 在程序運行之前 , 代理類就存在了,這就是靜態代理 ; 動態代理是程序運行時動態生成代理類
-
靜態代理實現的步驟 :
- 存在一個抽象角色
- 定義被代理角色
- 定義代理,增強被代理角色的功能
案例實踐 :
- 以現實中經紀人代理明星
已知存在接口:
// 1.抽象角色
interface Star {// 真人秀方法double liveShow(double money);void sleep();
}
定義被代理類:
- 定義王寶強類,實現Star方法
// - 定義被代理角色(寶強)
class BaoQiang implements Star {@Overridepublic double liveShow(double money) {System.out.println("寶強參加了一個真人秀活動賺了" + money + "錢");return money;}@Overridepublic void sleep() {System.out.println("寶強累了 , 睡覺...");}
}
定義代理類:
- 定義宋喆經紀人類
// - 定義代理角色(宋喆),增強被代理角色的功能
class SongZhe implements Star {private BaoQiang baoQiang;public SongZhe(BaoQiang baoQiang) {this.baoQiang = baoQiang;}@Overridepublic double liveShow(double money) {// 前增強System.out.println("宋喆幫寶強拉了一個真人秀的活動,獲取傭金" + money * 0.8 + "元");// 被代理角色的功能double result = baoQiang.liveShow(money * 0.2);// 后增強System.out.println("宋喆幫寶強把賺的錢存了起來...");return result;}@Overridepublic void sleep() {// 前增強System.out.println("宋喆幫寶強定了一家五星級大酒店");baoQiang.sleep();// 后增強System.out.println("宋喆幫寶強退房...");}
}
定義測試類進行測試
/*靜態代理實現的步驟 :- 存在一個抽象角色- 定義被代理角色(寶強)- 定義代理角色(宋喆),增強被代理角色的功能*/
public class StaticAgentDemo {public static void main(String[] args) {// 創建被代理角色 , 沒有任何增強BaoQiang baoQiang = new BaoQiang();double result = baoQiang.liveShow(1000);System.out.println(result);baoQiang.sleep();System.out.println("===========================");// 創建代碼角色對象 , 可以對被代理角色功能做前后增強SongZhe songZhe = new SongZhe(baoQiang);double result2 = songZhe.liveShow(1000);System.out.println(result2);songZhe.sleep();}
}
關系圖 :
宋喆和寶強都有共同的父類型。他們的業務方法都是一樣。
靜態代理和裝飾模式的對比 :
? BufferedRead(FileRead)
? 1 裝飾設計模式是功能擴展功能,在原有的功能基礎之上增加了新的功能
? 2 而代理主要對功能的前后做了增強
知識小結
-
請問什么叫做靜態代理?
代碼執行前,已經確定了代理的代碼邏輯。
2. 動態代理
學習目標
- 能夠知道什么是動態代理
- 能夠熟悉動態代理相關API
- 能夠熟悉動態代理代碼執行流程
內容講解
1)概述
在實際開發過程中往往我們自己不會去創建代理類而是通過JDK提供的Proxy
類在程序運行時,運用反射機制動態創建而成
這就是我們所謂的動態代理。
與靜態代理之間的區別,在于不用自己寫代理類
雖然我們不需要自己定義代理類創建代理對象
但是我們要定義對被代理對象直接訪問方法的攔截,原因就是對攔截的方法做增強。
動態代理技術在框架中使用居多,例如:很快要學到的數據庫框架MyBatis框架等一些主流框架技術(Spring,SpringMVC)中都使用了動態代理技術。
2)API學習
Proxy類
java.lang.reflect.Proxy
類提供了用于創建動態代理類和對象的靜態方法
? 它還是由這些方法創建的所有動態代理類的超類(代理類的父類是Proxy)。
public static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h ) 獲取代理對象的方法 - 返回值:該方法返回就是動態生成的代理對象
- 參數列表說明:1. ClassLoader loader - 定義代理類的類加載器2. Class<?>[] interfaces - 代理類要實現的接口列表,要求與被代理類的接口一樣。3. InvocationHandler h - 就是具體實現代理邏輯的接口
InvocationHandler接口
源碼 :
interface InvocationHandler{public Object invoke(Object proxy, Method method, Object[] args); //代理邏輯
}
java.lang.reflect.InvocationHandler
是代理對象的實際處理代理邏輯的接口,具體代理實現邏輯在其 invoke 方法中。所有代理對象調用的方法,執行是都會經過invoke。因此如果要對某個方法進行代理增強,就可以在這個invoke方法中進行定義。
方法說明如下:
public Object invoke(Object proxy, Method method, Object[] args);1. 返回值:方法被代理后執行的結果。
2. 參數列表:1. proxy - 就是代理對象2. method - 代理對象調用的方法3. args - 代理對象調用方法傳入參數值的對象數組.
3)代碼實踐
將經紀人代理明星的案例使用動態代理實現
- 把父接口定義
- 定義被代理類:寶強
- 動態生成代理類
- 定義代理邏輯
package com.itheima.dynamicproxy_demo;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 測試類
/*1 Proxy類 :public static Object newProxyInstance (ClassLoader loader,Class<?>[] interfaces,InvocationHandler h ) 獲取代理對象的方法- 返回值:該方法返回就是動態生成的代理對象- 參數列表說明:1. ClassLoader loader - 定義代理類的類加載器2. Class<?>[] interfaces - 代理類要實現的接口列表,要求與被代理類的接口一樣。3. InvocationHandler h - 就是具體實現代理邏輯的接口2 InvocationHandler接口public Object invoke(Object proxy, Method method, Object[] args);1. 返回值:方法被代理后執行的結果。2. 參數列表:1. proxy - 就是代理對象2. method - 代理對象調用的方法3. args - 代理對象調用方法傳入參數值的對象數組.*/
public class DynamicProxyDemo {public static void main(String[] args) {// Proxy.newProxyInstance(被代理角色的類加載器 , 被代理角色實現的所有接口 , 處理器);// 被代理角色的類加載器ClassLoader classLoader = BaoQiang.class.getClassLoader();// 被代理角色實現的所有接口Class<?>[] interfaces = BaoQiang.class.getInterfaces();// 創建被代理角色對象BaoQiang baoQiang = new BaoQiang();// 代理角色 , 動態生成Star songZhe = (Star) Proxy.newProxyInstance(classLoader, interfaces, new MyInvocationHandler(baoQiang));// 代理角色調用liveShow方法songZhe.liveShow(1000);songZhe.sleep();}
}// 定義InvocationHandler接口的實現類
class MyInvocationHandler implements InvocationHandler {private BaoQiang baoQiang;public MyInvocationHandler(BaoQiang baoQiang) {this.baoQiang = baoQiang;}// invoke什么時候會執行????// 代理對象調用功能 , 就會觸發invoke方法// 此方法對被代理角色的功能做增強// method : 代理對象調用功能就會觸發invoke方法 , invoke方法中的method代表的就是調用的方法對象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("liveShow")) {// 代理角色對象調用liveShow方法 , 此位置會攔截// 前增強System.out.println("宋喆幫寶強拉了一個真人秀的活動,獲取傭金" + (double) args[0] * 0.8 + "元");Object result = method.invoke(baoQiang, (double) args[0] * 0.2);// 后增強System.out.println("宋喆幫寶強把賺的錢存了起來...");return result;} else if (method.getName().equals("sleep")) {// 代理角色對象調用sleep方法 , 此位置會攔截method.invoke(baoQiang);} else {// 除了liveShow和sleep方法 , 會執行else代碼塊中的內容}return null;}
}// 1 抽象角色
interface Star {double liveShow(double money);void sleep();
}// 2 定義被代理角色(寶強)
class BaoQiang implements Star {@Overridepublic double liveShow(double money) {System.out.println("寶強參加了一個真人秀活動賺了" + money + "錢");return money;}@Overridepublic void sleep() {System.out.println("寶強累了 , 睡覺...");}
}
動態代理調用流程:
小結
-
什么是動態代理?
在代碼執行前,沒有代理類,代理類是在程序運行的時候動態生成.Proxy.newProxyInstance
-
動態代理有什么好處?
動態代理可以為 “被代理對象” 的所有接口的所有方法做代理,動態代理可以在不改變方法源碼的情況下,實現對方法功能的增強。 -
動態代理相關的API有哪些?
Proxypublic static Object newProxyInstance(類加載器,接口列表,調用處理器)類加載器 = 被代理對象.getClass().getClassLoader();接口列表 = 被代理對象.getClass().getInterfaces();調用處理器 = new InvocationHandler(){ 實現 invoke 方法 };InvocationHandlerpublic Object invoke(代理對象,方法對象,方法的實參類別) 該方法執行時機是,代理對象調用方法時觸發執行
-
動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。
-
缺點 :只能針對接口的實現類做代理對象,普通類是不能做代理對象的。
后面框架學習的時候會接觸到CGLib(Code Genneration Library ): 可以實現對類的代理。
數據庫連接池與JDBC Template
數據庫連接池
1. 概念:其實就是一個容器(集合),存放數據庫連接的容器。當系統初始化好后,容器被創建,容器中會申請一些連接對象,當用戶來訪問數據庫時,從容器中獲取連接對象,用戶訪問完之后,會將連接對象歸還給容器。2. 好處:1. 節約資源2. 用戶訪問高效3. 實現:1. 標準接口:DataSource javax.sql包下的1. 方法:* 獲取連接:getConnection()* 歸還連接:Connection.close()。如果連接對象Connection是從連接池中獲取的,那么調用Connection.close()方法,則不會再關閉連接了。而是歸還連接2. 一般我們不去實現它,有數據庫廠商來實現1. C3P0:數據庫連接池技術2. Druid:數據庫連接池實現技術,由阿里巴巴提供的4. C3P0:數據庫連接池技術* 步驟:1. 導入jar包 (兩個) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar ,* 不要忘記導入數據庫驅動jar包2. 定義配置文件:* 名稱: c3p0.properties 或者 c3p0-config.xml* 路徑:直接將文件放在src目錄下即可。3. 創建核心對象 數據庫連接池對象 ComboPooledDataSource4. 獲取連接: getConnection* 代碼://1.創建數據庫連接池對象DataSource ds = new ComboPooledDataSource();//2. 獲取連接對象Connection conn = ds.getConnection();
5. Druid:數據庫連接池實現技術,由阿里巴巴提供的1. 步驟:1. 導入jar包 druid-1.0.9.jar2. 定義配置文件:* 是properties形式的* 可以叫任意名稱,可以放在任意目錄下3. 加載配置文件。Properties4. 獲取數據庫連接池對象:通過工廠來來獲取 DruidDataSourceFactory5. 獲取連接:getConnection* 代碼://3.加載配置文件Properties pro = new Properties();InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");pro.load(is);//4.獲取連接池對象DataSource ds = DruidDataSourceFactory.createDataSource(pro);//5.獲取連接Connection conn = ds.getConnection();2. 定義工具類1. 定義一個類 JDBCUtils2. 提供靜態代碼塊加載配置文件,初始化連接池對象3. 提供方法1. 獲取連接方法:通過數據庫連接池獲取連接2. 釋放資源3. 獲取連接池的方法* 代碼:public class JDBCUtils {//1.定義成員變量 DataSourceprivate static DataSource ds ;static{try {//1.加載配置文件Properties pro = new Properties();pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));//2.獲取DataSourceds = DruidDataSourceFactory.createDataSource(pro);} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}/*** 獲取連接*/public static Connection getConnection() throws SQLException {return ds.getConnection();}/*** 釋放資源*/public static void close(Statement stmt,Connection conn){/* if(stmt != null){try {stmt.close();} catch (SQLException e) {e.printStackTrace();}}if(conn != null){try {conn.close();//歸還連接} catch (SQLException e) {e.printStackTrace();}}*/close(null,stmt,conn);}
?
public static void close(ResultSet rs , Statement stmt, Connection conn){
?
if(rs != null){try {rs.close();} catch (SQLException e) {e.printStackTrace();}}
?
if(stmt != null){try {stmt.close();} catch (SQLException e) {e.printStackTrace();}}if(conn != null){try {conn.close();//歸還連接} catch (SQLException e) {e.printStackTrace();}}}/*** 獲取連接池方法*/public static DataSource getDataSource(){return ds;}}
Spring JDBC
* Spring框架對JDBC的簡單封裝。提供了一個JDBCTemplate對象簡化JDBC的開發
* 步驟:1. 導入jar包2. 創建JdbcTemplate對象。依賴于數據源DataSource* JdbcTemplate template = new JdbcTemplate(ds);3. 調用JdbcTemplate的方法來完成CRUD的操作* update():執行DML語句。增、刪、改語句* queryForMap():查詢結果將結果集封裝為map集合,將列名作為key,將值作為value 將這條記錄封裝為一個map集合* 注意:這個方法查詢的結果集長度只能是1* queryForList():查詢結果將結果集封裝為list集合* 注意:將每一條記錄封裝為一個Map集合,再將Map集合裝載到List集合中* query():查詢結果,將結果封裝為JavaBean對象* query的參數:RowMapper* 一般我們使用BeanPropertyRowMapper實現類。可以完成數據到JavaBean的自動封裝* new BeanPropertyRowMapper<類型>(類型.class)* queryForObject:查詢結果,將結果封裝為對象* 一般用于聚合函數的查詢4. 練習:* 需求:1. 修改1號數據的 salary 為 100002. 添加一條記錄3. 刪除剛才添加的記錄4. 查詢id為1的記錄,將其封裝為Map集合5. 查詢所有記錄,將其封裝為List6. 查詢所有記錄,將其封裝為Emp對象的List集合7. 查詢總記錄數* 代碼:import cn.itcast.domain.Emp;import cn.itcast.utils.JDBCUtils;import org.junit.Test;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import java.sql.Date;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;import java.util.Map;public class JdbcTemplateDemo2 {//Junit單元測試,可以讓方法獨立執行
?
//1. 獲取JDBCTemplate對象private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());/*** 1. 修改1號數據的 salary 為 10000*/@Testpublic void test1(){//2. 定義sqlString sql = "update emp set salary = 10000 where id = 1001";//3. 執行sqlint count = template.update(sql);System.out.println(count);}/*** 2. 添加一條記錄*/@Testpublic void test2(){String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";int count = template.update(sql, 1015, "郭靖", 10);System.out.println(count);}/*** 3.刪除剛才添加的記錄*/@Testpublic void test3(){String sql = "delete from emp where id = ?";int count = template.update(sql, 1015);System.out.println(count);}/*** 4.查詢id為1001的記錄,將其封裝為Map集合* 注意:這個方法查詢的結果集長度只能是1*/@Testpublic void test4(){String sql = "select * from emp where id = ? or id = ?";Map<String, Object> map = template.queryForMap(sql, 1001,1002);System.out.println(map);//{id=1001, ename=孫悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}}/*** 5. 查詢所有記錄,將其封裝為List*/@Testpublic void test5(){String sql = "select * from emp";List<Map<String, Object>> list = template.queryForList(sql);for (Map<String, Object> stringObjectMap : list) {System.out.println(stringObjectMap);}}/*** 6. 查詢所有記錄,將其封裝為Emp對象的List集合*/@Testpublic void test6(){String sql = "select * from emp";List<Emp> list = template.query(sql, new RowMapper<Emp>() {@Overridepublic Emp mapRow(ResultSet rs, int i) throws SQLException {Emp emp = new Emp();int id = rs.getInt("id");String ename = rs.getString("ename");int job_id = rs.getInt("job_id");int mgr = rs.getInt("mgr");Date joindate = rs.getDate("joindate");double salary = rs.getDouble("salary");double bonus = rs.getDouble("bonus");int dept_id = rs.getInt("dept_id");emp.setId(id);emp.setEname(ename);emp.setJob_id(job_id);emp.setMgr(mgr);emp.setJoindate(joindate);emp.setSalary(salary);emp.setBonus(bonus);emp.setDept_id(dept_id);return emp;}});
?
for (Emp emp : list) {System.out.println(emp);}}/*** 6. 查詢所有記錄,將其封裝為Emp對象的List集合*/@Testpublic void test6_2(){String sql = "select * from emp";List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));for (Emp emp : list) {System.out.println(emp);}}/*** 7. 查詢總記錄數*/@Testpublic void test7(){String sql = "select count(id) from emp";Long total = template.queryForObject(sql, Long.class);System.out.println(total);}}