zoukankan      html  css  js  c++  java
  • java中的代理模式

    代理模式

    在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

    简单来说代理模式就是通过一个代理对象去访问一个实际对象,并且可以像装饰模式一样给对象添加一些功能。

    静态代理

    所谓静态代理即在程序运行前代理类就已经存在,也就是说我们编写代码的时候就已经把代理类的代码写好了,而动态代理则是在程序运行时自动生成代理类。

    描述起来太过抽象,看一下代码就明白是怎么回事了

    • main
    public class Main {
    
    	public static void main(String[] args) {
    		Water water = new Water();
    		WaterProxy waterProxy = new WaterProxy(water);
    		waterProxy.drink();
    	}
    
    }
    
    • 接口
    //代理类与被代理类共同实现的接口
    public interface Drink {
    	void drink();
    }
    
    
    • 被代理类
    //被代理的类
    public class Water implements Drink {
    
    	@Override
    	public void drink() {
    		System.out.println("drink water");
    	}
    
    }
    
    • 代理类
    //代理类
    //与被代理类实现同一个接口
    public class DrinkProxy implements Drink {
    	
    	private Drink drinkImpl;
    	
    	//通过构造函数传入Water对象
    	public DrinkProxy(Drink drinkImpl) {
    		this.drinkImpl = drinkImpl;
    	}
    	
    	@Override
    	public void drink() {
    		//在执行被代理对象的方法前做一些事情
    		System.out.println("before drink");
    		//执行被代理对象的方法
    		drinkImpl.drink();
    		//在执行被代理对象的方法后做一些事
    		System.out.println("after drink");
    	}
    
    }
    

    执行结果

    before drink
    drink water
    after drink
    

    动态代理

    有时候我们只想改变代理类所代理的类,但是代理对象执行实际对象的方法前后所做的事情是一样的,正所谓铁打的代理类,流水的被代理类。而采用静态代理就只能代理实现了同一接口的类,如果要代理任意类则必须写很多重复的代理类。此时我们可以采用动态代理,java已经为实现动态代理提供了一套比较方便的工具。

    • java.lang.reflect.Proxy类中可以动态生成代理对象的方法
      /**
         *返回实现了指定接口的对象,调用代理对象的方法会调用 
         *InvocationHandler的invoke方法
         *
         * @param   loader 获取代理类所使用的类加载器
         * @param   interfaces 代理类所要实现的接口
         * @param   h 实现了InvocationHandler接口的对象
         * @return  代理对象
         */
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
    
    • InvocationHandler接口
    /**
     *每个代理类都有一个关联的InvocationHandler
     *当代理对象执行一个方法的时候会直接执行invoke方法
     */
    public interface InvocationHandler {
    
        /**
         * @param   调用该方法的代理对象
         * @param   method 代理对象所调用的方法
         * @param   args 调用的方法的参数
         * @return  调用的方法的返回值
         */
        public Object invoke(Object proxy, Method method, Object[] args)
    }
    
    

    描述总是比较抽象,还是看实际例子比较好理解

    例子

    • InvocationHandler接口的实现类
    public class CommonInvocationHandler implements InvocationHandler {
    	
    	//被代理的对象
    	private Object proxied;
    	
    	public CommonInvocationHandler(Object proxied) {
    		this.proxied = proxied;
    	}
    	
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		//在调用被代理对象的方法前做一些事情
    		System.out.println("before doing something");
    		//调用被代理对象的方法
    		Object result = method.invoke(proxied, args);
    		//在调用被代理对象的方法后做一些事情
    		System.out.println("after doing something");;
    		return result;
    	}
    
    }
    
    • Main
    
    public class Main {
    
    	public static void main(String[] args) {
    		//被代理的对象
    		Water water = new Water();
    		//动态获取代理对象
    		Drink waterProxy = 
    				(Drink) Proxy.newProxyInstance(water.getClass().getClassLoader(),
    						water.getClass().getInterfaces(), 
    						new CommonInvocationHandler(water));
    		//通过代理对象调用方法
    		waterProxy.drink();
    	}
    
    }
    
    
    • 输出结果
    before doing something
    drink water
    after doing something
    

    也可以不要具体的被代理对象,但是必须有相应的接口(没有实现接口的类可以使用cglib实现动态代理)才可以动态获取代理对象。像最近比较火的Retrofit就直接通过声明好的接口使用动态代理进行网络请求。

    例子

    简单的模拟一下retrofit

    • POST注解
    //Post请求注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface POST {
      String value() default "";
    }
    
    • Query注解
    //Post请求注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface POST {
      String value() default "";
    }
    
    • Service接口
    public interface Service {
    	//用POST注解声明请求的方式和相对路径
    	@POST("/login")
    	//@Query注解声明请求的参数名
    	void login(@Query("username")String username, 
    			@Query("password")String password);
    }
    
    
    • Main
    public class Main {
    
    	public static void main(String[] args) {
    		// 动态获取Service接口的代理
    		Service service = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(),
    				new Class[] { Service.class }, new InvocationHandler() {
    
    					@Override
    					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    						// 通过注解获取请求的相对路径
    						String retativePath = ((POST) method.getAnnotations()[0]).value();
    						System.out.println("relative path: " + retativePath);
    						// 获取参数的注解
    						Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    						// 通过参数的注解获取请求参数
    						for (int i = 0; i < parameterAnnotations.length; i++) {
    							if (parameterAnnotations[i].length != 0) {
    								for (int j = 0; j < parameterAnnotations[i].length; j++) {
    									Query query = (Query) parameterAnnotations[i][j];
    									System.out.println(query.value() + ": " + args[i].toString());
    								}
    							}
    						}
    						return null;
    					}
    				});
    		// 调用代理对象的方法
    		service.login("hello", "world");
    	}
    
    }
    

    参考

    JAVA动态代理

    代理模式

  • 相关阅读:
    Bzoj1597 [Usaco2008 Mar]土地购买
    Bzoj1500 [NOI2005]维修数列
    模拟7 题解
    模拟6 题解
    模拟5 题解
    远古杂题 2
    远古杂题 1
    [NOIP2013]华容道 题解
    奇袭 CodeForces 526F Pudding Monsters 题解
    图论杂题
  • 原文地址:https://www.cnblogs.com/bloglkl/p/6350153.html
Copyright © 2011-2022 走看看