Java代理模式 动态代理举例

目录

一、代理模式是什么?Java的静态、动态代理模式

1、静态代理

2、动态代理

2.1 基于接口的动态代理(JDK官方)

2.2 基于子类的动态代理(第三方CGLib)

二、Spring AOP 怎么应用的动态代理


 

参考资料:

你真的完全了解Java动态代理吗?看这篇就够了

Java的三种代理模式

理解java的三种代理模式

 

一、代理模式是什么?Java的静态、动态代理模式

Proxy是一种设计模式,通过 代理对象 来访问 被代理对象 ,可以在被代理对象的基础之上,增强额外的功能,而不需要修改别人已经写好的代码。

1、静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

代码实例:

被代理对象与代理对象一起实现相同的接口如下:

/**
 * 接口
 */
public interface IUserDao {

    void save();
}

目标对象(被代理对象):

/**
 * 接口实现
 * 目标对象
 */
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已经保存数据!----");
    }
}

代理对象:

/**
 * 代理对象,静态代理
 */
public class UserDaoProxy implements IUserDao{
    //接收保存目标对象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("开始事务...");
        target.save(); //代理对象的方法中包含了被代理对象的方法,执行被代理对象的方法
        System.out.println("提交事务...");
    }
}

测试:

/**
 * 测试类
 */
public class App {
    public static void main(String[] args) {
        //目标对象
        UserDao target = new UserDao();

        //代理对象,把目标对象传给代理对象,建立代理关系
        UserDaoProxy proxy = new UserDaoProxy(target);

        proxy.save();//执行的是代理对象的方法
    }
}

总结:代理对象特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。这里的包含是关键。

静态代理的缺点:

1、需增加的代理类太多

2、被实现的接口如果增加了方法,被代理对象与代理对象都需要修改

 

2、动态代理

在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。

2.1 基于接口的动态代理(JDK官方)

动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)。代理对象不需要实现接口,需要指定接口类型。但是目标对象一定要实现接口,否则不能用动态代理

代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h )

三个参数的意义是:

ClassLoader loader:指定当前目标对象使用的类加载器,也就是代理对象被代理对象使用相同的类加载器

Class[] interfaces:目标对象实现的接口的类型。也就是代理对象被代理对象具有相同的行为。实现相同的接口。

InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

实例代码

目标对象必须实现的接口:

public interface IActor {
    /**
     * 基本演出
     * @param time 表演时长
     */
    public void basicAct(float time);

    /**
     * 危险演出
     * @param time 表演时长
     */
    public void dangerousAct(float time);
}

目标对象的类,实现了接口:

public class Actor implements IActor {

    /**
     * 基本演出
     *
     * @param time 表演时长
     */
    @Override
    public void basicAct(float time) {
        System.out.println("基本表演,时长为:" + time);
    }

    /**
     * 危险演出
     *
     * @param time 表演时长
     */
    @Override
    public void dangerousAct(float time) {
        System.out.println("危险表演,时长为:" + time);
    }
}

测试类,在本类中实现了代理对象

public class ClientTest {

    public static void main(String[] args){
        // 生成被代理的对象,此处和静态代理的目的不一样,所以不是生成IActor的实例
        final Actor actor = new Actor();  // 内部类调用外部成员变量,必须为final

        // 获取代理对象
        IActor proxy = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(),
                                                      actor.getClass().getInterfaces(),
                                                      new InvocationHandler() {
           /**
            *   执行被代理对象的任何方法,都会经过该方法。
            *   此方法有拦截的功能。
            * @param proxy 代理对象的引用。不一定每次都用得到
            * @param method 当前执行的方法对象
            * @param args  执行方法所需的参数
            * @return 前执行方法的返回值
            * @throws Throwable
            */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String name = method.getName(); // 代理对象执行了接口声明的任意方法都会调用本invoke方法,此处获得是调用了哪个方法名
                Float time = (Float)args[0];
                Object returnValue = null;  // Method method的返回值
                if("basicAct".equals(name)) {
                        returnValue = method.invoke(actor, time);
                }
                return returnValue;
            }
        });

        // 开始测试
        proxy.basicAct(100);
    }
}

 

上面的测试类中,明明代理对象已经是IActor类型的,为什么还要使用第二个getInterfaces()参数呢?如果看下面这个例子就能明白了:

/**
 * 创建动态代理对象
 * 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory{

    //维护一个目标对象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }

   //给目标对象生成代理对象
    public Object getProxyInstance(){
        return 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("开始事务2");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事务2");
                        return returnValue;
                    }
                }
        );
    }

}

 

2.2 基于子类的动态代理(第三方CGLib)

有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

 

实例代码

还是之前的目标对象,但是没有实现接口

public class Actor {

    /**
     * 基本演出
     *
     * @param time 表演时长
     */
    @Override
    public void basicAct(float time) {
        System.out.println("基本表演,时长为:" + time);
    }

    /**
     * 危险演出
     *
     * @param time 表演时长
     */
    @Override
    public void dangerousAct(float time) {
        System.out.println("危险表演,时长为:" + time);
    }
}

 

测试类,包含了生成的代理对象:

public class Client {
    /**
     * 基于子类的动态代理
     * 要求:
     * 被代理对象不能是最终类
     * 用到的类:
     * Enhancer
     * 用到的方法:
     * create(Class, Callback):创建参数Class的子类对象
     * 方法的参数:
     * Class:被代理对象的字节码
     * Callback:如何代理
     *
     * @param args
     */
    public static void main(String[] args) {
        final Actor actor = new Actor();
        Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
                new MethodInterceptor() {
                    /**
                     * 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何
                     方法进行增强。
                        在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自            
                        己的代理逻辑,类似于JDK中的InvocationHandler接口
                     *
                     * 参数:
                     * 前三个和基于接口的动态代理是一样的。
                     * proxy为:由CGLib动态生成的代理类实例
                     * MethodProxy:当前执行方法的代理对象,也就是生成的代理类对方法的代理引用。
                     * 更通俗的说,MethodProxy是cglib生成用来代替Method对象的一个对象,使用 
                     * MethodProxy比调用JDK自身的Method直接执行方法效率会有提升。
                     * 返回值:
                     * 当前执行方法的返回值
                     */
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] args,
                                            MethodProxy methodProxy) throws Throwable {
                        String name = method.getName();
                        Float time = (Float) args[0];
                        Object rtValue = null;
                        if ("basicAct".equals(name)) {
                            //基本演出
                            if (time > 2000) {
                                rtValue = method.invoke(actor, time / 2);
                            }
                        }
                        return rtValue;
                    }
                });
        cglibActor.basicAct(10000);
    }
}

二、Spring AOP 怎么应用的动态代理

在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理

你可能感兴趣的:(Java复习)