1.代理模式概述
学习内容
1)概述
为什么要有 "代理" ?
- 生活中就有很多例子,比如委托业务,黄牛(票贩子)等等
- 代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,这才是"代理"存在的原因。
- 例如要租房子,房产中介可以在我们住房前代理我们找房子。中介就是代理,而自己就是被代理了。
在代码设计中,代理模式作用主要就是让 "被代理对象 " 的某个方法执行之前 或者执行之后加入其他增强逻辑。
test
前增强 : 例如获取当前时间
被代理对象调用方法
后增强 : 例如获取当前时间
计算方法执行的时间
2)代理的前提条件 : 掌握 !
- 抽象角色 :声明功能
- 代理角色 :实现抽象功能 , 完成代理逻辑
- 被代理角色 :实现抽象功能
意味着被代理角色和代理角色有着共同的父类型(既抽象角色) , 例如我要租房子, 我只能找房产中介, 不能找票贩子
-
代理模式存在两种实现方式:
- 静态代理
- 动态代理
知识小结
-
请说出代码中代理模式的作用?
代理角色对 被代理就角色某个方法执行的前或者后进行 功能增强
-
请说出代理模式中的三个角色?
抽象角色 代理角色 被代理角色
==============================================================================================================
1.1 静态代理
学习目标
- 能够写出静态代理模式代码
内容讲解
-
静态代理是由程序员创建 或 工具生成代理类的源码,再编译代理类。
在程序运行前就已经存在代理类的字节码文件,代理类和被代理类的关系在运行前就确定了。
简单理解 : 在程序运行之前 , 代理类就存在了,这就是静态代理 ; 动态代理是程序运行时动态生成代理类
-
静态代理实现的步骤 :
- 存在一个抽象角色
- 定义被代理角色
- 定义代理,增强被代理角色的功能
案例实践 :
- 以现实中经纪人代理明星
已知存在接口:
java
// 1.抽象角色
interface Star {
// 真人秀方法
double liveShow(double money);
void sleep();
}
定义被代理类:
- 定义王宝强类,实现Star方法
java
// - 定义被代理角色(宝强)
class BaoQiang implements Star {
@Override
public double liveShow(double money) {
System.out.println("宝强参加了一个真人秀活动赚了" + money + "钱");
return money;
}
@Override
public void sleep() {
System.out.println("宝强累了 , 睡觉...");
}
}
定义代理类:
- 定义宋喆经纪人类
java
// - 定义代理角色(宋喆),增强被代理角色的功能
class SongZhe implements Star {
private BaoQiang baoQiang;
public SongZhe(BaoQiang baoQiang) {
this.baoQiang = baoQiang;
}
@Override
public double liveShow(double money) {
// 前增强
System.out.println("宋喆帮宝强拉了一个真人秀的活动,获取佣金" + money * 0.8 + "元");
// 被代理角色的功能
double result = baoQiang.liveShow(money * 0.2);
// 后增强
System.out.println("宋喆帮宝强把赚的钱存了起来...");
return result;
}
@Override
public void sleep() {
// 前增强
System.out.println("宋喆帮宝强定了一家五星级大酒店");
baoQiang.sleep();
// 后增强
System.out.println("宋喆帮宝强退房...");
}
}
定义测试类进行测试
java
/*
静态代理实现的步骤 :
- 存在一个抽象角色
- 定义被代理角色(宝强)
- 定义代理角色(宋喆),增强被代理角色的功能
*/
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)。
java
public static Object newProxyInstance (
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h ) 获取代理对象的方法
- 返回值:该方法返回就是动态生成的代理对象
- 参数列表说明:
1. ClassLoader loader - 定义代理类的类加载器
2. Class<?>[] interfaces - 代理类要实现的接口列表,要求与被代理类的接口一样。
3. InvocationHandler h - 就是具体实现代理逻辑的接口
InvocationHandler接口
源码 :
java
interface InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args); //代理逻辑
}
java.lang.reflect.InvocationHandler
是代理对象的实际处理代理逻辑的接口,具体代理实现逻辑在其 invoke 方法中。所有代理对象调用的方法,执行是都会经过invoke。因此如果要对某个方法进行代理增强,就可以在这个invoke方法中进行定义。
方法说明如下:
java
public Object invoke(Object proxy, Method method, Object[] args);
1. 返回值:方法被代理后执行的结果。
2. 参数列表:
1. proxy - 就是代理对象
2. method - 代理对象调用的方法
3. args - 代理对象调用方法传入参数值的对象数组.
3)代码实践
将经纪人代理明星的案例使用动态代理实现
- 把父接口定义
- 定义被代理类:宝强
- 动态生成代理类
- 定义代理逻辑
java
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代表的就是调用的方法对象
@Override
public 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 {
@Override
public double liveShow(double money) {
System.out.println("宝强参加了一个真人秀活动赚了" + money + "钱");
return money;
}
@Override
public void sleep() {
System.out.println("宝强累了 , 睡觉...");
}
}
动态代理调用流程:
小结
-
什么是动态代理?
在代码执行前,没有代理类,代理类是在程序运行的时候动态生成.
Proxy.newProxyInstance
-
动态代理有什么好处?
动态代理可以为 "被代理对象" 的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强。
-
动态代理相关的API有哪些?
markdownProxy public static Object newProxyInstance(类加载器,接口列表,调用处理器) 类加载器 = 被代理对象.getClass().getClassLoader(); 接口列表 = 被代理对象.getClass().getInterfaces(); 调用处理器 = new InvocationHandler(){ 实现 invoke 方法 }; InvocationHandler public 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. 创建核心对象 数据库连接池对象 ComboPooledDataSource
4. 获取连接: getConnection
* 代码:
//1.创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
//2. 获取连接对象
Connection conn = ds.getConnection();
5. Druid:数据库连接池实现技术,由阿里巴巴提供的
1. 步骤:
1. 导入jar包 druid-1.0.9.jar
2. 定义配置文件:
* 是properties形式的
* 可以叫任意名称,可以放在任意目录下
3. 加载配置文件。Properties
4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory
5. 获取连接: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. 定义一个类 JDBCUtils
2. 提供静态代码块加载配置文件,初始化连接池对象
3. 提供方法
1. 获取连接方法:通过数据库连接池获取连接
2. 释放资源
3. 获取连接池的方法
* 代码:
public class JDBCUtils {
//1.定义成员变量 DataSource
private static DataSource ds ;
static{
try {
//1.加载配置文件
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//2.获取DataSource
ds = 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 为 10000
2. 添加一条记录
3. 删除刚才添加的记录
4. 查询id为1的记录,将其封装为Map集合
5. 查询所有记录,将其封装为List
6. 查询所有记录,将其封装为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
*/
@Test
public void test1(){
//2. 定义sql
String sql = "update emp set salary = 10000 where id = 1001";
//3. 执行sql
int count = template.update(sql);
System.out.println(count);
}
/**
* 2. 添加一条记录
*/
@Test
public void test2(){
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
int count = template.update(sql, 1015, "郭靖", 10);
System.out.println(count);
}
/**
* 3.删除刚才添加的记录
*/
@Test
public void test3(){
String sql = "delete from emp where id = ?";
int count = template.update(sql, 1015);
System.out.println(count);
}
/**
* 4.查询id为1001的记录,将其封装为Map集合
* 注意:这个方法查询的结果集长度只能是1
*/
@Test
public 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
*/
@Test
public 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集合
*/
@Test
public void test6(){
String sql = "select * from emp";
List<Emp> list = template.query(sql, new RowMapper<Emp>() {
@Override
public 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集合
*/
@Test
public 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. 查询总记录数
*/
@Test
public void test7(){
String sql = "select count(id) from emp";
Long total = template.queryForObject(sql, Long.class);
System.out.println(total);
}
}