代理模式:控制对象访问
远程代理
远程代理就是“远程对象的本地代表”。远程对象就是活在不同jvm中的对象(在不同地址空间运行的远程对象)。本地代表是可以由本地方法调用的对象,其行为会转发到远程对象中。
远程方法调用是如何发生的
- 客户对象调用客户辅助对象的doBigThing()方法。
- 客户辅助对象打包调用信息(方法、变量名等),通过网络运给服务辅助对象。
- 服务辅助对象把来自客户辅助对象的信息解包,找出被调用的方法,然后调用真正的服务对象上的真正方法。
- 服务对象上的方法被调用将结果返回服务辅助对象,然后打包通过网络传回客户辅助对象。
- 客户辅助对象解包,返回给客户。
利用RMI制作远程服务
- 制作远程接口
实现Remote标识接口,这是一个规则;所有方法抛出异常;方法的返回值必须是可以被序列化的。
- 制作远程实现
实现远程接口;继承UnicasRemoteObject以具有“远程”功能;然后用RMI Registry注册服务。
3.执行rmic生成_stub
会生成一个class,这个class是客户端需要的
4 注册服务和启动服务
# rmiregistry
# java MyRemoteImpl
5 客户端调用
客户端需要MyRemote.class和MyRemoteImpl_stub.class
import java.net.MalformedURLException;
import java.rmi.*;
public class MyRemoteClient {
public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
new MyRemoteClient().go();
}
public void go() throws MalformedURLException, RemoteException, NotBoundException{
MyRemote service = (MyRemote)Naming.lookup("rmi://192.168.150.131:1099/RemoteHello");
System.out.println(service.sayHello());
}
}
如果报这个异常
java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:
需要
1 在/etc/hosts里添加一行,如果192.168.151.131是现在的ip,则添加
192.168.150.131 test localhost
2 修改/etc/sysconfig/network文件的HOSTNAME=test,则可以访问成功。
定义代理模式
代理模式是为另一个对象提供一个替身或占位符以控制对这个对象的访问。
被代理对象可以是远程对象,创建开销大的对象或需要安全控制的对象。
- 远程代理只是一般代理模式的一种实现。
- 虚拟代理是创建开销大的对象的代表。虚拟代理经常直到真正需要一个对象时才创建它。当对象在创建前和创建中的时候,由虚拟代理扮演该对象的替身,做点别的事。当对象创建完成了代理会把请求直接委托给真实的对象。
- 代理模式和装饰者主要区别,前者主要控制对象的访问,后者给对象添加行为。
保护代理
java.lang.reflect包中有自己的代理支持,可以在运行时动态创建一个代理类,并将方法的调用转发到指定类。称为动态代理。
下边利用java的动态代理创建一个代理实现(保护代理),类图如下:
Proxy是java提供的,不能把逻辑放在Proxy类中,因为Proxy不是你直接实现的。既然这样要把代码逻辑放入InvocationHandler中。InvocationHandler的工作是响应代理的任何调用。可以把InvocationHandler想成是代理收到方法调用后,请求实际工作的对象。
一个例子
现在有一个约会系统,系统的核心是男男女女(PersonBean接口)
package com.headfirst.proxy;
public interface PersonBean {
String getName();
String getGender();//性别
String getInterests();//爱好
int getHotOrNotRating();//评分
void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setHotOrNotRating(int hotOrNotRating);
}
具体的男男女女
package com.headfirst.proxy;
public class PersonBeanImpl implements PersonBean{
String name;
String gender;
String interests;
int rating = 0; //评分
int ratingCount = 0;//评分计算次数
@Override
public String getName() {
return name;
}
@Override
public String getGender() {
return gender;
}
@Override
public String getInterests() {
return interests;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGender(String gender) {
this.gender = gender;
}
@Override
public void setInterests(String interests) {
this.interests = interests;
}
//取得某人的评分的平均值
@Override
public int getHotOrNotRating() {
if(ratingCount == 0) return 0;
return rating/ratingCount;
}
//模拟给人进行评分并记录评分次数
@Override
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}
约会系统有一定的规则:不允许修改别的人基本信息,可以给别人进行评分
那么PersonBean里边的所有set,get方法都是公开的,如何实现这样的需求呢?
这样的话可以使用javaAPI创建两个PersonBean的代理对象:一个是访问自己的PersonBean对象(拥有者),另一个是访问别人的PersonBean对象(非拥有者),代码运行中,不管哪种情况,都可以创建一个合适的代理。
- 创建InvocationHandler
当代理的方法被调用时,代理就会把调用转发到InvocationHandler,不管代理被调用的是何种方法,处理器被调用的一定是那个唯一的方法invoke()方法。所以只需要在invoke方法中写逻辑就可以了。
拥有者
package com.headfirst.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//所有调用处理器都实现InvocationHandler接口
public class OwnerInvocationHandler implements InvocationHandler{
PersonBean person;
//保持对person的引用
public OwnerInvocationHandler(PersonBean person){
this.person = person;
}
/*
* 每次proxy的方法被调用就会导致proxy调用此方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果是拥有者,不能修改自己的评分,其他的操作都可以
if(method.getName().startsWith("get")){
return method.invoke(person, args);
}else if(method.getName().equals("setHotOrNotRating")){
throw new RuntimeException("自己不能修改自己的评分!");
}else if(method.getName().startsWith("set")){
return method.invoke(person, args);
}
return null;
}
}
非拥有者
package com.headfirst.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class NonOwnerInvocationHandler implements InvocationHandler{
PersonBean person;
public NonOwnerInvocationHandler(PersonBean person){
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//非拥有者不能修改别人的个人信息,但是可以给别人进行评分
if(method.getName().startsWith("get")){
return method.invoke(person, args);
}else if(method.getName().equals("setHotOrNotRating")){
return method.invoke(person, args);
}else if(method.getName().startsWith("set")){
throw new RuntimeException("不能修改别人的个人信息,只能给别人评分!");
}
return null;
}
}
- 看看代理如何控制对setter的访问
package com.headfirst.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MainDrive {
//
// //拥有着代理
// //此方法需要一个person作为参数,然后返回它的代理
// PersonBean getOwnerProxy(PersonBean person){
// return (PersonBean)Proxy.newProxyInstance(//该方法创建代理
// person.getClass().getClassLoader(),//personBean的类加载器
// person.getClass().getInterfaces(), //代理需要实现的接口
// new OwnerInvocationHandler(person));//将person传入调用处理器的构造器中,保持一个引用
// }
//
// //非拥有着代理
// PersonBean getNonOwnerProxy(PersonBean person){
// return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(),
// person.getClass().getInterfaces(),
// new NonOwnerInvocationHandler(person));
// }
//该方法是上边两个方法的简化版本
PersonBean getProxy(PersonBean person,InvocationHandler invocationHander){
return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(),
person.getClass().getInterfaces(), invocationHander);
}
public void drive(){
Map<String,PersonBean> personMap = this.initializeDB();//模拟从数据库读取的person列表
PersonBean zhangsan = personMap.get("张三");
//创建张三的拥有者代理
PersonBean ownerProxy = getProxy(zhangsan,new OwnerInvocationHandler(zhangsan));
System.out.println("name is "+ownerProxy.getName());
ownerProxy.setInterests("乒乓球");
System.out.println(zhangsan.getInterests());
try{
ownerProxy.setHotOrNotRating(10); //尝试修改自己的评分
}catch(Exception e){
System.out.println(e.getMessage());
}
PersonBean nonOwnerProxy = getProxy(zhangsan,new NonOwnerInvocationHandler(zhangsan)); //非拥有者代理
System.out.println("name is "+nonOwnerProxy.getName());
try{
nonOwnerProxy.setInterests("乒乓球"); //尝试修改爱好
}catch(Exception e){
System.out.println(e.getMessage());
}
nonOwnerProxy.setHotOrNotRating(3);
System.out.println(zhangsan.getHotOrNotRating());
}
public Map<String,PersonBean> initializeDB(){
PersonBean zhangsan = new PersonBeanImpl();
zhangsan.setName("张三");
zhangsan.setGender("男");
zhangsan.setInterests("篮球,足球");
PersonBean xiaohong = new PersonBeanImpl();
xiaohong.setName("小红");
xiaohong.setGender("女");
xiaohong.setInterests("绘画,唱歌");
return Stream.of(zhangsan,xiaohong).collect(Collectors.toMap(PersonBean :: getName, person -> person));
}
public static void main(String[] args){
new MainDrive().drive();
}
}
- 动态代理之所以称为动态,是因为运行时才将它的类创建出来,代码开始执行时,还没有proxy类。
- InvocationHandler不是proxy,它只是帮助proxy的类,proxy会把调用转发给它处理。Proxy本身是利用静态的Proxy.newProxyInstance()方法运行时动态创建的。