代理模式


给某一个对象提供一个代理,并由代理对象控制对原对象的引用。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

在Java中代理模式从实现方式上可以分为两个类别:静态代理和动态代理

静态代理: 也就是我们学习设计模式之代理模式时常见的事例,具体不在赘述,参见:【Java设计模式-13代理模式】

动态代理: 在静态代理中,因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。那么如何解决这种问题呢?答案就是动态代理,下面会使用两种动态代理(jdk代理、cglib代理)分别实现同一个事例来体会一下动态代理的实现。

动态代理

动态代理具有以下特性:

1.代理对象不需要实现接口

2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象

jdk代理

只支持对接口的的实现,其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

Proxy类主要用来获取动态代理对象,InvocationHandler接口用来约束调用者实现。

JDK的动态代理用起来非常简单,但它有一个限制,就是使用动态代理的对象必须实现一个或多个接口

package com.lkf.parttern.proxy.dynamic;

/**
* 账户操作接口
*
* @author kaifeng
*/
public interface Account { /**
* 查询余额
*/
void queryAccountBalance(); /**
* 充值话费
*/
void updateAccountBalance();
}
package com.lkf.parttern.proxy.dynamic;

/**
* 账户操作实现
*
* @author kaifeng
*/
public class AccountImpl implements Account {
/**
* 查询余额
*/
@Override
public void queryAccountBalance() {
System.out.println("【AccountImpl::queryAccountBalance】-查询账户余额");
} /**
* 充值话费
*/
@Override
public void updateAccountBalance() {
System.out.println("【AccountImpl::updateAccountBalance】-话费充值");
}
}

代理对象实现接口InvocationHandler

package com.lkf.parttern.proxy.dynamic.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* 这里使用的是Jdk的动态代理,其必须实现接口,这也是jdk代理的缺陷,不过cglib代理会修补这个缺陷
*
* @author kaifeng
*/
public class JDKAccountProxyFactory implements InvocationHandler { private Object target; /**
* 代理方式实例化对象
*/
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
boolean objFlag = method.getDeclaringClass().getName().equals("java.lang.Object"); Object result = null;
if (!objFlag) {
System.out.println("【JDKAccountProxyFactory::invoke】-代理before");
}
//真实方法调用
result = method.invoke(this.target, args);
if (!objFlag) {
System.out.println("【JDKAccountProxyFactory::invoke】-代理after");
}
return result;
}
}
package com.lkf.parttern.proxy.dynamic.jdk;

import com.lkf.parttern.proxy.dynamic.Account;
import com.lkf.parttern.proxy.dynamic.AccountImpl; /**
* jdk动态代理测试
*/
public class JDKAccountProxyFactoryTest {
public static void main(String[] args) {
Account account = (Account) new JDKAccountProxyFactory().bind(new AccountImpl());
account.queryAccountBalance();
System.out.println("***************************");
account.updateAccountBalance();
}
} 【JDKAccountProxyFactory::invoke】-代理before
【AccountImpl::queryAccountBalance】-查询账户余额
【JDKAccountProxyFactory::invoke】-代理after
***************************
【JDKAccountProxyFactory::invoke】-代理before
【AccountImpl::updateAccountBalance】-话费充值
【JDKAccountProxyFactory::invoke】-代理after

CGLIB代理

对于上面说到JDK仅支持对实现接口的委托类进行代理的缺陷,CGLIB解决了这个问题,使其委托类也可是非接口实现类。

CGLIB内部使用到ASM,所以我们下面的例子需要引入cglib-3.2.5.jar

Cglib的原理是对指定的目标类动态生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类和final方法进行代理。

营业员实体类,没有实现接口的普通类

package com.lkf.parttern.proxy.dynamic;

import java.util.UUID;

/**
* 营业员
*
* @author kaifeng
*/
public class SalesPerson {
private String jobNum = String.valueOf(UUID.randomUUID()); public String getJobNum() {
return jobNum;
}
}

cglib动态代理实现了接口MethodInterceptor

package com.lkf.parttern.proxy.dynamic.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /**
* cglib动态生成代理对象
*
* @author kaifeng
*/
public class CglibAccountProxyFactory implements MethodInterceptor { private Object target; /**
* 获取对象实例
*/
public Object getInstance(Object target) {
this.target = target;
return Enhancer.create(this.target.getClass(), this);
} /**
* 方法拦截
* <p>
* 一般使用proxy.invokeSuper(obj,args)方法
* proxy.invoke(obj,args),这是执行生成子类的方法。
* 如果传入的obj就是子类的话,会发生内存溢出,因为子类的方法不进入intercept方法,而这个时候又去调用子类的方法,两个方法直接循环调用了
* </p>
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 排除Object类中的toString等方法
System.out.println(method.getDeclaringClass().getName());
boolean objFlag = method.getDeclaringClass().getName().equals("java.lang.Object");
if (!objFlag) {
System.out.println("【CglibAccountProxyFactory::intercept】-代理before");
}
Object result = null;
result = methodProxy.invokeSuper(obj, args);
if (!objFlag) {
System.out.println("【CglibAccountProxyFactory::intercept】-代理after");
}
return result;
} }

cglib动态代理测试

package com.lkf.parttern.proxy.dynamic.cglib;

import com.lkf.parttern.proxy.dynamic.Account;
import com.lkf.parttern.proxy.dynamic.AccountImpl;
import com.lkf.parttern.proxy.dynamic.SalesPerson; /**
* cglib动态代理测试
*
* @author kaifeng
*/
public class CglibAccountProxyFactoryTest {
public static void main(String[] args) {
// 下面是用cglib的代理
// 1.支持实现接口的类
Account account = (Account) new CglibAccountProxyFactory().getInstance(new AccountImpl());
account.queryAccountBalance(); // 2.支持未实现接口的类
SalesPerson salesPerson = (SalesPerson) new CglibAccountProxyFactory().getInstance(new SalesPerson());
System.out.println("我的工号是:" + salesPerson.getJobNum());
}
} com.lkf.parttern.proxy.dynamic.AccountImpl
【CglibAccountProxyFactory::intercept】-代理before
【AccountImpl::queryAccountBalance】-查询账户余额
【CglibAccountProxyFactory::intercept】-代理after
com.lkf.parttern.proxy.dynamic.SalesPerson
【CglibAccountProxyFactory::intercept】-代理before
【CglibAccountProxyFactory::intercept】-代理after
我的工号是:5cee95e6-9032-463c-8a4a-139d1f882b4f

总结

  • Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

在Spring的AOP编程中:

如果加入容器的目标对象有实现接口,会使用JDK代理;

如果目标对象没有实现接口,会使用Cglib代理

Java中jdk代理和cglib代理的更多相关文章

  1. spring的AOP动态代理--JDK代理和CGLIB代理区分以及注意事项

    大家都知道AOP使用了代理模式,本文主要介绍两个代理模式怎么设置以及区别,对原文一些内容进行了引用后加入了自己的理解和更深入的阐述:   一.JDK代理和CGLIB代理的底层实现区别* JDK代理只能 ...

  2. 总结两种动态代理jdk代理和cglib代理

    动态代理 上篇文章讲了什么是代理模式,为什么用代理模式,从静态代理过渡到动态代理. 这里再简单总结一下 什么是代理模式,给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原 ...

  3. jdk代理和cglib代理源代码之我见

    以前值是读过一遍jdk和cglib的代理,时间长了,都忘记入口在哪里了,值是记得其中的一些重点了,今天写一篇博客,当作是笔记.和以前一样,关键代码,我会用红色标记出来. 首先,先列出我的jdk代理对象 ...

  4. jdk代理和cglib代理

    1.jdk静态代理(静态代理和动态代理) 本质:在内存中构建出接口的实现类. 缺陷:只能对实现接口的类实现动态代理, 使用cglib可以对没有实现接口的类进行动态代理. 2.cglib动态代理     ...

  5. Spring中AOP的两种代理方式(Java动态代理和CGLIB代理)

    第一种代理即Java的动态代理方式上一篇已经分析,在这里不再介绍,现在我们先来了解下GCLIB代理是什么?它又是怎样实现的?和Java动态代理有什么区别? cglib(Code Generation ...

  6. 设计模式---JDK动态代理和CGLIB代理

    Cglig代理设计模式 /*测试类*/ package cglibProxy; import org.junit.Test; public class TestCglib { @Test public ...

  7. JDK动态代理和 CGLIB 代理

    JDK动态代理和 CGLIB 代理 JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期期间创建一个接口的实现类来完成对目标对象的代理. 代码示例 接口 public interface ...

  8. Java动态代理和CGLib代理

    本文参考 在上一篇"Netty + Spring + ZooKeeper搭建轻量级RPC框架"文章中涉及到了Java动态代理和CGLib代理,在这篇文章中对这两种代理方式做详解 下 ...

  9. SpringAOP-JDK 动态代理和 CGLIB 代理

    在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类. 1.JDK 动态代理 那么接 ...

随机推荐

  1. 作业13:Map相关知识点(一)

    一 Map相关类图 二 Map接口 1 Map接口中的方法 jdk 方法名 简单描述 put(K,V):V 添加value,当Key对应无值,返回null;有值则返回上一个值.(覆盖式,可以反复覆盖前 ...

  2. Docker搭建Gitlab代码管理平台

    一.Gitlab的安装 宿主机环境: CentOS 7 docker docker-compose 1.查找镜像 docker search gitlab 2.拉取镜像 docker pull git ...

  3. WinPE基础知识之重定位表

    typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; // (重要)需要重定位的位置,这是一个RVA DWORD SizeOfBl ...

  4. Python 针对Excel操作

    1.python 读取Excel # -*- coding: utf-8 -*- import xlrd import os,sys reload(sys) sys.setdefaultencodin ...

  5. Flutter——Column组件(垂直布局组件)

    Column组件的常用属性 属性 说明 mainAxisAlignment 主轴的排序方式 crossAxisAlignment 次轴的排序方式 children 组件子元素 import 'pack ...

  6. 03.Zabbix应用服务监控

    一.Zabbix监控Nginx 1.1 实验环境 服务器系统 角色 IP CentOS 7.4 x86_64 Zabbix-Server 192.168.90.10 CentOS 7.4 x86_64 ...

  7. git回退到历史版本

    问题描述 在开发的过程中,想要修改一个参数的命名.然后修改各种地方,并且push上码云的远程仓库.然后突然发现还要改很多地方,突然后悔不想改动了.那该怎么办呢? 处理步骤 回退本地的git版本 将本地 ...

  8. PHP面试题--基础

    1.PHP语言的一大优势是跨平台,什么是跨平台?一.PHP基础: PHP的运行环境最优搭配为Apache+MySQL+PHP,此运行环境可以在不同操作系统(例如windows.Linux等)上配置,不 ...

  9. [CodeForces 160A] Twins

    题目链接:http://codeforces.com/problemset/problem/160/A 注意排序是从大到小排,不要上来就sort导致从小到大排,细节水题. AC代码: #include ...

  10. idou老师教你学Istio 04:Istio性能及扩展性介绍

    Istio的性能问题一直是国内外相关厂商关注的重点,Istio对于数据面应用请求时延的影响更是备受关注,而以现在Istio官方与相关厂商的性能测试结果来看,四位数的qps显然远远不能满足应用于生产的要 ...