1 初识代理模式:
定义:为其他对象提供一种代理以控制对这个对象的访问
结构:
参考实现:
2 体会代理模式:
场景问题: 查询某部门下员工基本信息,如果想看详细,则点击查看
不使用模式下的解决方案:
select person.* , dept.* from person, dept where person.deptid = dept.id and dept.id = ?
使用模式的解决方案:
1+N次查询:
select person.name, person.id from person, dept where person.deptid = dept.id and dept.id = ?
将最常用的字段 查询出来,放在代理对象中,
代理对象引用真实对象,将代理对象中需要查看的真实对象的 getXX setXX 通过再次查询数据库得到后放在代理对象中 eg: 查询 sex age location等
这就是1+N次查询,N表示有多少个bean对象,就有可能点击每一个实体对象查看详细,这时就需要重新连接数据库查询N次,不过这是个概率问题,不可能得到N次这么多次, 这是明显是花销多时间,少空间的做法,所有的字段只有在需要的时候才会被加载,第一次加载的都是最少的字段;(主要为了节约内存考虑,hibernate的 lazy load也是用这种思路)
参考代码如下: 如下代理是 虚代理(虚拟真实对象)
package cn.javass.dp.proxy.example3;
/**
* 定义用户数据对象的接口
*/
public interface UserModelApi {
public String getUserId();
public void setUserId(String userId);
public String getName();
public void setName(String name);
public String getDepId();
public void setDepId(String depId);
public String getSex();
public void setSex(String sex);
}
package cn.javass.dp.proxy.example3;
/**
* 描述用户数据的对象
*/
public class UserModel implements UserModelApi{
/**
* 用户编号
*/
private String userId;
/**
* 用户姓名
*/
private String name;
/**
* 部门编号 代理时才加载
*/
private String depId;
/**
* 性别 代理时才加载
*/
private String sex;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepId() {
return depId;
}
public void setDepId(String depId) {
this.depId = depId;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString(){
return "userId="+userId+",name="+name+",depId="+depId+",sex="+sex+"\n";
}
}
package cn.javass.dp.proxy.example3;
import java.sql.*;
import java.util.*;
/**
* 代理对象,代理用户数据对象
=================主要是看代理对象引用真实对象 和 代理对象中 relaod()写法
*/
public class Proxy implements UserModelApi{
/**
* 持有被代理的具体的目标对象
*/
private UserModel realSubject=null;
/**
* 构造方法,传入被代理的具体的目标对象
* @param realSubject 被代理的具体的目标对象
*/
public Proxy(UserModel realSubject){
this.realSubject = realSubject;
}
/**
* 标示是否已经重新装载过数据了 针对字段 depId 和 sex而言
*/
private boolean loaded = false;
public String getUserId() {
return realSubject.getUserId();
}
public void setUserId(String userId) {
realSubject.setUserId(userId);
}
public String getName() {
return realSubject.getName();
}
public void setName(String name) {
realSubject.setName(name);
}
public void setDepId(String depId) {
realSubject.setDepId(depId);
}
public void setSex(String sex) {
realSubject.setSex(sex);
}
public String getDepId() {
//需要判断是否已经装载过了
if(!this.loaded){
//从数据库中重新装载
reload();
//设置重新装载的标志为true
this.loaded = true;
}
return realSubject.getDepId();
}
public String getSex() {
if(!this.loaded){
reload();
this.loaded = true;
}
return realSubject.getSex();
}
/**
* 重新查询数据库以获取完整的用户数据
*/
private void reload(){
System.out.println("重新查询数据库获取完整的用户数据,userId=="+realSubject.getUserId());
Connection conn = null;
try{
conn = this.getConnection();
String sql = "select * from tbl_user where userId=? ";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, realSubject.getUserId());
ResultSet rs = pstmt.executeQuery();
if(rs.next()){
//只需要重新获取除了userId和name外的数据
realSubject.setDepId(rs.getString("depId"));
realSubject.setSex(rs.getString("sex"));
}
rs.close();
pstmt.close();
}catch(Exception err){
err.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public String toString(){
return "userId="+getUserId()+",name="+getName()
+",depId="+getDepId()+",sex="+getSex()+"\n";
}
private Connection getConnection() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
return DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
}
}
public class UserManager {
/**
* 根据部门编号来获取该部门下的所有人员
* @param depId 部门编号
* @return 该部门下的所有人员
*/
public Collection<UserModelApi> getUserByDepId(String depId)throws Exception{
Collection<UserModelApi> col = new ArrayList<UserModelApi>();
Connection conn = null;
try{
conn = this.getConnection();
//只需要查询userId和name两个值就可以了
String sql = "select u.userId,u.name "
+"from tbl_user u,tbl_dep d "
+"where u.depId=d.depId and d.depId like ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, depId+"%");
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
//这里是创建的代理对象,而不是直接创建UserModel的对象
Proxy proxy = new Proxy(new UserModel());
//只是设置userId和name两个值就可以了
proxy.setUserId(rs.getString("userId"));
proxy.setName(rs.getString("name"));
col.add(proxy);
}
rs.close();
pstmt.close();
}finally{
conn.close();
}
return col;
}
/**
* 获取与数据库的连接
* @return 数据库连接
*/
private Connection getConnection() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
return DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
}
}
public class Client {
public static void main(String[] args) throws Exception{
UserManager userManager = new UserManager();
Collection<UserModelApi> col = userManager.getUserByDepId("0101");
//如果只是显示用户名称,那么不需要重新查询数据库 有效减少查询DB需要的内存 时间 典型的以时间换空间的做法
for(UserModelApi umApi : col){
System.out.println("用户编号:="+umApi.getUserId()+",用户姓名:="+umApi.getName());
}
//如果访问非用户编号和用户姓名外的属性,那就会重新查询数据库, 此时使用代理的包装方法来获取 其余属性
for(UserModelApi umApi : col){
System.out.println("用户编号:="+umApi.getUserId()+",用户姓名:="+umApi.getName()+",所属部门:="+umApi.getDepId());
}
}
}
3 理解代理模式:
认识代理模式: 客户端操作代理对象,代理操作真实对象, 代理对象夹在客户端和真实对象之间,相当于一个中转,我们可以再中转过程中,增加一些自定义功能,eg: 判断权限,只有权限足够下才可以调用真实对象,否则跳转登陆
代理对象和真实对象之间必须是一一对象的关系吗?
这种一一对应关系经常在虚代理下才有;
代理模式顺序图:
Java中的静态代理:(即自己手写接口实现类)
前面自己实现的代理模式,成为Java静态模式,特点就是 有共同接口,代理类和目标类实现这个接口,
缺点就是如果接口发生改变,那么代理类和目标都要变化不灵活。
Java中的动态代理:(交给Javase.reflect.invocationhandler来动态实现 取代手写方式)
依靠反射机制+动态生成类 = 生成动态代理对象 同样也是实现接口上的很多方法,
但是特点是:动态代理类只有一个方法invoke,即使接口方法变更时,动态代理也不需要变更(用一个高层抽象方法将变化给屏蔽掉)
Javaee动态代理使用案例: spring aop 面向切面编程
Java动态代理代码:
接口:
package cn.javass.dp.proxy.example5;
/**
* 订单对象的接口定义
*/
public interface OrderApi {
/**
* 获取订单订购的产品名称
* @return 订单订购的产品名称
*/
public String getProductName();
/**
* 设置订单订购的产品名称
* @param productName 订单订购的产品名称
* @param user 操作人员
*/
public void setProductName(String productName,String user);
/**
* 获取订单订购的数量
* @return 订单订购的数量
*/
public int getOrderNum();
/**
* 设置订单订购的数量
* @param orderNum 订单订购的数量
* @param user 操作人员
*/
public void setOrderNum(int orderNum,String user);
/**
* 获取创建订单的人员
* @return 创建订单的人员
*/
public String getOrderUser();
/**
* 设置创建订单的人员
* @param orderUser 创建订单的人员
* @param user 操作人员
*/
public void setOrderUser(String orderUser,String user);
}
目标类:
package cn.javass.dp.proxy.example5;
/**
* 订单对象
*/
public class Order implements OrderApi{
/**
* 订单订购的产品名称
*/
private String productName;
/**
* 订单订购的数量
*/
private int orderNum;
/**
* 创建订单的人员
*/
private String orderUser;
/**
* 构造方法,传入构建需要的数据
* @param productName 订单订购的产品名称
* @param orderNum 订单订购的数量
* @param orderUser 创建订单的人员
*/
public Order(String productName,int orderNum,String orderUser){
this.productName = productName;
this.orderNum = orderNum;
this.orderUser = orderUser;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName,String user) {
this.productName = productName;
}
public int getOrderNum() {
return orderNum;
}
public void setOrderNum(int orderNum,String user) {
System.out.println("调用到真正方法...");
this.orderNum = orderNum;
}
public String getOrderUser() {
return orderUser;
}
public void setOrderUser(String orderUser,String user) {
this.orderUser = orderUser;
}
public String toString(){
return "productName="+this.getProductName()+",orderNum="+this.getOrderNum()+",orderUser="+this.getOrderUser();
}
}
动态代理类: 获取动态代理类对象的方法中 使用形参来接受真正目标对象,以实现目标对象和代理对象的绑定:
package cn.javass.dp.proxy.example5;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 使用Java中的动态代理
*/
public class DynamicProxy implements InvocationHandler{
/**
* 被代理的对象
*/
private OrderApi order = null;
/**
* 获取绑定好 代理和具体目标对象 后的目标对象的接口 (动态获取获取目标对象的代理对象)
* @param order 具体的订单对象,相当于具体目标对象
*/
public OrderApi getProxyInterface(Order order){
this.order = order;
//把真正的订单对象和动态代理关联起来, 即将创建目标对象的动态代理对象 交给 javase.reflect.invocationhandler来做, 取代你手动new出一个类 手写目标的对象的代理类(这叫Java静态代理)
OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
order.getClass().getClassLoader(),// 目标对象
order.getClass().getInterfaces(), // 目标对象所有的接口
this);
return orderApi;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("调用目标对象的所有方法前,动态代理下,都会先走我...");
//下面是针对目标对象不同方法添加逻辑判断
//如果是调用setter方法就需要检查权限
if(method.getName().startsWith("set")){
//如果不是创建人,那就不能修改
if(order.getOrderUser()!=null && order.getOrderUser().equals(args[1])){
//可以操作
return method.invoke(order, args);// 用着一句话 代替所有访问真实对象的方法
}else{
System.out.println("对不起,"+args[1]+",您无权修改本订单中的数据");
}
}else{
//不是调用的setter方法就继续运行
return method.invoke(order, args);
}
return null;
}
}
客户端:
package cn.javass.dp.proxy.example5;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//张三先登录系统创建了一个订单
Order order = new Order("设计模式",100,"张三");
//创建一个动态代理
DynamicProxy dynamicProxy = new DynamicProxy();
//然后把订单和动态代理关联起来
OrderApi orderApi = dynamicProxy.getProxyInterface(order);
orderApi.getOrderNum();
//以下就需要使用被代理过的接口来操作了
//李四想要来修改,那就会报错
orderApi.setOrderNum(123, "李四");
//输出order
System.out.println("李四修改后订单记录没有变化:"+orderApi);
//张三修改就不会有问题
orderApi.setOrderNum(123, "张三");
//再次输出order
System.out.println("张三修改后,订单记录:"+orderApi);
}
}
4 思考代理模式:
本质: 控制对象的访问,能增强目标对象的功能,甚至替换目标对象的功能
何时使用:
a) 需要为一个对象在不同地址空间提供局部代表时,可以使用远程代理
b) 需要按照需要创建开销很大的对象时,可以使用虚代理(eg:加载小图标 大图片 查看用户基本资料 详细资料)
c) 需要控制对原始对象访问时,可以使用保护代理
d) 需要在访问对象的时候,执行一些附加操作时,可以使用智能指引代理
5代理模式脑图: