設計模式之代理模式
代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能.舉個栗子,比如你要調用別人的代碼,想計算調用別人方法耗時多久,你不可能說要別人在裏面添加統計耗時,你也不可能說在每個調用別人方法的時候在調用方法前和後計算耗時,這個時候你只要寫個代理類,調用它的方法時調用這個代理類的方法就可以.
URL圖如下
代碼也很簡單 Subject 接口
public interface Subject { String request(); }
代理類Proxy
public class Proxy implements Subject{
private Subject realSubject;
public Proxy(Subject s){
realSubject=s;
}
@Override
public String request() {
System.out.println("代理前操作...");
long start=System.currentTimeMillis();
String requst=realSubject.request();
long end=System.currentTimeMillis();
System.out.println("耗時:"+(end-start)+"ms");
System.out.println("代理後操作...");
return requst;
}
}
被代理的類
public class RealSubject implements Subject{
@Override
public String request() {
System.out.println("RealSubject方法request被調用");
Random random=new Random();
try {
Thread.sleep(random.nextInt(1000));//模擬耗時操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return "這是RealSubject request調用後返回的結果 ";
}
}
測試類
public class HelloWorld {
public static void main(String[] args) {
RealSubject re=new RealSubject();
Proxy pro=new Proxy(re);
System.out.println(pro.request());;
}
}
輸出結果:
代理前操作...
RealSubject方法request被調用
耗時:936ms
代理後操作...
這是RealSubject request調用後返回的結果
這樣做的完全實現了代理的功能,但是帶來的缺點也很明顯,就是要多個代碼類處理了。要是很多類需要代理,那難道要寫很多個代理類出來??
針對這個缺點,動態代理也就隨之而出了
類:
/**
* 創建動態代理對象
* 動態代理不需要實現接口,但是需要指定接口類型
*/
public class ProxyFactory{
//維護一個目標對象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//給目標對象生成代理對象
public Object getProxyInstance(){
Object o=Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
return o;
}
class MyInvocationHandler implements InvocationHandler{
private Object target;
MyInvocationHandler(Object o){
target=o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("代理方法:"+method.getName());
System.out.println("代理前操作...");
long start=System.currentTimeMillis();
Object returnValue=method.invoke(target, args);//執行目標對象方法
long end=System.currentTimeMillis();
System.out.println("耗時:"+(end-start)+"ms");
System.out.println("代理後操作...");
return returnValue;
}
}
}
調用:
RealSubject re=new RealSubject();
ProxyFactory proxyFactory=new ProxyFactory(re);
Subject o=(Subject) proxyFactory.getProxyInstance();
System.out.println(o.request());
輸出:
代理方法:request
代理前操作...
RealSubject方法request被調用
耗時:203ms
代理後操作...
這是RealSubject request調用後返回的結果
結果和前面一個.
既然是代理RealSubject類,那是不是強轉爲RealSubject是不是也行呢?本着好奇,我這樣寫:
RealSubject o=(RealSubject) proxyFactory.getProxyInstance();
結果報異常
java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to RealSubject
於是我又打印代理的類
Object o= proxyFactory.getProxyInstance();
System.out.println(o.toString());
輸出:RealSubject@5c3f1224
看起來好像也是RealSubject類一樣,But 我強制轉換的時候爲啥報錯了呢,難道此RealSubject對象非我自己寫的RealSubject類??
於是乎我打印它裏面的所有方法:
for(Method m:o.getClass().getMethods()){
System.out.print(m.getName()+”,”);
}
equals,toString,hashCode,request,isProxyClass,getInvocationHandler,getProxyClass,newProxyInstance,wait,wait,wait,getClass,notify,notifyAll
裏面多了幾個方法
我又把他們的class name打印出來
System.out.println(o.getClass().getName());
System.out.println(re.getClass().getName());
輸出:
com.sun.proxy.$Proxy0
com.test.RealSubject
原來它的class是這樣子的啊,難怪它產生的對象裏面的方法還多出幾個,爲啥是這樣子的呢,
點進去Proxy.newProxyInstance方法裏面去看
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//根據要代理的接口生成class
Class<?> cl = getProxyClass0(loader, intfs);
try {
//生成構造器,待會利用這個構造器去生成實例
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
//在這個返回一個實現interfaces接口的實例對象
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
try {
return cons.newInstance(new Object[] {h} );
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString());
}
}
}
看完源碼才恍然大悟,原來它是自己生成了class對象,class對象根據傳入參數interfaces接口,實現interfaces的所有方法,這樣要代理哪個接口的方法就傳入哪個接口的方法.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
後來又想,要是有些方法需要計算時間,有些方法不需要那咋操作了.
於是想到了註解,把註解添加進去
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CompteTime {
}
Subject 接口有兩個方法,一個需要計算,一個不需要計算的
public interface Subject {
String request();
String sayHello();
}
RealSubject 這個類就是這樣的.
public class RealSubject implements Subject{
public String say(){
System.out.println("sayHello");
return "sayHello";
}
@Override
public String request() {
System.out.println("RealSubject方法request被調用");
Random random=new Random();
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "這是RealSubject request調用後返回的結果 ";
}
@CompteTime
@Override
public String sayHello() {
System.out.println("sayHello");
return "sayHello";
}
}
ProxyFactory類
public class ProxyFactory{
//維護一個目標對象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
private List<String> cache=new ArrayList<>();
//給目標對象生成代理對象
public Object getProxyInstance(){
Object o=Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
for(Method m:target.getClass().getMethods()){
Annotation[] annotations=m.getAnnotations();
for(Annotation a:annotations){
if(a instanceof CompteTime){
if(!cache.contains(m.getName())){
cache.add(m.getName());
}
}
}
}
return o;
}
class MyInvocationHandler implements InvocationHandler{
private Object target;
MyInvocationHandler(Object o){
target=o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("invoke:"+proxy.getClass().getName());
boolean isComputeleTime=false;
if(cache.contains(method.getName())){
isComputeleTime=true;
}
if(isComputeleTime){
return comleteTime(method,args);
}else{
return method.invoke(target, args);
}
}
private Object comleteTime(Method method,Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{
System.out.println("代理方法:"+method.getName());
System.out.println("代理前操作...");
long start=System.currentTimeMillis();
Object returnValue=method.invoke(target, args);//執行目標對象方法
long end=System.currentTimeMillis();
System.out.println("耗時:"+(end-start)+"ms");
System.out.println("代理後操作...");
return returnValue;
}
}
}
調用沒變
ProxyFactory proxyFactory=new ProxyFactory(re);
Subject o= (Subject)proxyFactory.getProxyInstance();
o.sayHello();
o.request();
輸出:
代理方法:sayHello
代理前操作...
sayHello
耗時:0ms
代理後操作...
RealSubject方法request被調用
可以看到需要計算的方法被計算了,不需要的還是原來的