【代理】大家都知道,特别是在spring中aop、spring中的事务、spring解析注解@Configuration,以及最原始的解析spring.xml的配置,这些都是使用代理来进行实现的,所以今天进行总结下代理。

  开始之前,我们需要解决一下3个问题:

    1.为什么需要使用代理?

    2.jdk代理怎么玩?

    3.cglib代理怎么玩?

  案例剖析:

  1.创建一个接口。有3个方法。

public interface IService {

    void m1();

    void m2();

    void m3();
}

  2.Aservice实现类

public class Aservice implements IService {

    @Override
public void m1() {
System.out.println("我是ServiceA中的m1方法!");
} @Override
public void m2() {
System.out.println("我是ServiceA中的m2方法!");
} @Override
public void m3() {
System.out.println("我是ServiceA中的m3方法!");
}
}

  3.Bservice实现类

public class Bservice implements IService {
@Override
public void m1() {
System.out.println("我是ServiceB中的m1方法!");
} @Override
public void m2() {
System.out.println("我是ServiceB中的m2方法!");
} @Override
public void m3() {
System.out.println("我是ServiceB中的m3方法!");
}
}

  4.测试类

public class ProxyTest {

    @Test
public void test() {
IService serviceA = new Aservice();
IService serviceB = new Bservice(); serviceA.m1();
serviceA.m2();
serviceA.m3(); serviceB.m1();
serviceB.m2();
serviceB.m3();
}
}

  5.输出结果如下:

  我是ServiceA中的m1方法!
  我是ServiceA中的m2方法!
  我是ServiceA中的m3方法!
  我是ServiceB中的m1方法!
  我是ServiceB中的m2方法!
  我是ServiceB中的m3方法!

  

比较好的方式就是:我们可以使用Iservice接口创建一个代理类,通过这个代理类来间接的访问Iservice接口的实现类,在代理类中做接口耗时以及发送到监控的代码:做法如下:

  

public class ServiceProxy implements IService {

    /**
* 目标对象,被代理的对象
*/
private IService target; @Override
public void m1() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
} @Override
public void m2() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m2()方法耗时(纳秒):" + (endTime - starTime));
} @Override
public void m3() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m3()方法耗时(纳秒):" + (endTime - starTime));
}
}

ServiceProxy是Iservice的接口的代理类,target是被代理的对象,即实际需要访问的对象,也实现了Iservice接口,并且3个方法进行统计耗时的代码,当我们需要进行访问Iservice的其他的实现类的时候,可以使用ServiceProxy来进行简介的访问,用法如下:

  


@Test
public void test2() {
IService serviceA = new ServiceProxy(new Aservice());
IService serviceB = new ServiceProxy(new Bservice()); serviceA.m1();
serviceA.m2();
serviceA.m3(); serviceB.m1();
serviceB.m2();
serviceB.m3();
}

  输出结果如下:

  我是ServiceA中的m1方法!
  class com.hg.代理.Aservice.m1()方法耗时(纳秒):86300
  我是ServiceA中的m1方法!
  class com.hg.代理.Aservice.m2()方法耗时(纳秒):37900
  我是ServiceA中的m1方法!
  class com.hg.代理.Aservice.m3()方法耗时(纳秒):37000
  我是ServiceB中的m1方法!
  class com.hg.代理.Bservice.m1()方法耗时(纳秒):51500
  我是ServiceB中的m1方法!
  class com.hg.代理.Bservice.m2()方法耗时(纳秒):36100
  我是ServiceB中的m1方法!
  class com.hg.代理.Bservice.m3()方法耗时(纳秒):38900

 我们可以看到并没有去修改ServiceA和ServiceB的方法,只是给Iservice接口创建了一个代理类,通过代理类去访问目标对象,需要添加一个公共的方法都放在了代理中,当领导有其他需求的时候只需要修改ServiceProxy的代码,方便系统扩展和测试。

 如果现在我们需要给系统的每一个接口都加上统计耗时的功能,如果按照上面的方式,那么每一接口都要创建代理类,此时代码量和工作量巨大,我们强烈的需要一个通用的代理类对象。

Jdk代理详解:

  jdk中自带的代理类  

    java.lang.reflect.Proxy

    java.lang.reflect.InvocationHandler

   第一种方式:使用步骤就是:

1.调用Proxy.getProxyClass方法获取代理类的Class对象

2.使用InvocationHandler接口创建代理类的处理器

3.通过代理类和InvocationHandler创建代理对象

4.上面已经创建好代理对象了,接着我们就可以使用代理对象了

@Test
public void test3() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取接口对应的代理类
Class<?> proxyClass = Proxy.getProxyClass(this.getClass().getClassLoader(), IService.class);
// 2. 创建代理类的处理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName());
return null;
}
}; // 3. 创建代理实例
IService proxyService = (IService) proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler); // 4. 调用代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();
}

  第二种方式:步骤如下

1.使用InvocationHandler接口创建代理类的处理器

2.使用Proxy类的静态方法newProxyInstance直接创建代理对象

3.使用代理对象

代码如下:

  

@Test
public void test4() {
//1.创建代理类的处理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是InvocationHandler,被调用的方法是:" +
method.getName());
return null;
}
}; // 2. 创建代理实例
IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
// 3. 调用代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();
}

这两种效果一样的。

案例:任意接口中的方法耗时统计

  

public class CostTimeInvocationHandler implements InvocationHandler {

    /**
* 目标类对象
*/
private Object target; public CostTimeInvocationHandler(Object target) {
this.target = target;
} /**
* 执行方法
*
* @param proxy proxy
* @param method method
* @param args args
* @return Object
* @throws Throwable e
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starTime = System.nanoTime();
Object result = method.invoke(this.target, args);
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
return result;
} /**
* 用来创建targetInterface接口的代理对象
*
* @param target 需要被代理的对象
* @param targetInterface 被代理的接口
* @param <T>
* @return
*/
public static <T> T createProxy(Object target, Class<T> targetInterface) {
if (!targetInterface.isInterface()) {
throw new IllegalStateException("targetInterface必须是接口类型!");
} else if (!targetInterface.isAssignableFrom(target.getClass())) {
throw new IllegalStateException("target必须是targetInterface接口的实现类!");
}
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
}
}

  测试代码:

  

@Test
public void test5() { IService serviceA = CostTimeInvocationHandler.createProxy(new Aservice(), IService.class);
IService serviceB = CostTimeInvocationHandler.createProxy(new Bservice(), IService.class); serviceA.m1();
serviceA.m2();
serviceA.m3();
serviceB.m1();
serviceB.m2();
serviceB.m3();
}

我是ServiceA中的m1方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):367500
我是ServiceA中的m2方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):34800
我是ServiceA中的m3方法!
class com.hg.代理.Aservice.m1()方法耗时(纳秒):28200
我是ServiceB中的m1方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):49500
我是ServiceB中的m2方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):26700
我是ServiceB中的m3方法!
class com.hg.代理.Bservice.m1()方法耗时(纳秒):23500

可以看到正常运行。当我们需要创建一个新的接口的时候不需要新建一个代理类,只需要使用CostTimeInvocationHandler.createProxy 去创建一个新的代理对象就行了,方便了很多。

Proxy使用的注意事项:

  1.jdk代理只能给接口生成代理类,如果想给某一个类生成代理对象那么就可以考虑使用cglib代理

  2.Proxy类中提供的几个常用的静态方法需要掌握

  3.通过Proxy创建代理对象,当调用代理对象任意方法的时候,会被invocationHandler接口中的invoke方法进行处理,接口是关键。

cglib代理:

   在Java中,cglib是一个强大的代码生成库,它可以用来生成Java类的子类,同时能够拦截被代理类的方法调用并添加额外的功能。

  在cglib中,代理生成的方式分为两种:基于接口的动态代理和基于类的动态代理,其中基于类的动态代理是借助于ASM框架实现的,下面我们来详细了解一下cglib的基于类的动态代理原理。

  cglib基于类的动态代理是通过继承被代理类来实现的,它的代理机制可以分为两步:

    1.生成代理对象的子类: 在这一步中,cglib会在内存中动态生成一个被代理类的子类,同时该子类会重写被代理类中的所有非final方法,并在方法前后插入增强逻辑。

      这里需要注意的是,为了避免出现循环调用,cglib还会将被代理类的非private方法和代理类中重写的方法区分开来,并且调用它们的时候使用不同的方法索引。

    2.代理对象实例化: 在这一步中,cglib会创建代理对象的子类实例,并将被代理对象的引用传递给代理类,从而通过代理类调用被代理类中的方法时,就会自动执行代理类中添加的增强逻辑。

总而言之,cglib生成代理子类的过程中,就是使用ASM框架生成代理类的字节码,并通过字节码重写非final方法的实现,从而添加额外的逻辑增强,完成了对代理对象行为的扩展。与其他代理框架相比,cglib代理的效率更高,并且可以代理没有实现接口的类。同时,由于cglib生成代理类强制覆盖被代理类的所有非final方法,因此cglib代理的约束条件更严格,需要被代理类存在无参构造器才能生成代理子类,这是值得注意的。

CGLIB案例一:

  1、定义一个普通的类

public class Service1 {
public void m1() {
System.out.println("我是m1方法");
} public void m2() {
System.out.println("我是m2方法");
}
}

  2、使用代理

@Test
public void test() {
//使用Enhancer来给某个类创建代理类,步骤
//1.创建Enhancer对象
Enhancer enhancer = new Enhancer();
//2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
enhancer.setSuperclass(Service1.class);
/*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
enhancer.setCallback(new MethodInterceptor() {
/**
* 代理对象方法拦截器
* @param o 代理对象
* @param method 被代理的类的方法,即Service1中的方法
* @param objects 调用方法传递的参数
* @param methodProxy 方法代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法:" + method);
//可以调用MethodProxy的invokeSuper调用被代理类的方法
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}); //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
Service1 proxy = (Service1) enhancer.create(); //5.调用代理对象的方法
proxy.m1();
proxy.m2(); }

案例2:

  1、创建普通类对象,但是方法m1中进行调用了m2();

public class Service2 {
public void m1() {
System.out.println("我是m1方法");
this.m2(); //@1
} public void m2() {
System.out.println("我是m2方法");
}
}

  2、

@Test
public void test2() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service2.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法:" + method);
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
});
Service2 proxy = (Service2) enhancer.create();
proxy.m1(); //@1
}

  输出结果:

调用方法:public void com.hg.代理.cglib.Service2.m1()
我是m1方法
调用方法:public void com.hg.代理.cglib.Service2.m2()
我是m2方法

只是调动了m1()方法,但是出现了2次方法调用。

  案例三:拦截方法并且返回固定的值:

 我们可以使用FixedValue接口。具体做法如下:

 1、创建普通类

public class Service3 {
public String m1() {
System.out.println("我是m1方法");
return "hello:m1";
} public String m2() {
System.out.println("我是m2方法");
return "hello:m2";
}
}

2、测试类,可以看到设置callback的时候使用函数式接口,进行返回固定值。

@Test
public void test3() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service3.class);
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "固定值";
}
});
Service3 proxy = (Service3) enhancer.create();
System.out.println(proxy.m1());//@1
System.out.println(proxy.m2()); //@2
System.out.println(proxy.toString());//@3
}

输出结果是:

固定值
固定值
固定值

  案例四:直接放行不做任何操作:这个是搞笑的吧,直接放行?

  原理是使用callback下的一个子接口,NoOp,将他当做callback的时候,被调用的直接放行了,像没有做任何代理一样,直接进入目标类的方法了。

@Test
public void test6() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service3.class);
enhancer.setCallback(NoOp.INSTANCE);
Service3 proxy = (Service3) enhancer.create();
System.out.println(proxy.m1());
System.out.println(proxy.m2());
}

  输出结果是:

我是m1方法
hello:m1
我是m2方法
hello:m2

  案例五:不同的方法使用不同的拦截器(callbackFilter)

  1.创建类

  

public class Service4 {

    public void insert1() {
System.out.println("我是insert1");
} public void insert2() {
System.out.println("我是insert2");
} public String get1() {
System.out.println("我是get1");
return "get1";
} public String get2() {
System.out.println("我是get2");
return "get2";
}
}

  2.我的需求是给insert开头的方法统计耗时,以get开头的方法返回固定的字符串。

  

@Test
public void test4() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service4.class);
Callback[] callbacks = {
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[]
objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime -
starTime));
return result;
}
},
new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "固定值";
}
}
};
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
return 0;
}
});
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
String methodName = method.getName();
return methodName.startsWith("insert") ? 0 : 1;
}
});
Service4 proxy = (Service4) enhancer.create();
System.out.println("---------------");
proxy.insert1();
System.out.println("---------------");
proxy.insert2();
System.out.println("---------------");
System.out.println(proxy.get1());
System.out.println("---------------");
System.out.println(proxy.get2());
}

执行结果:

  ---------------
我是insert1
public void com.hg.代理.cglib.Service4.insert1(),耗时(纳秒):10729500
---------------
我是insert2
public void com.hg.代理.cglib.Service4.insert2(),耗时(纳秒):54600
---------------
固定值
---------------
固定值

可以对例子五进行一个优化:使用callbackHelper

  

@Test
public void test5() {
Enhancer enhancer = new Enhancer();
//创建2个Callback
Callback costTimeCallback = (MethodInterceptor) (Object o, Method method,
Object[] objects, MethodProxy methodProxy) -> {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return result;
};
//下面这个用来拦截所有get开头的方法,返回固定值的
Callback fixdValueCallback = (FixedValue) () -> "固定值";
CallbackHelper callbackHelper = new CallbackHelper(Service4.class, null) {
@Override
protected Object getCallback(Method method) {
return method.getName().startsWith("insert") ? costTimeCallback :
fixdValueCallback;
}
};
enhancer.setSuperclass(Service4.class);
//调用enhancer的setCallbacks传递Callback数组
enhancer.setCallbacks(callbackHelper.getCallbacks());
/**
* 设置CallbackFilter,用来判断某个方法具体走哪个Callback
运行输出:
输出效果和案例4一模一样的,上面重点在于 CallbackHelper ,里面做了一些封装,有兴趣的可
以去看一下源码,比较简单。
*/
enhancer.setCallbackFilter(callbackHelper);
Service4 proxy = (Service4) enhancer.create();
System.out.println("---------------");
proxy.insert1();
System.out.println("---------------");
proxy.insert2();
System.out.println("---------------");
System.out.println(proxy.get1());
System.out.println("---------------");
System.out.println(proxy.get2());
}

执行结果:

---------------
我是insert1
public void com.hg.代理.cglib.Service4.insert1(),耗时(纳秒):13787200
---------------
我是insert2
public void com.hg.代理.cglib.Service4.insert2(),耗时(纳秒):64900
---------------
固定值
---------------
固定值

例子六:实现通用的统计任意类方法耗时的代理类

public class CostTimeProxy implements MethodInterceptor {

    //目标对象
private Object target; public CostTimeProxy(Object target) {
this.target = target;
} @Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
//调用被代理对象(即target)的方法,获取结果
Object result = method.invoke(target, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return result;
} /**
* 创建任意类的代理对象
*
* @param target
* @param <T>
* @return
*/
public static <T> T createProxy(T target) {
CostTimeProxy costTimeProxy = new CostTimeProxy(target);
Enhancer enhancer = new Enhancer();
enhancer.setCallback(costTimeProxy);
enhancer.setSuperclass(target.getClass());
return (T) enhancer.create();
}
}
@Test
public void test7() {
//创建Service1代理
Service1 service1 = CostTimeProxy.createProxy(new Service1());
service1.m1();
//创建Service3代理
Service3 service3 = CostTimeProxy.createProxy(new Service3());
System.out.println(service3.m1());
}

执行结果:

我是m1方法
public void com.hg.代理.cglib.Service1.m1(),耗时(纳秒):86600
我是m1方法
public java.lang.String com.hg.代理.cglib.Service3.m1(),耗时(纳秒):77200
hello:m1

CGLIB和JDK代理的区别

代理详解(java代理和CGLIB动态代理)的更多相关文章

  1. java的静态代理、jdk动态代理和cglib动态代理

    Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...

  2. jdk动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)

    代理模式是一种很常见的模式,本文主要分析cglib动态代理的过程 1. 举例 使用cglib代理需要引入两个包,maven的话包引入如下 <!-- https://mvnrepository.c ...

  3. JDK动态代理和CGLib动态代理简单演示

    JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...

  4. 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  5. Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。

    借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...

  6. Spring <tx:annotation-driven>注解 JDK动态代理和CGLIB动态代理 区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  7. 代理模式之静态代理,JDK动态代理和cglib动态代理

    代理模式,顾名思义,就是通过代理去完成某些功能.比如,你需要购买火车票,不想跑那么远到火车站售票窗口买,可以去附近的火车票代售点买,或者到携程等第三方网站买.这个时候,我们就把火车站叫做目标对象或者委 ...

  8. Java代理:静态代理、JDK动态代理和CGLIB动态代理

    代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式.所谓的代理者是指一个类别可以作为其它东西的接口.代理者可以作任何东西的接口:网络连接.存储器中的大对象.文件或其它昂贵或无法复制 ...

  9. Spring 静态代理+JDK动态代理和CGLIB动态代理

    代理分为两种:静态代理 动态代理 静态代理:本质上会在硬盘上创建一个真正的物理类 动态代理:本质上是在内存中构建出一个类. 如果多个类需要进行方法增强,静态代理则需要创建多个物理类,占用磁盘空间.而动 ...

  10. JDK动态代理和CGLIB动态代理编码

    JDK动态代理[接口]: import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import jav ...

随机推荐

  1. [ORACLE]Oracle客户端SQLPlus安装与运用

    简述 sqlplus :oracle公司提供用户操作oracle数据库的工具. sqlplus是oracle原始数据操作的客户端,这种命令行的格式有着强大的逻辑性,如果经常使用会对数据库的理解加深很多 ...

  2. Java设计模式 —— 观察者模式

    16 观察者模式 16.1 观察者模式概述 Observer Pattern: 定义对象之间的依赖关系(一对多),当一个对象的状态发生改变时,其关联的依赖对象均收到通知并自动更新. 观察者模式又称:发 ...

  3. PHP开发者交流群

    PHP开发者交流群 欢迎大家加入学习讨论 QQ群(493834732)

  4. scikit-learn 中 Boston Housing 数据集问题解决方案

    scikit-learn 中 Boston Housing 数据集问题解决方案 在部分旧教程或教材中是 sklearn,现在[2023]已经变更为 scikit-learn 作用:开源机器学习库,支持 ...

  5. Vite-Admin后台管理系统|vite4+vue3+pinia前端后台框架实例

    基于vite4.x+vue3+pinia前端后台管理系统解决方案ViteAdmin. 前段时间分享了一篇vue3自研pc端UI组件库VEPlus.这次带来最新开发的基于vite4+vue3+pinia ...

  6. ABPvNext-微服务框架基础入门

    ABPvNext-微服务框架基础入门 本文使用的是ABPvNext商业版 最新稳定版本7.0.2为演示基础的,后续如果更新,会单独写一篇最新版本的,此文为零基础入门教程,后续相关代码会同步更新到git ...

  7. RESTful API 为何成为顶流 API 架构风格?

    作者孙毅,API7.ai 技术工程师,Apache APISIX Committer 万物互联的世界充满着各式各样的 API ,如何统筹规范 API 至关重要.RESTful API 是目前世界上最流 ...

  8. Python pip速度慢,更换源

    版权声明:本文为CSDN博主「cocoprince」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/Coco ...

  9. Kubuesphere部署Ruoyi(三):持久化存储配置

    按照如下教程配置NFS 先服务器: https://kubesphere.io/zh/docs/v3.3/reference/storage-system-installation/nfs-serve ...

  10. Qt第三方库QtAV--- ubuntu编译与运行

    Qt第三方库QtAV--- ubuntu编译与运行 今天又要接触这个,把一些错误或者不足的地方重新补充下!!!由于前面一段时间,项目中需要借助QtAV接口进行视频播放,特此记录下整个配置过程.整个代码 ...