java中静态代理和动态代理

一、概述

代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能的特点也为多人所用。

按照代理的创建时期,代理类可以分为两种: 

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

动态:在程序运行时运用反射机制动态创建而成。

我们根据加载被代理类的时机不同,将代理分为静态代理和动态代理。如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制(反射),在代码运行期间加载被代理的类这就是动态代理,比如RPC框架和Spring AOP机制。

二、图形描述

  

  

三、静态代理

静态代理的实现比较简单,代理类通过实现与目标对象相同的接口,并在类中维护一个代理对象。通过构造器塞入目标对象,赋值给代理对象,进而执行代理对象实现的接口方法,并实现前拦截,后拦截等所需的业务功能,一个典型的代理模式通常有三个角色,这里称之为**代理三要素**:共同的接口,代理对象、被代理对象

package com.bjsxt.staticProxy;

/**
 * Created by Administrator on 2019/3/10.
 */
public class TestStaticProxy {

    public static void main(String[] args){
        //用法类似于线程的代理对象 new Thread(线程对象).start();
        new WeddingCompany(new You()).happyMarry();
    }
}

/**
 * 共同的接口
 */
interface Marry{
    void happyMarry();
}

/**
 * 真实的角色,被代理角色
 */
class You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("你结婚了。。。");
    }
}

/**
 * 代理角色,代理真实的角色
 */
class WeddingCompany implements Marry{

    private Marry marry;    //代理对象内部维护一个真实的角色(需要代理的角色)

    public WeddingCompany(Marry marry){
        this.marry=marry;
    }


    @Override
    public void happyMarry() {
        ready();
        marry.happyMarry();
        after();
    }


    private void ready() {
        System.out.println("布置婚礼。。。。");
    }
    private void after() {
        System.out.println("闹洞房。。。。");
    }
}

  静态代理的总结

  优点:可以做到不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截。

  缺点:因为代理对象,需要实现与目标对象一样的接口,会导致代理类十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护。

四、动态代理

  在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象,动态代理有两种方式jdk动态代理和cglib动态代理

  (1)使用jdk动态代理

  我们看到jdk的代理机制必须要求被代理类实现某个方法,这样在生成代理类的时候才能知道重写那些方法。(事实上jdk动态代理会动态生成一个被代理类所实现的接口的实现类,跟静态代理类似)这样一个没有实现任何接口的类就无法通过jdk的代理机制进行代理,当然解决方法是使用cglib的代理机制进行代理。

package com.bjsxt.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * JDK动态代理:使用前提(基于接口的实现)
 * JDK动态代理:本质上生产了一个代理类,该代理与被代理目标实现相同的接口,
 * 当前用户访 问代理类中的方法时,将委派InvacationHandler中的invoke()方法进行处理,
 * 在invoke()方法中添加业务处理代理和回调被代理目标中方法。
 */
public class TestJdkProxy {

    public static void main(String[] args){
        You you=new You();//创建被代理类
        WeddingCompany weddingCompany=new WeddingCompany(you);
        //生成代理类
        Marry marry=(Marry)weddingCompany.createProxy();
        //通过代理类,调用被代理类的方法
        marry.happyMarry();
    }

}

/**
 * 共同的接口
 */
interface Marry{
    void happyMarry();
}

/**
 * 真实的角色,被代理角色
 */
class You implements Marry {
    @Override
    public void happyMarry() {
        System.out.println("你结婚了。。。");
    }
}

class WeddingCompany implements InvocationHandler {

    private Object target;     //被代理的对象

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

    /**
     * 生成代理
     * @return
     */
    public Object  createProxy(){
        //获得加载被代理类的 类加载器
        ClassLoader loader = target.getClass().getClassLoader();
        //指明被代理对象实现的接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //this是当前类的对象,也是代理类,之后想要调用被代理类的方法时,都会委托给这个类的
        return Proxy.newProxyInstance(loader, interfaces, this);
    }

    /**
     *
     * @param proxy 代理对象
     * @param method    原对象被调用的方法
     * @param args  方法传递的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ready();
        Object returnValue=method.invoke(target,args);
        after();
        return returnValue;
    }


    private void ready() {
        System.out.println("布置婚礼。。。。");
    }
    private void after() {
        System.out.println("闹洞房。。。。");
    }
}

  jdk动态代理总结

    优点:代理对象无需实现接口,免去了编写很多代理类的烦恼,同时接口增加方法也无需再维护目标对象和代理对象,只需在事件处理器中添加对方法的判断即可。

    缺点:代理对象不需要实现接口,但是目标对象一定要实现接口,否则无法使用JDK动态代理。

  

(2)使用cglib动态代理

  cglib是通过继承的方式做的动态代理,因此,如果某个类被标记为final,那么它是无法使用cglib做动态代理。cglib会生成的代理类继承当前的被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等),因此被代理类有没有实现接口都是一样的

  当然使用cglib需要添加下cglib所需的jar包:cglib-2.2.2.jar,asm-3.3.1.jar,asm-util-3.3.1.jar,ant-1.6.2.jar

  

package com.bjsxt.dynamicCglibProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class TestCglibProxy {

    public static void main(String[] args){
        You you=new You();//创建被代理类
        WeddingCompany weddingCompany=new WeddingCompany(you);
        //生成代理类
        You you1=(You)weddingCompany.createProxy();
        //通过代理类,调用被代理类的方法
        you1.happyMarry();
    }

}


/**
 * 真实的角色,被代理角色
 */
class You {

    public void happyMarry() {
        System.out.println("你结婚了。。。");
    }
}

/**
 * 动态没生成代理对象
 * 实现 MethodInterceptor方法代理接口,创建代理类
 * 当访问代理中的方法时,委派给MethodInteceptor中的处理程序(intercept方法)进行出来,
 * 在处理程序中添加了业务逻辑和回掉了被代理目标中的方法。
 */
class WeddingCompany implements MethodInterceptor {

    private Object target;     //被代理的对象

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

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

    /**
     *
     * @param proxy 生成的代理对象
     * @param method    被调用的方法
     * @param args  方法的入参
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        ready();
        Object value = method.invoke(target, args);//回掉被代理对象中的方法
        after();
        return value;
    }

    private void ready() {
        System.out.println("布置婚礼。。。。");
    }
    private void after() {
        System.out.println("闹洞房。。。。");
    }
}

 

posted @ 2019-03-10 14:47  段然涛  阅读(741)  评论(0编辑  收藏  举报