静态代理、动态代理概念及使用

文章目录

  • 1. 为什么要用静态代理
  • 2. 静态代理的实现
  • 3. 静态代理的缺点
  • 4. 动态代理
    • 4.1 JDK动态代理
      • 4.1.1 InvocationHandler
      • 4.1.2 Class
      • 4.1.3 ClassLoader
    • 4.2 JDK动态代理编码
    • 4.3 JDK动态代理编码注意事项
    • 4.4 Cglib动态代理
      • 4.4.1 MethodInterceptor
    • 4.5 Cglib动态代理编码
  • 5. 对于两种方式的总结
  • 6. JDK动态代理与Cglib动态代理的区别

1. 为什么要用静态代理

个人博客地址:
http://xiaohe-blog.top

在分层开发中,哪个层次对我们来说更加重要呢 ?Dao?Service?Controller?

肯定是Service层了,不管做什么样的项目,其主要任务就是通过各个Service的分工合作来满足用户的需求。Dao专注于与数据库打交道,Controller负责处理前端传来的数据。Service负责连接二者完成业务。

那么Service中包含了哪些代码 ?

静态代理、动态代理概念及使用_第1张图片

我们要在Service中写两种代码 :附加功能与核心功能。

附加功能例如开启事务、记录一下这个操作进行的日期、机器的运行状态…

核心功能才是我们完成业务的代码。

附加功能的特点是什么呢?通用,例如事务无非就是开启、提交、回滚。假如附加业务有20行,一个addUser需要这么多附加业务,那还有dalateUser、getUser、UpdateUser…甚至其他的StudentService也需要这些代码,一个项目加起来怕是有上千行这样重复的代码。

那我们能不能把它们抽取出来制作为一个类,让这个类代替Service的附加功能,需要的时候直接调用呢?

这就是代理,将附加业务抽取出来,让其他的类完成,需要的时候直接调用即可。

这里有两个名词 :代理类、原始类。

代理类 :被抽取出来的附加功能。

原始类 :包含核心业务的类。


如果你还没有搞懂,那我们来看看代理模式典型案例 :租房 。在早期租房过程中,有两个角色 :租户、房东。

租户要去看租房信息、联系房东、看房、付钱。

房东需要去发布房子广告、带领客户看房、收钱。

按照一切皆对象的原则,我们可以将二者抽象为两个类

静态代理、动态代理概念及使用_第2张图片

设想一下你是房东,你的主要目的是什么 ?收钱啊,打广告、带人看房多累的事啊我tm一年啥也不干了就在外面打广告呢,老子只想拿钱,拿钱就是我的核心业务,其他我都不想干。

这时候另一个行业兴起了 :中介。你不想打广告?我帮你打!你不想带客户看房?我帮你带!虽然我没房,但是我愿意花这个时间挣这个钱啊。于是房东将租房的事交给中介…

静态代理、动态代理概念及使用_第3张图片

中介去打广告、带租户看房,等到满意了直接让房东收钱就好。

注意这里并不是我们想的房东调用中介的附加功能,而是全权交给中介,中介调用房东的核心功能。

中介就是代理类,房东是原始类。

于是我们就能总结静态代理的概念与好处 :

1. 概念: 通过代理类为原始类增加额外功能

2. 好处: 利于原始类功能的扩展与维护

2. 静态代理的实现

并且中介的方法名也要叫“出租房屋”,不叫出租房屋叫啥?出租电动车?肯定要与房东想要的一样啊。各位可能想的是将房东设计为接口,让中介实现它。但是房东的核心功能要实现,那么房东这个接口中全部都是默认方法了,很不美观。我们让房东和中介实现同样的接口不就行了?

在这里,中介是代理对象;房东依然是原始对象。

所以我们先定义一个接口:

public interface UserService {
    // 出租房屋
    public void rentHome();
}

让房东和中介都实现这个接口

// 房东
public class UserServiceImpl implements UserService{
    @Override
    public void rentHome() {
        System.out.println("收钱...");
    }
}

中介完成自己的核心功能,它的核心功能就是给房东添加附加功能。

// 中介
public class UserServiceProxy implements UserService{
    private UserServiceImpl fangDong = new UserServiceImpl();
    @Override
    public void rentHome() {
        System.out.println("打广告...");
        System.out.println("带客户看房...");
        System.out.println("签合同...");
        // 调用核心业务
        fangDong.rentHome();
       
        System.out.println("给房客提供后续业务..");
    }
}

以后,我们的房东可以专注于核心业务:“收钱” 的完成,无需关心其他。

这样,我们就完成了静态代理模式的开发。

3. 静态代理的缺点

虽然刚学静态代理,但是可以看出静态代理的缺点 :

一个房东租房需要编写一个代理类 ,那如果还有什么租车、租游戏号都需要中介…那么我们是不是要写很多代理类?

马上一个项目全是代理类得了。那我们能不能让别人替我们创建代理类呢?可以使用动态代理来解决这个问题。

动态代理很重要!!!

4. 动态代理

动态代理解决了静态代理的“代理类繁杂”问题,但也付出了代价 :编写难度高。希望大家可以坚持。

动态代理分为两种:

  1. JDK动态代理

  2. Cglib动态代理

他们的区别会在下文介绍。

4.1 JDK动态代理

JDK为我们提供了一个接口完成动态代理的开发 :Proxy,它有一个静态方法 newProxyInstance()

这个方法的参数有三个,初学者很容易被劝退,这将是我们学习的重点,也是难点。笔者将会将他们分为三个小模块分别讲解。(CGLIB代理也是这几个,现在学了,等会就不讲了)

Proxy.newInstance(
    ClassLoader loader, // 类加载器
    Class<?>[] interfaces, // 接口Class数组
    InvocationHandler h // 某个神秘的接口
)

静态代理、动态代理概念及使用_第4张图片

4.1.1 InvocationHandler

我们实现动态代理的主要目的是 “实现附加业务”,所以先来看看在哪里编写附加业务。

InvocationHandler是一个接口,我们需要实现它并将实现类传进去。它只有一个方法 :invoke

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable;
}

参数 :

Object proxy

核心业务对象。你为谁添加代附加功能?房东?那proxy就是房东对象。值得一提的是,这个proxy是代理后的对象而不是原始对象。

Method method

核心业务。你为哪一个方法添加额外功能 ?租房?那method就是租房方法.

可以通过method.invoke(原始对象的实例, args) 调用它。

Object[] args

核心业务的参数。

返回值:

Object :核心业务的返回值。附加业务要与核心业务的返回值相同,今天你为房东代理,返回值为钱,明天你为宠物代理,返回的是动物…那么我们怎么知道核心业务要返回什么呢?刚才看到参数中的 Method代表核心业务,那我们是否可以执行它,获取它的返回值!本来核心业务就需要执行,我不仅执行了,还获取返回值返回,一举两得!

于是InvocationHandler可以这样写 :

public MyInvocationHandler implements InvocationHandler {
    private UserService = new UserServiceImpl();
    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable {
        System.out.println("打广告...");
        System.out.println("带客户看房...");
        System.out.println("签合同...");
        // 调用核心业务,返回值保留,以后返回。
        Object ret = method.invoke(userService, args);
       
        System.out.println("给房客提供后续业务..");
        return ret;
    }
}

(当然用匿名内部类也可以,下面的完整编码为了减少代码量就是用了匿名内部类)

Proxy.newProxyInstance(
    x, 
    x, 
    new MyInvocationHandler());

4.1.2 Class

在静态代理的学习中我们说过 :代理类和原始类要实现相同的接口。并且 Class 这个参数的参数名也是 interfaces,所以这个参数就可以是 userService.getClass().getInterfaces()。

userService.getClass().getInterfaces()
Proxy.newProxyInstance(
    x, 
    userService.getClass().getInterfaces();
    new MyInvocationHandler());

4.1.3 ClassLoader

学过反射应该知道这个东西,它叫类加载器,为什么需要类加载器?不管是核心业务类的读取还是动态代理类的创建,都需要用到类加载器,相比于上面的那个类,这个就很简单了。

我们可能不知道类加载器分为几种,也不知道类加载器如何单独创建,但是我们可以借助别人的。

// 用谁的都一样,只要不是JDK内置类的类加载器就行。
// 可以是
userService.getClass().getClassLoader();
Proxy.newProxyInstance(
    userService.getClass(), 
    userService.getClass().getInterfaces(),
    new MyInvocationHandler());

4.2 JDK动态代理编码

学习Proxy.newProxyInstance()的三个参数后,我们就可以完成JDK动态代理的代码编写了。

动态代理编程分为三步 :

  1. 创建原始对象
  2. 完成 InvocationHandler 代理
  3. 调用 Proxy.newProxyInstance
@Test
public void test() {
    //1. 创建原始对象
    UserService userService = new UserServiceImpl();
    //2. 匿名内部类创建 InvocationHandler
    InvocationHandler handler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("打广告...");
            System.out.println("带客户看房...");
            System.out.println("签合同...");
            // 调用核心业务,返回值保留,以后返回。
            Object ret = method.invoke(userService, args);

            System.out.println("给房客提供后续业务..");
            return ret;
        }
    };
	//3. 调用 Proxy.newProxyInstance
    UserService proxyInstance = (UserService) 
        Proxy.newProxyInstance(
        	userService.getClass().getClassLoader(),
        	userService.getClass().getInterfaces(),
        	new MyInvocationHandler()
    	)
    proxyInstance.rentHome();
}

4.3 JDK动态代理编码注意事项

刚才我们在完成InvocationHandler 类时这样写 :

静态代理、动态代理概念及使用_第5张图片

在完成另外两个参数时也是用了userService.getClass() 而没用使用 UserService.class来获取类实例,为什么呢?

静态代理、动态代理概念及使用_第6张图片

我们可以先试试能不能使用 :

静态代理、动态代理概念及使用_第7张图片

果不其然,报错!那么为什么呢?

对于为什么不能使用proxy直接传参,刚才在介绍时已经说了,invoke需要的是原始方法,proxy是代理后对象。

那么我们将proxy换成 userService行不行呢?

静态代理、动态代理概念及使用_第8张图片

可以看到依旧不行,那么问题一定在 UserService.classuserService.getClass() 的区别。

在学习反射我们学了获取Class实例的方法有 .class、。getClass(),也学了他俩的区别 :

.class是编译时Class实例,它不受多态影响。

.getClass是运行时Class实例,说白了就是多态实例。

大白话:

对于:
UserService userService = new UserServiceImpl();

UserService.class获取的是 UserService 的Class实例。

userService.getClass() 获取的是 UserServiceImpl 的Class实例

我们可以进行验证 :

UserService userService1 = new UserServiceImpl();
UserServiceImpl userService2 = new UserServiceImpl();
System.out.println(userService1.getClass() == UserService.class);
System.out.println(userService2.getClass() == UserService.class);
System.out.println(userService1.getClass() == userService2.getClass());
// 无奖竞答:这个答案是什么?

UserService.class 获取的是 UserService 的Class

userService1 获取的是 UserServiceImpl 的Class

userService2 获取的是 UserServiceImpl 的Class

所以答案是 false、false、true。

所以在完成动态代理编码时一定要注意使用 原始对象的Class实例,而不是类的Class实例。

4.4 Cglib动态代理

学习JDK动态代理后,Cglib就容易一点了。

JDK动态代理和Cglib动态代理的区别,先别说底层原理,就我们“肉眼可见”可见的最大区别,就是 :

JDK动态代理基于接口;Cglib动态代理基于继承

Cglib动态代理需要代理类继承原始类

Cglib提供了一个类 :Enhancer,这个类有一个create()方法生成动态代理类。

Enhancer不是接口,所以直接new Enhancer之后将它的成员变量赋值,例如要指定父类是谁、类加载器用啥样的…

所以在调用create()方法之前,我们需要指定几个参数 :

1、类加载器 setClassLoader();

2、父类Class实例 setSuperclass();

3、MethodInterceptor 实现类 setCallback();

前两个类已经不用讲了,现在就是一个新的类 :MethodInterceptor 接口。

(它与Springaop中的MethodInterceptor可不是同一个包下的啊,不要搞混)

接下来学习一下它。

4.4.1 MethodInterceptor

public interface MethodInterceptor extends Callback {
	Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
        throws Throwable;
}

参数 :

(它跟InvocationHandler有三个一样的参数,就不再赘述,最后一个参数用不到,提一下。)

Object o

代理后的对象。

Method method

核心业务。

Object[] args

核心业务的参数。

MethodProxy methodProxy

生成的代理类对方法的代理引用

返回值:

Object

这个不用说了吧,跟上面JDK动态代理InvocationHandler.invoke()的返回值一样,都代表核心业务的返回值。

于是 MethodInterceptor可以这样写 :

public MyMethodInterceptor implements MethodInterceptor {
    private UserService = new UserServiceImpl();
    Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) 
        throws Throwable {
        System.out.println("打广告...");
        System.out.println("带客户看房...");
        System.out.println("签合同...");
        // 调用核心业务,返回值保留,以后返回。
        Object ret = method.invoke(userService, args);
       
        System.out.println("给房客提供后续业务..");
        return ret;
    }
}

(当然用匿名内部类也可以,下面的完整编码为了减少代码量就是用了匿名内部类)

4.5 Cglib动态代理编码

原始对象为父,代理类为子。

// 原始对象
public class UserServiceImpl {
    public void rendHome() {
        System.out.println("收钱...");
    }
}

完整代码如下 :

@Test
public void testCglib() {
    UserServiceImpl userService = new UserServiceImpl();
    MethodInterceptor methodInterceptor = new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("打广告...");
            System.out.println("带客户看房...");
            System.out.println("签合同...");
            // 调用核心业务,返回值保留,以后返回。
            Object ret = method.invoke(userService, args);

            System.out.println("给房客提供后续业务..");
            return ret;
        }
    };
    Enhancer enhancer = new Enhancer();
    // 设置三个属性: 类加载器、父类Class实例、MethodInterceptor实例
    enhancer.setClassLoader(userService.getClass().getClassLoader());
    enhancer.setSuperclass(userService.getClass());
    enhancer.setCallback(methodInterceptor);
    // 获取代理类
    UserServiceImpl userServiceProxy = (UserServiceImpl) enhancer.create();
    userServiceProxy.rentHome();
}

5. 对于两种方式的总结

我画了一张流程图,希望对大家有帮助 :

静态代理、动态代理概念及使用_第9张图片

6. JDK动态代理与Cglib动态代理的区别

1、使用技术不同

  • JDK动态代理原理是拦截器+反射机制,将原始类拦下进行包装。

  • CGLIB动态代理原理是动态字节码,通过修改字节码生成子类。

2、受用对象不同

  • JDK动态代理是针对接口的代理技术
  • CGLIB动态代理针对继承的代理技术

当有接口时使用JDK动态代理,没有接口只能继承时,使用CGLIB动态代理。

不写了,累了,种地去了

你可能感兴趣的:(spring,代理模式,java,开发语言)