代理模式

1. 概念

在代理模式中,一个类代表另一个类的功能,即我们创建具有现有对象的对象,以便向外界提供功能接口。主要为其他对象提供一种代理以控制对这个对象的访问。

1.1. 针对的问题
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层

2. 涉及角色

抽象角色:声明真实对象和代理对象的共同接口;

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装

真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

3.代理分类

根据以上对代理的理解,对于代理的具体实现,我们有不同的方式,如果按照代理的创建时期,代理类可以分为两种。:静态代理、动态代理。

3.1静态代理

由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

	 public interface Account {  
        // 查询 
        public void queryAccount ();  
  
        // 修改  
		public void updateAccount ();    
    }  
 
   /** 
     * 接口实现类(包含业务逻辑) 
     *  即:委托类
     * @author Cassie  
     */  
	public class AccountImpl implements Account{  
      
        @Override  
		public void queryAccount() {  
            System.out.println("查询方法...");        
        }  
      
        @Override  
        public void updateAccount() {  
            System.out.println("修改方法...");        
        }  
      
    }
    
	//代理类
	public class AccountProxy implements Account{  
		private AccountImpl accountImpl;  
  
	    /** 
	     * 重写默认构造函数
	     * @param accountImpl :真正要执行业务的对象
	     */  
		public AccountProxy(AccountImpl accountImpl) {  
        	this.accountImpl =accountImpl;  
    	}  
  
	    @Override  
	    public void queryAccount() {  
	        System.out.println("业务处理之前...");  
	        // 调用委托类的方法,这是具体的业务方法  
	       account>Impl.queryCount();  
	        System.out.println("业务处理之后...");  
	    }  
  
	    @Override  
	    public void updateAccount() {  
	        System.out.println("业务处理之前...");  
	        // 调用委托类的方法;  
	        accountImpl.updateAccount();  
	        System.out.println("业务处理之后...");    
	    }    
	} 
3.2.动态代理

静态代理需要在运行之前就写好代理类,这样就造成了代码的大量重复,所以我们通过动态代理在运行时期动态生成业务类的代理类,那么动态代理类是如何实现的呢?

动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。 原来是利用反射的机制来实现的.

1.JDK的动态代理的实现。

JDK动态代理中包含一个类和一个接口: InvocationHandler接口,和我们定义的一个实现类Proxy,这是一个万能的代理类,我们就是通过这个代理类来动态代理的。每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的Proxy实例都关联了动态代理类调用处理程序,当我们通过Proxy实例调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。

InvocationHandler接口

	public interface InvocationHandler { 
		public Object invoke(Object proxy,
	    	Method method,Object[] args) throws Throwable; 
      	} 
  参数说明: 
       Object proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
       Method method:我们所要调用某个对象真实的方法的Method对象
      Object[] args:指代代理对象方法传递的参数

Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

	public static Object newProxyInstance(ClassLoader loader, 
				Class[] interfaces, InvocationHandler h)  
						throws IllegalArgumentException 
参数说明: 
       ClassLoader loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
      Class<[] interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
      InvocationHandler h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

举例说明:
首先我们定义了一个Subject类型的接口,为其声明了两个方法:

	public interface Subject
	{
	    public void rent();
	    
	    public void hello(String str);
	}    

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

   public class RealSubject implements Subject
{
    @Override
    public void rent()
    {
        System.out.println("I want to rent my house");
    }
    
    @Override
    public void hello(String str)
    {
        System.out.println("hello: " + str);
    }
}

下一步,我们就要定义一个动态代理类处理程序了,前面说个,每一个动态代理处理程序类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理处理程序类也不例外:

	public class DynamicProxy implements InvocationHandler
{
    // 这个就是我们要代理的真实对象
    private Object subject;
    
    //    构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject)
    {
        this.subject = subject;
    }
    
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
        
        System.out.println("Method:" + method);
        
        //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        
        //  在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }

}

最后,来看看我们的Client类:

public class Client
{
    public static void main(String[] args)
    {
        //    我们要代理的真实对象
        Subject realSubject = new RealSubject();

        //    我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);
        
        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}

我们看控制台输出:

$Proxy0

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
I want to rent my house
after rent house

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after rent house

我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?
可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。可参考Java反射之getInterfaces()方法

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

接着我们来看看这两句
subject.rent();
subject.hello("world");

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:

1、代理类继承了Proxy类并且实现了要代理的接口,由于java不支持多继承,所以JDK动态代理不能代理类
2、重写了equals、hashCode、toString
3、有一个静态代码块,通过反射或者代理类的所有方法
4、通过invoke执行代理类中的目标方法doSomething

JDK的动态代理也有缺陷,即每个委托类都必须是要有接口的,也就是说JDK的动态代理依靠接口实现,要是我一个没有接口的类想被代理怎么办?如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。

2.cglb动态代理

CGLIB的底层采用ASM字节码生成框架,使用字节码技术生成代理,比使用反射生成代理的效果要高,是对指定的目标类生成一个子类,并覆盖其中方法实现增强。但是也有一点点不足,因为采用的是继承,所以不能对final修饰的类进行代理。

    public class Account{           
        @Override      
        public void queryAccount() {      
            System.out.println("查询方法...");            
        }            
        @Override      
        public void updateAccount() {      
            System.out.println("修改方法...");            
        }           
    }


	public class CglibProxy implements MethodInterceptor {  
    	//委托对象,运行时定类型
    	private Object target;  
  
	    /** 
	     * 创建代理对象 
	     *  
	     * @param target 
	     * @return 
	     */  
   		public Object getInstance(Object target) {  
        	this.target = target;  
        	Enhancer enhancer = new Enhancer();  
        	enhancer.setSuperclass(this.target.getClass());  
        	// 回调方法  
        	enhancer.setCallback(this);  
        	// 创建代理对象  
        	return enhancer.create();  
   		}  
  
	    @Override  
	    // 回调方法  
	    public Object intercept(Object obj, Method method, Object[] args,  
	            MethodProxy proxy) throws Throwable {  
	        System.out.println("before");  
	        Object result = proxy.invokeSuper(obj, args);  
	        System.out.println("after");  
	        return result
    	}  
	}

	public class TestCglib {  
		      
		  public static void main(String[] args) { 
		//实例化代理 
		  CglibProxy cglib=new CglibProxy(); 
		//通过代理拿到对象 
		  Account account = (Account)cglib.getInstance(new Account());  
		  account.query();  
		 }  
	}

使用CGLIB,需要实现 CGLib 给我们提供的 MethodInterceptor 实现类,并填充 intercept() 方法。方法中最后一个 MethodProxy 类型的参数 proxy,值得注意!CGLib 给我们提供的是方法级别的代理,也可以理解为对方法的拦截。我们直接调用 proxy 的 invokeSuper() 方法,将被代理的对象 obj 以及方法参数 args 传入其中即可。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值