java中的静态代理和动态代理

一、什么是代理

假如有一个目标对象A,使用者想使用A中的方法,它可以先访问一个代理对象B,再由B去访问A,此时B对象就是A的代理;这时可以在不改变A源码的情况下,通过B来增强A中方法的功能。其中的A类被称为委托类,B类被称为代理类。

代理的实现方式有两种,静态代理和动态代理。

二、静态代理

静态代理实际上是创建了一个代理类,传入原始的委托类的对象,在代理类的内部先执行增强的功能,再执行委托类的方法。为了保持功能的一致,委托类和代理类会实现同一个接口,以下以一个UserService的场景举例。

假设存在一个IUserService接口,其中有对用户相关操作的方法

package com.lyy.service;

/**
 * UserService接口,提供用户相关的操作
 */
public interface IUserService {

    void addUser(String username);

    void deleteUser(String userId);
}

UserServiceImpl是实现类

package com.lyy.service.impl;

import com.lyy.service.IUserService;

public class UserServiceImpl implements IUserService {

    @Override
    public void addUser(String username) {
        System.out.println("增加用户:"+username);
    }

    @Override
    public void deleteUser(String userId) {
        System.out.println("删除用户:"+userId);
    }
}

现在创建一个UserServiceImpl的代理类来增强其功能,比如在方法的执行前后增加日志输出的功能

package com.lyy.service.proxy;

import com.lyy.service.IUserService;

/**
 * UserService静态代理类
 */
public class UserServiceLogProxy implements IUserService {

    private IUserService userService;

    //通过构造方法传入委托类的对象
    public UserServiceLogProxy(IUserService userService){
        this.userService=userService;
    }

    //给委托类的方法执行前后增加日志输出的功能
    @Override
    public void addUser(String username) {
        System.out.println("addUser开始执行");
        userService.addUser(username);
        System.out.println("addUser执行完成");
    }
    //给委托类的方法执行前后增加日志输出的功能
    @Override
    public void deleteUser(String userId) {
        System.out.println("deleteUser开始执行");
        userService.deleteUser(userId);
        System.out.println("deleteUser执行完成");
    }
}

最后测试通过代理对象来执行委托类的方法

	/**
     * 测试通过静态代理来增强委托类方法的功能
     */
    @Test
    public void test1(){
        //创建委托类的对象
        IUserService userService=new UserServiceImpl();
        //创建代理类的对象
        UserServiceLogProxy userServiceLogProxy=new UserServiceLogProxy(userService);
        //通过代理类的对象来执行委托类的方法
        userServiceLogProxy.addUser("张三");
        System.out.println("-----------------");
        System.out.println("-----------------");
        userServiceLogProxy.deleteUser("123");
    }

执行的结果如下图

以上就是使用静态代理来增强委托类方法的功能的举例。现在对这种方式的优缺点进行分析

优点: 在不改变UserServiceImpl源码的前提下增强了其中方法的功能

缺点: 委托类和代理类实现了同样的接口,有较多的代码重复;一个代理类只能对一个委托类的方法进行增强,如果需要对另一个Service实现类也添加前后日志记录的功能,则必须再创建一个代理类,这个代理类的代码不能复用。

三、动态代理

动态代理的动态是相对于静态代理来说的。对于静态代理,代理类是在编码时就完成的,在程序运行前就生成了对应的字节码文件;而动态代理,是在程序运行时才动态的生成代理类的字节码文件。

java中实现动态代理有两种方式,Proxy 和cglib

1. 使用Proxy 实现动态代理

如果一个类至少实现了一个接口,就可以使用jdk自带的 Proxy 来动态的增强其方法的功能

(1)创建一个动态代理类

package com.lyy.service.proxy;

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

/**
 * 使用动态代理来增强方法的功能,给方法的前后增加日志
 * 代理类需要实现InvocationHandler接口,重写invoke方法
 */
public class LogServiceProxy implements InvocationHandler {

    private Object targetObj;//委托类的对象

    public LogServiceProxy(Object targetObj){
        this.targetObj=targetObj;
    }

    /**
     * 重写该方法,在其中完成委托类方法功能的增强
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+",开始执行");
        method.invoke(targetObj,args);//使用反射来执行委托类中的方法
        System.out.println(method.getName()+",执行成功");
        return null;
    }
}

(2)使用该代理类

 /**
     * 测试用动态代理来增强委托类的方法功能
     */
    @Test
    public void test2(){
        //创建委托类的对象
        IUserService userService=new UserServiceImpl();
        //创建代理类的对象
        LogServiceProxy logServiceProxy=new LogServiceProxy(userService);
        // 创建功能增强后的委托类对象,用委托类实现的接口作为类型来接收
        IUserService userServiceProxy= (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),logServiceProxy);

        //通过增强对象来执行方法
        userServiceProxy.addUser("李四");
        System.out.println("-----------------");
        System.out.println("-----------------");
        userServiceProxy.deleteUser("456");
    }

结果如图

使用过程主要分为如下几步:

  • 创建为委托类的对象
  • 创建代理类的对象并传入委托类的对象
  • 通过Proxy得到功能被增强的委托类对象,用它实现的接口类型来接收
  • 通过增强对象执行方法

Proxy.newProxyInstance(1,2,3)方法的3个参数介绍

1,传入委托类的类加载器

2,传入委托类实现的接口数组

3,代理类的对像

2.动态代理的优势

以上使用动态代理的LogServiceProxy代理类,不仅可以用来增强UserService的功能,还可以用来增强其他Service的功能,是可以复用的。比如有一个BolgServcie

IBlogService接口

package com.lyy.service;

/**
 * BlogService接口
 */
public interface IBlogService {

    void addBlog(String name);
}

BlogServiceImpl实现类

package com.lyy.service.impl;

import com.lyy.service.IBlogService;

public class BlogServiceImpl implements IBlogService {
    @Override
    public void addBlog(String name) {
        System.out.println("增加博客:"+name);
    }
}

使用LogServiceProxy来增强其功能

/**
     * 使用动态代理增强BlogService的功能
     */
    @Test
    public void test3(){
        //创建委托类的对象
        IBlogService blogService=new BlogServiceImpl();
        //创建代理类的对象
        LogServiceProxy logServiceProxy=new LogServiceProxy(blogService);
        // 创建功能增强后的委托类对象,用委托类实现的接口作为类型来接收
        IBlogService proxyBlogService= (IBlogService) Proxy.newProxyInstance(blogService.getClass().getClassLoader(),
                blogService.getClass().getInterfaces(),logServiceProxy);
        //通过增强对象来执行方法
        proxyBlogService.addBlog("动态代理学习");
    }

运行结果如下图

说明这个代理类是可以复用的

3.proxy动态代理的局限性

使用jdk自带的proxy实现动态代理有一个限制,就是委托类最少要实现一个接口,否则不能使用这种方式。

四、cglib动态代理

使用第三方提供的cglib也可以实现动态代理。该方式不要求委托类一定要实现接口,但它要求委托类不能是最终类,即要能够被继承。

使用maven构建工程时cglib的maven依赖如下

<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>

创建一个代理类,实现MethodInterceptor接口

package com.lyy.service.proxy;

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

import java.lang.reflect.Method;

/**
 * 使用cglib动态代理,实现service方法的日志功能增强
 */
public class LogServiceByCglib implements MethodInterceptor {

    private Object targetObject;

    //使用构造方法传入委托类的对象
    public LogServiceByCglib(Object targetObject){
        this.targetObject=targetObject;
    }

    //通过这个方法增强委托类的功能
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println(method.getName()+",开始执行");
        method.invoke(targetObject,args);
        System.out.println(method.getName()+",执行成功");
        return null;
    }
}

在测试类中测试,分别测试用cglib增强有实现接口的类和没有实现接口的类的方法

测试增强blogService的功能

	/**
     * 测试使用cglib来动态增强service的功能
     */
    @Test
    public void test4(){
        //创建委托类的对象
        IBlogService blogService=new BlogServiceImpl();
        //创建代理类的对象
        LogServiceByCglib logServiceByCglib=new LogServiceByCglib(blogService);
        // 创建功能增强后的委托类对象,用委托类实现的接口作为类型来接收(也可以直接用委托类来接收)
        IBlogService proxyBlogService = (IBlogService) Enhancer.create(blogService.getClass(), logServiceByCglib);

        //通过增强对象来执行方法
        proxyBlogService.addBlog("cglib动态代理");
    }

新建一个没有实现接口的service

package com.lyy.service;

/**
 * 测试cglib实现动态代理,该service类没有实现接口
 */
public class AccountService {

    public void addAccount(String name){
        System.out.println("添加账户:"+name);
    }
}

测试增强该类中方法的功能

/**
     * 测试用cglib来增强没有实现接口的类中方法的功能
     */
    @Test
    public void test5(){
        //创建委托类的对象
        AccountService accountService=new AccountService();
        //创建代理类的对象
        LogServiceByCglib logServiceByCglib=new LogServiceByCglib(accountService);
        // 创建功能增强后的委托类对象,直接用委托类来接收
        AccountService proxyAccountService = (AccountService) Enhancer.create(accountService.getClass(), logServiceByCglib);
        //执行方法
        proxyAccountService.addAccount("cglib增强没有实现接口的类");
    }

五、总结

静态代理和动态代理都可以在不修改委托类源码的基础上增强其中方法的功能,但动态代理更具有通用性,不需要有一个service就创建一个代理类。

动态代理有两种实现方式,各自都有自己的使用要求,

proxy:要求委托类至少实现一个接口

cglib:要求委托类不能是最终类,即该类是可以被其他类继承的。