什么是CGLIB,CGLIB使用简介

2018年08月20日 10:41:31 axiaositong 阅读数:348

什么是CGLIB

CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

为什么使用CGLIB

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。关于Java动态代理,可以参者这里Java动态代理分析

CGLIB组成结构

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解

例子

说了这么多,可能大家还是不知道CGLIB是干什么用的。下面我们将使用一个简单的例子来演示如何使用CGLIB对一个方法进行拦截。 
首先,我们需要在工程的POM文件中引入cglib的dependency,这里我们使用的是2.2.2版本

  1. <dependency>

  2. <groupId>cglib</groupId>

  3. <artifactId>cglib</artifactId>

  4. <version>2.2.2</version>

  5. </dependency>

依赖包下载后,我们就可以干活了,按照国际惯例,写个hello world

  1. public class SampleClass {

  2. public void test(){

  3. System.out.println("hello world");

  4. }

  5. public static void main(String[] args) {

  6. Enhancer enhancer = new Enhancer();

  7. enhancer.setSuperclass(SampleClass.class);

  8. enhancer.setCallback(new MethodInterceptor() {

  9. @Override

  10. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

  11. System.out.println("before method run...");

  12. Object result = proxy.invokeSuper(obj, args);

  13. System.out.println("after method run...");

  14. return result;

  15. }

  16. });

  17. SampleClass sample = (SampleClass) enhancer.create();

  18. sample.test();

  19. }

  20. }

在mian函数中,我们通过一个Enhancer和一个MethodInterceptor来实现对方法的拦截,运行程序后输出为:

  1. before method run...

  2. hello world

  3. after method run...

在上面的程序中,我们引入了Enhancer和MethodInterceptor,可能有些读者还不太了解。别急,我们后面将会一一进行介绍。就目前而言,一个使用CGLIB的小demo就完成了

常用的API

目前网络上对CGLIB的介绍资料比较少,造成对cglib的学习困难。这里我将对cglib中的常用类进行一个介绍。为了避免解释的不清楚,我将为每个类都配有一个demo,用来做进一步的说明。首先就从Enhancer开始吧。

Enhancer

Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多(如果对Proxy不懂,可以参考这里)。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。

  1. public class SampleClass {

  2. public String test(String input){

  3. return "hello world";

  4. }

  5. }

下面我们将以这个类作为主要的测试类,来测试调用各种方法

  1. @Test

  2. public void testFixedValue(){

  3. Enhancer enhancer = new Enhancer();

  4. enhancer.setSuperclass(SampleClass.class);

  5. enhancer.setCallback(new FixedValue() {

  6. @Override

  7. public Object loadObject() throws Exception {

  8. return "Hello cglib";

  9. }

  10. });

  11. SampleClass proxy = (SampleClass) enhancer.create();

  12. System.out.println(proxy.test(null)); //拦截test,输出Hello cglib

  13. System.out.println(proxy.toString());

  14. System.out.println(proxy.getClass());

  15. System.out.println(proxy.hashCode());

  16. }

程序的输出为:

  1. Hello cglib

  2. Hello cglib

  3. class com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7

  4. java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

  5. at com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7.hashCode(<generated>)

  6. ...

上述代码中,FixedValue用来对所有拦截的方法返回相同的值,从输出我们可以看出来,Enhancer对非final方法test()、toString()、hashCode()进行了拦截,没有对getClass进行拦截。由于hashCode()方法需要返回一个Number,但是我们返回的是一个String,这解释了上面的程序中为什么会抛出异常。

Enhancer.setSuperclass用来设置父类型,从toString方法可以看出,使用CGLIB生成的类为被代理类的一个子类,形如:SampleClass

EnhancerByCGLIBEnhancerByCGLIB

e3ea9b7

Enhancer.create(Object…)方法是用来创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。(虽然类的构造放法只是Java字节码层面的函数,但是Enhancer却不能对其进行操作。Enhancer同样不能操作static或者final类)。我们也可以先使用Enhancer.createClass()来创建字节码(.class),然后用字节码动态的生成增强后的对象。

可以使用一个InvocationHandler(如果对InvocationHandler不懂,可以参考这里)作为回调,使用invoke方法来替换直接访问类的方法,但是你必须注意死循环。因为invoke中调用的任何原代理类方法,均会重新代理到invoke方法中。

  1. public void testInvocationHandler() throws Exception{

  2. Enhancer enhancer = new Enhancer();

  3. enhancer.setSuperclass(SampleClass.class);

  4. enhancer.setCallback(new InvocationHandler() {

  5. @Override

  6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  7. if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){

  8. return "hello cglib";

  9. }else{

  10. throw new RuntimeException("Do not know what to do");

  11. }

  12. }

  13. });

  14. SampleClass proxy = (SampleClass) enhancer.create();

  15. Assert.assertEquals("hello cglib", proxy.test(null));

  16. Assert.assertNotEquals("Hello cglib", proxy.toString());

  17. }

为了避免这种死循环,我们可以使用MethodInterceptor,MethodInterceptor的例子在前面的hello world中已经介绍过了,这里就不浪费时间了。

有些时候我们可能只想对特定的方法进行拦截,对其他的方法直接放行,不做任何操作,使用Enhancer处理这种需求同样很简单,只需要一个CallbackFilter即可:

  1. @Test

  2. public void testCallbackFilter() throws Exception{

  3. Enhancer enhancer = new Enhancer();

  4. CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class, new Class[0]) {

  5. @Override

  6. protected Object getCallback(Method method) {

  7. if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){

  8. return new FixedValue() {

  9. @Override

  10. public Object loadObject() throws Exception {

  11. return "Hello cglib";

  12. }

  13. };

  14. }else{

  15. return NoOp.INSTANCE;

  16. }

  17. }

  18. };

  19. enhancer.setSuperclass(SampleClass.class);

  20. enhancer.setCallbackFilter(callbackHelper);

  21. enhancer.setCallbacks(callbackHelper.getCallbacks());

  22. SampleClass proxy = (SampleClass) enhancer.create();

  23. Assert.assertEquals("Hello cglib", proxy.test(null));

  24. Assert.assertNotEquals("Hello cglib",proxy.toString());

  25. System.out.println(proxy.hashCode());

  26. }

ImmutableBean

通过名字就可以知道,不可变的Bean。ImmutableBean允许创建一个原来对象的包装类,这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出IllegalStateException。但是我们可以通过直接操作底层对象来改变包装类对象。这有点类似于Guava中的不可变视图

为了对ImmutableBean进行测试,这里需要再引入一个bean

  1. public class SampleBean {

  2. private String value;

  3. public SampleBean() {

  4. }

  5. public SampleBean(String value) {

  6. this.value = value;

  7. }

  8. public String getValue() {

  9. return value;

  10. }

  11. public void setValue(String value) {

  12. this.value = value;

  13. }

  14. }

然后编写测试类如下:

  1. @Test(expected = IllegalStateException.class)

  2. public void testImmutableBean() throws Exception{

  3. SampleBean bean = new SampleBean();

  4. bean.setValue("Hello world");

  5. SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean); //创建不可变类

  6. Assert.assertEquals("Hello world",immutableBean.getValue());

  7. bean.setValue("Hello world, again"); //可以通过底层对象来进行修改

  8. Assert.assertEquals("Hello world, again", immutableBean.getValue());

  9. immutableBean.setValue("Hello cglib"); //直接修改将throw exception

  10. }

Bean generator

cglib提供的一个操作bean的工具,使用它能够在运行时动态的创建一个bean。

  1. @Test

  2. public void testBeanGenerator() throws Exception{

  3. BeanGenerator beanGenerator = new BeanGenerator();

  4. beanGenerator.addProperty("value",String.class);

  5. Object myBean = beanGenerator.create();

  6. Method setter = myBean.getClass().getMethod("setValue",String.class);

  7. setter.invoke(myBean,"Hello cglib");

  8. Method getter = myBean.getClass().getMethod("getValue");

  9. Assert.assertEquals("Hello cglib",getter.invoke(myBean));

  10. }

在上面的代码中,我们使用cglib动态的创建了一个和SampleBean相同的Bean对象,包含一个属性value以及getter、setter方法

Bean Copier

cglib提供的能够从一个bean复制到另一个bean中,而且其还提供了一个转换器,用来在转换的时候对bean的属性进行操作。

  1. public class OtherSampleBean {

  2. private String value;

  3. public String getValue() {

  4. return value;

  5. }

  6. public void setValue(String value) {

  7. this.value = value;

  8. }

  9. }

  10. @Test

  11. public void testBeanCopier() throws Exception{

  12. BeanCopier copier = BeanCopier.create(SampleBean.class, OtherSampleBean.class, false);//设置为true,则使用converter

  13. SampleBean myBean = new SampleBean();

  14. myBean.setValue("Hello cglib");

  15. OtherSampleBean otherBean = new OtherSampleBean();

  16. copier.copy(myBean, otherBean, null); //设置为true,则传入converter指明怎么进行转换

  17. assertEquals("Hello cglib", otherBean.getValue());

  18. }

BulkBean

相比于BeanCopier,BulkBean将copy的动作拆分为getPropertyValues和setPropertyValues两个方法,允许自定义处理属性

  1. @Test

  2. public void testBulkBean() throws Exception{

  3. BulkBean bulkBean = BulkBean.create(SampleBean.class,

  4. new String[]{"getValue"},

  5. new String[]{"setValue"},

  6. new Class[]{String.class});

  7. SampleBean bean = new SampleBean();

  8. bean.setValue("Hello world");

  9. Object[] propertyValues = bulkBean.getPropertyValues(bean);

  10. assertEquals(1, bulkBean.getPropertyValues(bean).length);

  11. assertEquals("Hello world", bulkBean.getPropertyValues(bean)[0]);

  12. bulkBean.setPropertyValues(bean,new Object[]{"Hello cglib"});

  13. assertEquals("Hello cglib", bean.getValue());

  14. }

使用注意: 
1. 避免每次进行BulkBean.create创建对象,一般将其声明为static的 
2. 应用场景:针对特定属性的get,set操作,一般适用通过xml配置注入和注出的属性,运行时才确定处理的Source,Target类,只需要关注属性名即可。

BeanMap

BeanMap类实现了Java Map,将一个bean对象中的所有属性转换为一个String-to-Obejct的Java Map

  1. @Test

  2. public void testBeanMap() throws Exception{

  3. BeanGenerator generator = new BeanGenerator();

  4. generator.addProperty("username",String.class);

  5. generator.addProperty("password",String.class);

  6. Object bean = generator.create();

  7. Method setUserName = bean.getClass().getMethod("setUsername", String.class);

  8. Method setPassword = bean.getClass().getMethod("setPassword", String.class);

  9. setUserName.invoke(bean, "admin");

  10. setPassword.invoke(bean,"password");

  11. BeanMap map = BeanMap.create(bean);

  12. Assert.assertEquals("admin", map.get("username"));

  13. Assert.assertEquals("password", map.get("password"));

  14. }

我们使用BeanGenerator生成了一个含有两个属性的Java Bean,对其进行赋值操作后,生成了一个BeanMap对象,通过获取值来进行验证

keyFactory

keyFactory类用来动态生成接口的实例,接口需要只包含一个newInstance方法,返回一个Object。keyFactory为构造出来的实例动态生成了Object.equals和Object.hashCode方法,能够确保相同的参数构造出的实例为单例的。

  1. public interface SampleKeyFactory {

  2. Object newInstance(String first, int second);

  3. }

我们首先构造一个满足条件的接口,然后进行测试

  1. @Test

  2. public void testKeyFactory() throws Exception{

  3. SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(SampleKeyFactory.class);

  4. Object key = keyFactory.newInstance("foo", 42);

  5. Object key1 = keyFactory.newInstance("foo", 42);

  6. Assert.assertEquals(key,key1);//测试参数相同,结果是否相等

  7. }

Mixin(混合)

Mixin能够让我们将多个对象整合到一个对象中去,前提是这些对象必须是接口的实现。可能这样说比较晦涩,以代码为例:

  1. public class MixinInterfaceTest {

  2. interface Interface1{

  3. String first();

  4. }

  5. interface Interface2{

  6. String second();

  7. }

  8. class Class1 implements Interface1{

  9. @Override

  10. public String first() {

  11. return "first";

  12. }

  13. }

  14. class Class2 implements Interface2{

  15. @Override

  16. public String second() {

  17. return "second";

  18. }

  19. }

  20. interface MixinInterface extends Interface1, Interface2{

  21. }

  22. @Test

  23. public void testMixin() throws Exception{

  24. Mixin mixin = Mixin.create(new Class[]{Interface1.class, Interface2.class,

  25. MixinInterface.class}, new Object[]{new Class1(),new Class2()});

  26. MixinInterface mixinDelegate = (MixinInterface) mixin;

  27. assertEquals("first", mixinDelegate.first());

  28. assertEquals("second", mixinDelegate.second());

  29. }

  30. }

Mixin类比较尴尬,因为他要求Minix的类(例如MixinInterface)实现一些接口。既然被Minix的类已经实现了相应的接口,那么我就直接可以通过纯Java的方式实现,没有必要使用Minix类。

String switcher

用来模拟一个String到int类型的Map类型。如果在Java7以后的版本中,类似一个switch语句。

  1. @Test

  2. public void testStringSwitcher() throws Exception{

  3. String[] strings = new String[]{"one", "two"};

  4. int[] values = new int[]{10,20};

  5. StringSwitcher stringSwitcher = StringSwitcher.create(strings,values,true);

  6. assertEquals(10, stringSwitcher.intValue("one"));

  7. assertEquals(20, stringSwitcher.intValue("two"));

  8. assertEquals(-1, stringSwitcher.intValue("three"));

  9. }

  • ace Maker

正如名字所言,Interface Maker用来创建一个新的Interface

  1. @Test

  2. public void testInterfaceMarker() throws Exception{

  3. Signature signature = new Signature("foo", Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE});

  4. InterfaceMaker interfaceMaker = new InterfaceMaker();

  5. interfaceMaker.add(signature, new Type[0]);

  6. Class iface = interfaceMaker.create();

  7. assertEquals(1, iface.getMethods().length);

  8. assertEquals("foo", iface.getMethods()[0].getName());

  9. assertEquals(double.class, iface.getMethods()[0].getReturnType());

  10. }

上述的Interface Maker创建的接口中只含有一个方法,签名为double foo(int)。Interface Maker与上面介绍的其他类不同,它依赖ASM中的Type类型。由于接口仅仅只用做在编译时期进行类型检查,因此在一个运行的应用中动态的创建接口没有什么作用。但是InterfaceMaker可以用来自动生成代码,为以后的开发做准备。

Method delegate

MethodDelegate主要用来对方法进行代理

  1. interface BeanDelegate{

  2. String getValueFromDelegate();

  3. }

  4. @Test

  5. public void testMethodDelegate() throws Exception{

  6. SampleBean bean = new SampleBean();

  7. bean.setValue("Hello cglib");

  8. BeanDelegate delegate = (BeanDelegate) MethodDelegate.create(bean,"getValue", BeanDelegate.class);

  9. assertEquals("Hello cglib", delegate.getValueFromDelegate());

  10. }

关于Method.create的参数说明: 
1. 第二个参数为即将被代理的方法 
2. 第一个参数必须是一个无参数构造的bean。因此MethodDelegate.create并不是你想象的那么有用 
3. 第三个参数为只含有一个方法的接口。当这个接口中的方法被调用的时候,将会调用第一个参数所指向bean的第二个参数方法

缺点: 
1. 为每一个代理类创建了一个新的类,这样可能会占用大量的永久代堆内存 
2. 你不能代理需要参数的方法 
3. 如果你定义的接口中的方法需要参数,那么代理将不会工作,并且也不会抛出异常;如果你的接口中方法需要其他的返回类型,那么将抛出IllegalArgumentException

MulticastDelegate

  1. 多重代理和方法代理差不多,都是将代理类方法的调用委托给被代理类。使用前提是需要一个接口,以及一个类实现了该接口
  2. 通过这种interface的继承关系,我们能够将接口上方法的调用分散给各个实现类上面去。
  3. 多重代理的缺点是接口只能含有一个方法,如果被代理的方法拥有返回值,那么调用代理类的返回值为最后一个添加的被代理类的方法返回值
  1. public interface DelegatationProvider {

  2. void setValue(String value);

  3. }

  4. public class SimpleMulticastBean implements DelegatationProvider {

  5. private String value;

  6. @Override

  7. public void setValue(String value) {

  8. this.value = value;

  9. }

  10. public String getValue() {

  11. return value;

  12. }

  13. }

  14. @Test

  15. public void testMulticastDelegate() throws Exception{

  16. MulticastDelegate multicastDelegate = MulticastDelegate.create(DelegatationProvider.class);

  17. SimpleMulticastBean first = new SimpleMulticastBean();

  18. SimpleMulticastBean second = new SimpleMulticastBean();

  19. multicastDelegate = multicastDelegate.add(first);

  20. multicastDelegate = multicastDelegate.add(second);

  21. DelegatationProvider provider = (DelegatationProvider) multicastDelegate;

  22. provider.setValue("Hello world");

  23. assertEquals("Hello world", first.getValue());

  24. assertEquals("Hello world", second.getValue());

  25. }

Constructor delegate

为了对构造函数进行代理,我们需要一个接口,这个接口只含有一个Object newInstance(…)方法,用来调用相应的构造函数

  1. interface SampleBeanConstructorDelegate{

  2. Object newInstance(String value);

  3. }

  4. /**

  5. * 对构造函数进行代理

  6. * @throws Exception

  7. */

  8. @Test

  9. public void testConstructorDelegate() throws Exception{

  10. SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(

  11. SampleBean.class, SampleBeanConstructorDelegate.class);

  12. SampleBean bean = (SampleBean) constructorDelegate.newInstance("Hello world");

  13. assertTrue(SampleBean.class.isAssignableFrom(bean.getClass()));

  14. System.out.println(bean.getValue());

  15. }

Parallel Sorter(并行排序器)

能够对多个数组同时进行排序,目前实现的算法有归并排序和快速排序

  1. @Test

  2. public void testParallelSorter() throws Exception{

  3. Integer[][] value = {

  4. {4, 3, 9, 0},

  5. {2, 1, 6, 0}

  6. };

  7. ParallelSorter.create(value).mergeSort(0);

  8. for(Integer[] row : value){

  9. int former = -1;

  10. for(int val : row){

  11. assertTrue(former < val);

  12. former = val;

  13. }

  14. }

  15. }

FastClass

顾明思义,FastClass就是对Class对象进行特定的处理,比如通过数组保存method引用,因此FastClass引出了一个index下标的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的获取method的方法。通过数组存储method,constructor等class信息,从而将原先的反射调用,转化为class.index的直接调用,从而体现所谓的FastClass。

  1. @Test

  2. public void testFastClass() throws Exception{

  3. FastClass fastClass = FastClass.create(SampleBean.class);

  4. FastMethod fastMethod = fastClass.getMethod("getValue",new Class[0]);

  5. SampleBean bean = new SampleBean();

  6. bean.setValue("Hello world");

  7. assertEquals("Hello world",fastMethod.invoke(bean, new Object[0]));

  8. }

注意

由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。

CGLIB和Java动态代理的区别

  1. Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
  2. Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

什么是CGLIB,CGLIB使用简介,cglib相关推荐

  1. Java设计模式-----Cglib动态代理(Cglib Proxy)

    接上文:4.2Java设计模式-----JDK动态代理(Dynamic Proxy) Cglib动态代理 百度百科:Cglib是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java ...

  2. 死磕cglib系列之一 cglib简介与callback解析

    简介 cglib是一套java动态代理实现框架,cglib被应用到spring app,hibernate等高级业务框架,spring事务在业务实现类未实现接口的情况下也会使用该技术. 实际上,cgl ...

  3. JVM插桩之四:Java动态代理机制的对比(JDK和CGLIB,Javassist,ASM)

    一.class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件, ...

  4. 动态代理proxy与CGLib的区别

    转载自 动态代理proxy与CGLib的区别 昨天被人问及动态代理与CGlib的区别,赶紧回顾一下: 什么是代理? 静态代理与动态代理 静态代理实例 JDK动态代理实例 CGLib 简介 CGLib ...

  5. 【java】简述CGLIB常用API

    1.概述 转载:简述CGLIB常用API 类似:[Spring]CGLIB动态代理 CGLIB,即Code Generation Library,是一个强大的.高性能的代码生成库.其被广泛应用于AOP ...

  6. Java设计模式(五)代理设计模式—静态代理—JDK动态代理—Cglib动态代理

    文章目录 什么是代理模式 代理模式应用场景 代理的分类 静态代理 什么是静态代理 深入解析静态代理 小结 动态代理 什么是动态代理 JDK动态代理 原理和实现方式 代码实现 优缺点 Cglib动态代理 ...

  7. 【java】CGLIB动态代理原理

    文章目录 1. 简介 2. 示例 3. 原理 4. JDK动态代理与CGLIB动态代理区别(面试常问) 1. 简介 CGLIB的全称是:Code Generation Library. CGLIB是一 ...

  8. CGLIB 动态代理用例及源码解析

    CGLIB 动态代理 参考链接:https://blog.csdn.net/yhl_jxy/article/details/80633194 参考链接:https://www.jianshu.com/ ...

  9. Proxy 代理模式 动态代理 CGLIB

    代理的基本概念 几个英文单词: proxy [ˈprɒksi] n. 代理服务器:代表权:代理人,代替物:委托书: invoke [ɪnˈvəʊk] vt. 乞灵,祈求:提出或授引-以支持或证明:召鬼 ...

最新文章

  1. xcode升级xcode9 1之后报错swift stdlib tool error
  2. 快速排序最好,最坏,平均复杂度分析
  3. 【转】托管函数的挂钩(完美版)
  4. Java继承多态经典案例分享
  5. Teradata Fastload 使用方法
  6. linux java启动脚本文件_不错的linux下通用的java程序启动脚本
  7. 四轮驱动移动机器人(SSMR)与两轮差速驱动机器人、car-like robot的对比分析
  8. 总结几个经典的java陷阱给大家。
  9. 【杂谈】我学习这么好,为什么找不到工作?
  10. 区块链 solidity io密集
  11. MapBar纯绿色桌面版:小M
  12. Code[VS]1159 最大全0子矩阵
  13. 微机原理课程设计-接口芯片编程记录
  14. 离散数学考点之度序列简单图化
  15. AI产品经理的前世今生
  16. spring 使用注解遇到的问题
  17. python实现批量图片格式转换
  18. @linux下tar解压失败a lone zero解决方法
  19. ISCSLP 2022 | AccentSpeech—从众包数据中学习口音来构建目标说话人的口音语音合成系统
  20. sinon.stub_JavaScript测试工具对决:Sinon.js vs testdouble.js

热门文章

  1. 不忘初心,方得始终——线性回归的python实现
  2. JVS低代码能力简介及功能清单
  3. 微信小程序基础知识3
  4. 如何获取不同年份的poi_抖音营销案例爆红抖音,万人争领,屈臣氏“魔盒”如何联动线上线下?...
  5. seata TCC模式
  6. 有哪些你看了以后大呼过瘾的数据分析书?
  7. Qt——“\r\n“回车换行符在Linguist里不奏效(无效果)?
  8. 井径测井原理、计算方法、主要应用、仪器刻度、质量控制
  9. 5万的oracle包含安装服务吗,windows 安装oracle 后,所有服务都是什么意思,需要开户吗?...
  10. scikit-learn决策树