代理模式
为另一个对象提供一个替身或占位符以控制对这个对象的访问。
需使用 RMI (Remote Method Invocation) 远程方法调用,需简单了解。
RMI 注意:
- 启动远程服务前先启动 rmiregistry(使用 Naming.rebind() 注册服务,rmiregister 必须运行)。
- 变量和返回值的泪水称为可序列化的类型(编译期无法发现)。
- 给客户提供 stub 类
RMI 测试
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @author NNroc
* @date 2020/8/18 12:36
*/
public interface MyRemote extends Remote {
public String sayHello() throws RemoteException;
}
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
/**
* @author NNroc
* @date 2020/8/18 12:37
*/
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
public String sayHello() {
return "Server says, 'Hey'";
}
public MyRemoteImpl() throws RemoteException {
}
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
MyRemote service = new MyRemoteImpl();
Naming.rebind("rmi://localhost:1099/RemoteHello", service);
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.rmi.Naming;
/**
* @author NNroc
* @date 2020/8/18 12:42
*/
public class MyRemoteClient {
public static void main(String[] args) {
new MyRemoteClient().go();
}
public void go() {
try {
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1:1099/RemoteHello");
String s = service.sayHello();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
}
远程代理
远程代理控制访问远程对象。
可以作为另一个 JVM 上的对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。
卖糖果机器服务端示例
CEO 想要获取远端每个糖果机的情况。
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
其中 5 种方式(soldOutState、noQuarterState、hasQuarterState、soldState、winnerState)请参考https://blog.csdn.net/weixin_43820352/article/details/108053317
需将其中类前两行中GumballMachine gumballMachine;
改为transient GumballMachine gumballMachine;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* @author NNroc
* @date 2020/8/18 11:51
*/
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
State state = soldOutState;
int count = 0;
String location;
public GumballMachine(String location, int numberGumballs) throws RemoteException {//设计一个构造方法来抛出异常,这是因为UnicastRemoteObject 就抛出异常,超类抛出异常时,子类也只得抛出同样的异常。
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
winnerState = new WinnerState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
this.location = location;
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
public void refill(int count) {
this.count = count;
state = noQuarterState;
}
public int getCount() {
return count;
}
public State getState() {
return state;
}
public String getLocation() {
return location;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public State getWinnerState() {
return winnerState;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("/nMighty Gumball, Inc.");
result.append("/nJava-enabled Standing Gumball Model #2004");
result.append("/nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("/n");
result.append("Machine is " + state + "/n");
return result.toString();
}
}
测试
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
/**
* @author NNroc
* @date 2020/8/18 14:49
*/
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachineRemote gumballMachine = null;
int count;
// if (args.length < 2) {
// System.out.println("GumballMachine ");
// System.exit(1);
// }
try {
// 本地注册
LocateRegistry.createRegistry(1099);
count = 100;
gumballMachine = new GumballMachine("1099", count);
// count = Integer.parseInt(args[1]);
// gumballMachine = new GumballMachine(args[0], count);
Naming.rebind("rmi://localhost:" + "1099" + "/gumballmachine", gumballMachine);
} catch (Exception e) {
e.printStackTrace();
}
}
}
虚拟代理
虚拟代理控制访问创建开销大的资源。
作为创建开销大的对象的代表。当我们真正需要创建一个对象时才创建它,当对象在创建前和创建中时,虚拟代理来扮演对象的替身,对象创建后,代理会将请求直接委托给对象。
加载图片示例
ImageProxy 工作过程:
- ImageProxy 首先创建一个 ImageIcon,然后开始从网络 URL 上加载图片。
- 在加载过程中,ImageProxy 显示“CD封面加载中,请稍候······”
- 当图像加载完毕,ImageProxy 把所有方法调用委托给真正的 ImageIcon,这些方法包括了 paintIcon()、getWidth() 和 getHeight()。
- 如果用户请求新的图像,我们就创建新的代理,重复过程。
import java.awt.*;
import javax.swing.*;
/**
* @author NNroc
* @date 2020/8/18 15:38
*/
public class ImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
private Icon icon;
public ImageComponent(Icon icon) {
this.icon = icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w) / 2;
int y = (600 - h) / 2;
icon.paintIcon(this, g, x, y);
}
}
import java.awt.*;
import java.net.URL;
import javax.swing.*;
/**
* @author NNroc
* @date 2020/8/18 15:34
*/
public class ImageProxy implements Icon {
// imageIcon是我们希望加载后显示出来的真正图象
volatile ImageIcon imageIcon;
final URL imageURL;
Thread retrievalThread;
// 是否已经下载完毕
boolean retrieving = false;
// url图像的位置
public ImageProxy(URL url) {
imageURL = url;
}
// 图象加载完毕前,返回默认的宽度,否则返回真实的宽度
public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}
// 图象加载完毕前,返回默认的高度,否则返回真实的高度
public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}
// 绘制图象的方法
public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
// 如果图象加载完毕,则通知图象绘制自己
imageIcon.paintIcon(c, g, x, y);
} else {
// 无图像则显示Loading
g.drawString("Loading CD cover, please wait...", x + 300, y + 190);
if (!retrieving) {
retrieving = true;
retrievalThread = new Thread(new Runnable() {
public void run() {
try {
setImageIcon(new ImageIcon(imageURL, "CD Cover"));
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
synchronized void setImageIcon(ImageIcon imageIcon) {
this.imageIcon = imageIcon;
}
}
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.swing.*;
/**
* @author NNroc
* @date 2020/8/18 15:39
*/
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame = new JFrame("CD Cover Viewer");
JMenuBar menuBar;
JMenu menu;
Hashtable<String, String> cds = new Hashtable<String, String>();
public static void main(String[] args) throws Exception {
ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
}
public ImageProxyTestDrive() throws Exception {
cds.put("Buddha Bar", "http://images.amazon.com/images/P/B00009XBYK.01.LZZZZZZZ.jpg");
cds.put("Ima", "http://images.amazon.com/images/P/B000005IRM.01.LZZZZZZZ.jpg");
cds.put("Karma", "http://images.amazon.com/images/P/B000005DCB.01.LZZZZZZZ.gif");
cds.put("MCMXC A.D.", "http://images.amazon.com/images/P/B000002URV.01.LZZZZZZZ.jpg");
cds.put("Northern Exposure", "http://images.amazon.com/images/P/B000003SFN.01.LZZZZZZZ.jpg");
cds.put("Selected Ambient Works, Vol. 2", "http://images.amazon.com/images/P/B000002MNZ.01.LZZZZZZZ.jpg");
URL initialURL = new URL((String) cds.get("Selected Ambient Works, Vol. 2"));
menuBar = new JMenuBar();
menu = new JMenu("Favorite CDs");
menuBar.add(menu);
frame.setJMenuBar(menuBar);
for (Enumeration<String> e = cds.keys(); e.hasMoreElements(); ) {
String name = (String) e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(event -> {
imageComponent.setIcon(new ImageProxy(getCDUrl(event.getActionCommand())));
frame.repaint();
});
}
// set up frame and menus
Icon icon = new ImageProxy(initialURL);
imageComponent = new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setVisible(true);
}
URL getCDUrl(String name) {
try {
return new URL((String) cds.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
保护代理
保护代理基于权限控制对资源的访问。
个人信息示例
要防止随意修改别人的信息
/**
* 用户信息接口
*
* @author NNroc
* @date 2020/8/18 15:46
*/
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 rating);
}
/**
* @author NNroc
* @date 2020/8/18 15:47
*/
public class PersonBeanImpl implements PersonBean {
String name;
String gender;
String interests;
//总评分
int rating;
//被评价次数
int ratingCount = 0;
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getInterests() {
return interests;
}
//获得平均评分
public int getHotOrNotRating() {
if (ratingCount == 0) return 0;
return (rating / ratingCount);
}
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setInterests(String interests) {
this.interests = interests;
}
//更新总评分和被评价次数
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 本类和OwnerInvocationHandler同理
*
* @author NNroc
* @date 2020/8/18 17:48
*/
public class NonOwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
try {
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 IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author NNroc
* @date 2020/8/18 15:55
*/
public class OwnerInvocationHandler implements InvocationHandler {
//持有RealSubject的引用(这样底层就可以调用RealSubject的方法)
PersonBean person;
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}
//proxy的任何代码被调用,就会导致invoke方法被调用
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
//不允许被评价者调用setHotOrNotRating方法,会抛出异常
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
//捕捉真正主题抛出的异常
e.printStackTrace();
}
//如果调用的是其他的方法,这里不予理会
return null;
}
}
import java.lang.reflect.Proxy;
import java.util.HashMap;
/**
* @author NNroc
* @date 2020/8/18 17:50
*/
public class MatchMakingTestDrive {
// 装载PersonBean的容器
HashMap<String, PersonBean> datingDB = new HashMap<String, PersonBean>();
public static void main(String[] args) {
MatchMakingTestDrive test = new MatchMakingTestDrive();
test.drive();
}
public MatchMakingTestDrive() {
//初始化datingDB
initializeDatabase();
}
public void drive() {
//取得PersonBean
PersonBean joe = getPersonFromDatabase("Joe Javabean");
//创建出PersonBean的"被评分者代理"对象
PersonBean ownerProxy = getOwnerProxy(joe);
System.out.println("Name is " + ownerProxy.getName());
//被评分者可以设置“名称”、“性别”、“爱好”三个信息
ownerProxy.setInterests("bowling, Go");
System.out.println("Interests set from owner proxy");
try {
//被评分者不能设置HotOrNotRating值
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
System.out.println("Can't set rating from owner proxy");
}
System.out.println("Rating is " + ownerProxy.getHotOrNotRating());
//创建出PersonBean的"评分者代理"对象
PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
System.out.println("Name is " + nonOwnerProxy.getName());
try {
//评分者不可以设置“名称”、“性别”、“爱好”三个信息
nonOwnerProxy.setInterests("bowling, Go");
} catch (Exception e) {
System.out.println("Can't set interests from non owner proxy");
}
//评分者可以设置HotOrNotRating值
nonOwnerProxy.setHotOrNotRating(3);
System.out.println("Rating set from non owner proxy");
System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());
}
//返回"被评分者的代理"对象
PersonBean getOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}
//返回"评分者的代理"对象
PersonBean getNonOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}
//根据名称从容器中获得PersonBean
PersonBean getPersonFromDatabase(String name) {
return (PersonBean)datingDB.get(name);
}
void initializeDatabase() {
PersonBean joe = new PersonBeanImpl();
joe.setName("Joe Javabean");
joe.setInterests("cars, computers, music");
joe.setHotOrNotRating(7);
datingDB.put(joe.getName(), joe);
PersonBean kelly = new PersonBeanImpl();
kelly.setName("Kelly Klosure");
kelly.setInterests("ebay, movies, music");
kelly.setHotOrNotRating(6);
datingDB.put(kelly.getName(), kelly);
}
}
代理模式与装饰者模式
代理模式目的:控制被代理者的访问。
装饰者模式目的:为被装饰者加上新的行为。
代表对象而不是装饰对象,作为真正主题的替身,可以保护对象避免不想要的访问,也可以避免在加载大对象的过程中GUI会挂起,或者隐藏主题在远程运行的事实。