Java基础:代理

这里写目录标题

什么是代理

代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展。


比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing()

java 复制代码
1 public class Singer{
2     public void sing(){
3         System.out.println("唱一首歌");
4     }  
5 }

假如你希望对目标对象Singer的sing方法进行功能扩展,例如在唱歌前后向观众问好和答谢,类似这样:

java 复制代码
1 public void sing(){
2     System.out.println("向观众问好");
3     System.out.println("唱一首歌");
4     System.out.println("谢谢大家");
5 }  

但是又不能直接对源代码进行修改,甚至有可能你都不知道要对哪个目标对象进行扩展。这时就需要用到java的代理模式了。

1.静态代理(委托类、代理类):

静态代理要求原始类有实现某个接口。

需要创建一个代理类,实现和原始类相同的接口,并在需要增强的方法里,调用原始类的该方法,调用前后加上我们需要添加的代码。

使用的时候,直接创建一个代理类实例,调用目标方法即可。

使用步骤:

共同接口

java 复制代码
public interface Action {
    public void doSomething();
}

原始类

java 复制代码
public class RealObject implements Action{
    public void doSomething() {
        System.out.println("do something");
    }
}

代理类

java 复制代码
public class Proxy implements Action {
    private Action realObject; 
    public Proxy(Action realObject) {
        this.realObject = realObject;
    }
    public void doSomething() {
        System.out.println("proxy do");
        realObject.doSomething();
    }
}

使用

java 复制代码
Proxy proxy = new Proxy(new RealObject());
proxy.doSomething();

示例

java 复制代码
public interface ISinger {
    void sing();
}

/**
 *  目标对象实现了某一接口
 */
public class Singer implements ISinger{
    public void sing(){
        System.out.println("唱一首歌");
    }  
}

/**
 *  代理对象和目标对象实现相同的接口
 */
public class SingerProxy implements ISinger{
    // 接收目标对象,以便调用sing方法
    private ISinger target;
    public UserDaoProxy(ISinger target){
        this.target=target;
    }
    // 对目标对象的sing方法进行功能扩展
    public void sing() {
        System.out.println("向观众问好");
        target.sing();
        System.out.println("谢谢大家");
    }
}

测试类:

java 复制代码
/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        //目标对象
        ISinger target = new Singer();
        //代理对象
        ISinger proxy = new SingerProxy(target);
        //执行的是代理的方法
        proxy.sing();
    }
}

总结:这里做的事情无非就是,创建一个代理类SingerProxy,继承原始类的ISinger接口,实现其中的方法,并在实现中调用目标对象的方法。这里的关键是"调用目标对象方法",如果直接重写就不叫代理了。

优缺点

优点:扩展原功能,不侵入原代码。

缺点:

①冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。

②不易维护。代理对象必须提前写出,一旦接口发生了变化,代理对象的代码也要进行维护。

2.动态代理(委托类、中介类)

代理类在程序运行时运用反射机制创建 的代理方式被成为动态代理。

也就是说,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的"指示"动态生成的。

相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

2.1 JDK动态代理

同样要求原始类实现某个接口,但不用手动创建代理类,而是创建中介类。中介类实现InvocationHandler接口。

使用:

调用Proxy类中的newProxyInstance(ClassLoader loader,Class<?>[]

interfaces,InvocationHandler h)方法以创建一个动态代理对象,其中第三个参数为我们创建的实现InvocationHandler接口的类(中介类),前两个参数可通过目标类.getclass().getxxx获取。

中介类:

需实现InvocationHandler接口,包含一个Object类型的对象,并利用其编写中介类的有参构造函数。重写的方法:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable里,proxy表示代理类对象, method标识了我们具体调用的代理类的方法,args为这个方法的参数。

示例1:
java 复制代码
public interface ISinger {
    void sing();
}

/**
 *  目标对象实现了某一接口
 */
public class Singer implements ISinger{
    public void sing(){
        System.out.println("唱一首歌");
    }  
}

-------------------------

public class Test{
    public static void main(String[] args) {
        Singer target = new Singer();//这行要自己写
        ISinger proxy  = (ISinger) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("向观众问好");
                        //目标对象方法前后编写需要扩展的代码
                        Object returnValue = method.invoke(target, args);
                        System.out.println("谢谢大家");
                        return returnValue;
                    }
                });
        proxy.sing();
    }
}
示例2:
java 复制代码
public static void main(String[] args) throws InterruptedException {
	EnHello enHello=new EnHello();
	Hello hello=(Hello)Proxy.newProxyInstance(enHello.getClass().getClassLoader(),enHello.getClass().getInterfaces(), new MyInvocationHandler(enHello));
	hello.sayHello("Tom");

}

interface Hello{
	String sayHello(String username);
}

static class EnHello implements Hello{
	@Override
	public String sayHello(String username) {
		System.out.println("Hello, "+username);
		return "finished";
	}
}

static class MyInvocationHandler implements InvocationHandler{
	private Object object;
	public MyInvocationHandler(Object object){
		this.object=object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result=null;
		System.out.println("before say hello");
		if("sayHello".equals(method.getName())){
			result=method.invoke(object,args);
		}
		System.out.println("before say hello");
		return result;
	}
}

还可以只为指定方法动态代理,在invoke方法加上以下判断:

java 复制代码
String methodName = method.getName();
if("eating".equals(methodName))
    method.invoke(obj,args);

优点一:可以隐藏委托类的实现;

优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理

2.2 CGLib动态代理

JDK动态代理和cglib动态代理有什么区别?

使用JDK动态代理的对象必须实现一个或多个接口

使用cglib代理的对象则无需实现接口。

cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理,所以如果目标对象被final修饰,那么该类无法被cglib代理。

使用方法:

导包-创建MethodInterceptor实现类

使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。

目标类(原始类)不能为final

目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

示例1:
java 复制代码
/**
 * 目标对象,没有实现任何接口
 */
public class Singer{

    public void sing() {
        System.out.println("唱一首歌");
    }
}

----------------------

/**
 * Cglib子类代理工厂
 */
public class ProxyFactory implements MethodInterceptor{
    // 维护目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

/*
使用时只有intercept方法中,代码行 method.invoke前后的代码需要修改,其他的代码直接使用
*/


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("向观众问好");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("谢谢大家");
        return returnValue;
    }
}

-----------------------

/**
 * 测试类
 */
public class Test{
    public static void main(String[] args){
        //目标对象
        Singer target = new Singer();
        //代理对象
        Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
        //执行代理对象的方法
        proxy.sing();
    }
}
示例2:
java 复制代码
public class TestCglib implements MethodInterceptor {    
    Object target;   
    //动态生成一个新的类,使用父类的无参构造方法创建一个指定了特定回调的代理实例    
    public Object getProxyObject(Object object) {      
        this.target = object;       
        //增强器,动态代码生成器     
        Enhancer enhancer=new Enhancer();        
        //回调方法
        enhancer.setCallback(this); 
        //设置生成类的父类类型        
        enhancer.setSuperclass(target.getClass());   
        //动态生成字节码并返回代理对象      
        return enhancer.create();   
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy 
methodProxy) throws Throwable {
        System.out.println("----------before");    
        // 调用方法      
        Object result = methodProxy.invoke(target, objects);   
        System.out.println("----------after");       
        return null;  
    }
}

//使用
public static void main(String[] args) {
      Boss boss=(Boss) new TestCglib().getProxyObject(new Boss());
      boss.eating();
      boss.sleeping();
}
相关推荐
Swift社区3 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht3 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht3 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20243 小时前
Swift 数组
开发语言
吾日三省吾码4 小时前
JVM 性能调优
java
stm 学习ing4 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc5 小时前
《Python基础》之字符串格式化输出
开发语言·python
弗拉唐5 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi776 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器