设计模式之代理模式

本文介绍了Java中的静态代理和动态代理两种方式,以强制程序员在写代码时更新文档为例。静态代理易于理解和实现,但需要为每个类创建一个代理类。动态代理分为基于接口的InvocationHandler和基于类的CGLIB实现。InvocationHandler能代理多个对象,但要求被代理类实现接口;CGLIB适用于未实现接口的类,但需要创建被代理类的子类。
摘要由CSDN通过智能技术生成

1、静态代理

开发一个接口IDeveloper,该接口包含一个方法writeCode,写代码。

public interface IDeveloper {
     public void writeCode();
}

创建一个Developer类,实现该接口。

public class Developer implements IDeveloper{
	private String name;
	public Developer(String name){
		this.name = name;
	}
	@Override
	public void writeCode() {
		System.out.println("Developer " + name + " writes code");
	}
}

测试代码:创建一个Developer实例,名叫Jerry,去写代码!

public class DeveloperTest {
	public static void main(String[] args) {
		IDeveloper jerry = new Developer("Jerry");
		jerry.writeCode();
	}
}

现在问题来了。Jerry的项目经理对Jerry光写代码,而不维护任何的文档很不满。假设哪天Jerry休假去了,其他的程序员来接替Jerry的工作,对着陌生的代码一脸问号。经全组讨论决定,每个开发人员写代码时,必须同步更新文档。

为了强迫每个程序员在开发时记着写文档,而又不影响大家写代码这个动作本身, 我们不修改原来的Developer类,而是创建了一个新的类,同样实现IDeveloper接口。这个新类DeveloperProxy内部维护了一个成员变量,指向原始的IDeveloper实例:

public class DeveloperProxy implements IDeveloper{
	private IDeveloper developer;
	public DeveloperProxy(IDeveloper developer){
		this.developer = developer;
	}
	@Override
	public void writeCode() {
		System.out.println("Developer is Write documentation...");
		this.developer.writeCode();
	}
}

这个代理类实现的writeCode方法里,在调用实际程序员writeCode方法之前,加上一个写文档的调用,这样就确保了程序员写代码时都伴随着文档更新。

静态代理方式的优点

1. 易于理解和实现

2. 代理类和真实类的关系是编译期静态决定的,和下文马上要介绍的动态代理比较起来,执行时没有任何额外开销。

静态代理方式的缺点

每一个真实类都需要一个创建新的代理类。还是以上述文档更新为例,假设老板对测试工程师也提出了新的要求,让测试工程师每次测出bug时,也要及时更新对应的测试文档。那么采用静态代理的方式,测试工程师的实现类ITester也得创建一个对应的ITesterProxy类。

public interface ITester {
	public void doTesting();
}

public class Tester implements ITester {
	private String name;
	public Tester(String name){
		this.name = name;
	}
	@Override
	public void doTesting() {
		System.out.println("Tester " + name + " is testing code");
	}
}
public class TesterProxy implements ITester{
	private ITester tester;
	public TesterProxy(ITester tester){
		this.tester = tester;
	}
	@Override
	public void doTesting() {
		System.out.println("Tester is preparing test documentation...");
		tester.doTesting();
	}
}

正是因为有了静态代码方式的这个缺点,才诞生了Java的动态代理实现方式。

Java动态代理实现方式一:InvocationHandler

InvocationHandler的原理我曾经专门写文章介绍过:Java动态代理之InvocationHandler最简单的入门教程

通过InvocationHandler, 我可以用一个EnginnerProxy代理类来同时代理Developer和Tester的行为。

public class EnginnerProxy implements InvocationHandler {
	Object obj;
	public Object bind(Object obj)
	{
		this.obj = obj;
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
		.getClass().getInterfaces(), this);
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
	throws Throwable
	    {
		
            String originName = method.getName();
            //System.out.println("originName:"+originName);
            if(originName.equals("writeCode")){
            System.out.println("Developer is Write documentation...");
        }else if(originName.equals("doTesting")){
            System.out.println("Tester is preparing test documentation...");
        }
        //JVM通过这条语句执行原来的方法(发射机制)
        Object res = method.invoke(obj, args);
        return res;

	}
}

真实类的writeCode和doTesting方法在动态代理类里通过反射的方式进行执行。

测试代码:

package com.example.designpatterns.proxy.invocationHandler;

import com.example.designpatterns.proxy.*;

/**
 * 动态代理方式一:
 * 基于接口:JDK的InvocationHandler
 *
 * 优点:可以同时代理多个对象
 * 缺点:如果被代理的类未实现任何接口,那么不能采用通过InvocationHandler动态代理的方式去代理它的行为
 */
public class TestMain {

    public static void main(String[] args) {
        IDeveloper developer = new Developer("Tom");
        ITester tester = new Tester("Jack");

        IDeveloper developerPorxy = (IDeveloper) new EnginnerProxy().bind(developer);
        ITester testerProxy = (ITester) new EnginnerProxy().bind(tester);
        developerPorxy.writeCode();
        testerProxy.doTesting();

        //产品经理类(ProductOwner) 没有实现任何接口,我们仍然采取EnginnerProxy代理类去代理它,编译时不会出错,运行时会报错。所以局限性就是:如果被代理的类未实现任何接口,那么不能采用通过InvocationHandler动态代理的方式去代理它的行为。
        /*ProductOwner productOwner = new ProductOwner("Rose");
        ProductOwner productProxy = (ProductOwner) new EnginnerProxy().bind(productOwner);
        productProxy.dofineBackLog();*/
    }
}
 Java动态代理实现方式二:CGLIB

假设有个产品经理类(ProductOwner) 没有实现任何接口。

public class ProductOwner {

    private String name;

    /**
     * Spring通过CGLIB生成代理类对象时,并没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,结果目标对象类没有默认构造函数,CGLIB生成子类时,也没有加入默认构造函数,所以,异常的发生成为必然。
     * 目标对象,定义一个无参数构造函数
     */
    public ProductOwner() {
    }

    public ProductOwner(String name) {
        this.name = name;
    }

    public void dofineBackLog() {
        System.out.println("PO: " + name + " defines Backlog.");
    }
}

使用CGLIB API来创建代理类

package com.example.designpatterns.proxy.cglib;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 动态代理之CGLIB
 *
 */
public class EnginnerCGLibProxy implements MethodInterceptor{
    Object object;

    public Object bind(Object target){
        this.object = target;
        //创建加强器,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        enhancer.setSuperclass(object.getClass());
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
        enhancer.setCallback(this);

        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Enginner 2 writes document");
        //调用业务类(父类中)的方法
        Object res = methodProxy.invokeSuper(o,objects);
        return res;
    }



}



测试代码:

package com.example.designpatterns.proxy.cglib;

import com.example.designpatterns.proxy.ProductOwner;

/**
 *  动态代理方式二:
 *  基于类:CGLIB创建代理类的原理
 *
 * 优点:可以同时代理多个对象
 * 缺点:通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。那么如果被代理类被标记成final,也就无法通过CGLIB去创建动态代理
 */
public class TestMain {

    public static void main(String[] args) {

        ProductOwner productOwner = new ProductOwner("Ross");
        ProductOwner productProxy = (ProductOwner) new EnginnerCGLibProxy().bind(productOwner);
        productProxy.dofineBackLog();
    }
}

用CGLIB实现Java动态代理的局限性

如果我们了解了CGLIB创建代理类的原理,那么其局限性也就一目了然。我们现在做个实验,将ProductOwner类加上final修饰符,使其不可被继承

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值