代理模式详细学习笔记_空槐树的博客-程序员秘密

技术标签: study  java  设计模式  

代理模式

代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。属于结构型模式。
在客户和目标对象间起中介作用,保护、增强目标对象,解耦,易扩展;但增加了系统复杂度,延长了处理时间。

在面向对象的编程之中,如果我们想要代理类和目标类可以实现相同的功能,有两种方式:

  1. 一个比较直观的方式,就是定义一个功能接口,然后让代理类和目标类来实现这个接口。
  2. 还有比较隐晦的方式,就是通过继承。因为如果代理类继承自目标类,这样代理类则拥有了目标类的功能,代理类还可以通过重写目标类中的方法,来实现多态。

一.JDK静态代理

方式:目标类和代理类同时实现一个目标接口,在代理类对象中持有一个目标接口类型的对象的引用,在代理类的方法中调用该目标接口的方法。

目标接口 Persion.java

public interface Person {
    void findHouse();  
}

目标类 Myself.java

public class Myself implements Person {
    public void findHouse() {
        System.out.println("想租房");
    }
}

代理类 Friend.java

public class Friend implements Person {
    private Person person;
    public Friend(Person person) {
        this.person = person;
    }
    public void findHouse() {
        System.out.println("我是朋友");
        this.person.findHouse();
        System.out.println("帮忙找到了");
    }
    
}

测试类 FriendProxyTest.java

public class FriendProxyTest {
    public static void main(String[] args) {
        Friend friend = new Friend(new Myself());
        friend.findHouse();
    }
}

缺点:采用硬编码的方式,一个代理类只能为一个目标类服务,造成系统臃肿,且不符合开闭原则。

二.JDK动态代理

机制:利用反射在运行时创建代理类。

1. 目标类实现一个目标接口(必需)。

目标类 租客 Tenant.java

public class Tenant implements Person {
    public void findHouse() {
        System.out.println("租房要求");
    }
}

2. 实现调用处理器接口InvocationHandler,声明目标接口类型的成员变量,重写invoke方法,在invoke方法中,反射调用成员变量的方法。

调用处理器类 房屋中介 JDKHouseAgent.java

public class JDKHouseAgent implements InvocationHandler {

    private Person target;

    public JDKHouseAgent (Person person) {
        this.target = person;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object obj = method.invoke(this.target, args);
        after();
        return obj;
    }

    private void before() {
        System.out.println("我是中介");
    }

    private void after() {
        System.out.println("这是找到的房子,中介费X元");
    }
}

3. 运行时,调用Proxy.newProxyInstance方法,指定目标类、目标接口数组、调用处理器,动态地创建一个代理类$proxy0的对象,它继承了Proxy类,实现了目标接口。

在调用$Proxy0代理类对象中的每一个方法时,在代码内部,都是直接调用了Proxy类持有的InvocationHandler成员的invoke方法,(此处InvocationHandler成员实际被指定为JDKHouseAgent类型),而invoke方法被我们在JDKHouseAgent类中重写了,它会根据传入的方法参数,反射调用目标类的目标方法。具体逻辑参见更下方的$Proxy0.class。?

测试类 JdkProxyTest.java

public class JdkProxyTest {

    public static void main(String[] args) {
        try {
            Person target = new Tenant();
            InvocationHandler handler = new JDKHouseAgent(target);
            //生成代理类的实例,这一步骤也可写在JDKHouseAgent类中,包装为一个getInstance方法
            Person obj = (Person) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(), //指定目标类加载器
                    target.getClass().getInterfaces(),  //指定目标接口数组
                    handler);                           //指派调用处理器
            System.out.println("obj的类型 :" + obj.getClass());
            obj.findHouse(); //类型强转,才能调用到findHouse方法
            
            //将运行时生成的代理类字节码输出到磁盘
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
            FileOutputStream fos = new FileOutputStream("$Proxy0.class");
            fos.write(bytes);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行时生成的字节码文件 $Proxy.class

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m3;
    private static Method m0;
    private static Method m2;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void findHouse() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.tmr.proxy.Person").getMethod("findHouse");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法生成代理类实例的逻辑:

  1. Class<?> cl = getProxyClass0(loader, interfaces); 其内部调用proxyClassCache.get(loader, interfaces)从代理类缓存中查找或生成新的代理类。
  2. newInstance(cons, ih); 通过构造器和调用处理器创建代理类实例。

Proxy类中的静态成员proxyClassCache,定义如下:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
它的两个构造参数,KeyFactory和ProxyClassFactory是Proxy类的静态内部类。

真正执行代理类生成的是ProxyClassFactory的apply方法,其生成字节码的位置是:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

三.CGLib动态代理

CGLib(Code Generation Library)为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
通常可以使用JDK动态代理,但要代理的类没有实现接口或者为了更好的性能时,使用CGLib。

CGLib与JDK动态代理的区别:

  1. JDK是采用实现目标接口的形式,去生成新的代理类;CGLib是采用继承的形式,去覆盖目标类的方法。
  2. JDK要求目标类必须实现一个接口;而CGLib对目标类没有要求。
  3. JDK生成代理的逻辑简单,效率高,但由于每次都用反射动态调用,执行效率较低;CGLib使用ASM框架生成字节码,生成了一个包含所有逻辑的FastClass,逻辑更复杂,效率较低,但由于没有反射,执行效率高。

(ASM是一个Java字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM可以直接产生二进制class文件,也可以在类被加载入JVM之前动态改变类行为。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。)

备注:当Bean实现了接口时,spring使用JDK动态代理,Bean未实现接口时,spring使用CGLib。可在配置文件中,强制使用CGLib。
<aop:aspectj-autoproxy proxy-target-class=true/>

CGLib的注意点:不能代理被final或者非public修饰的方法。

###FastClass实现机制

FastClass其实就是对Class对象进行特殊处理,根据方法签名(方法名+参数类型)得到索引值,根据索引值保存方法的引用信息,将原先的反射调用,转化为方法的直接调用,从而体现所谓的fast,下面通过一个例子了解一下FastClass的实现机制。

定义原类 Test.java

class Test {
    public void f(){
        System.out.println("f method");
    }
    public void g(){
        System.out.println("g method");
    }
}

生成Fast类 FastTest.java

class FastTest {
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }

    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }
}

在FastTest中有两个方法,getIndex方法根据Test类中每个方法签名的hash建立索引,invoke方法根据建好的索引,直接调用目标方法,避免了反射调用,从而提高效率。

###举例

1.定义业务逻辑。目标类 Customer.java

public class Customer {
    public void findHouse() {
        System.out.println("顾客想租房");
    }
}

2.定义方法拦截器类,实现MethodInterceptor接口 CGLibHouseAgent.java

public class CGLibHouseAgent implements MethodInterceptor {

	 //利用Enhancer类生成代理类,相当于Proxy.newProxyInstance操作
    public static Object getInstance(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new CGLibHouseAgent());
        return enhancer.create();
    }

	 //Object为由CGLib动态生成的代理类实例
    //Method为被代理的目标方法的引用
    //Object[]为被代理的目标方法的参数列表
    //MethodProxy为对目标方法的代理方法的引用
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o, objects);
        after();
        return obj;
    }

    private void before() {
        System.out.println("我是中介");
    }

    private void after() {
        System.out.println("这是找到的房子,中介费X元");
    }
}

3.运行时 测试类CGLibProxyTest.java

public class CGLibProxyTest {

    public static void main(String[] args) {
        try {
            //将CGLib生成的class文件写到磁盘
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/usernameXXX/workspace/proxy/cglib_classes");

            Customer obj = (Customer) CGLibHouseAgent.getInstance(Customer.class);
            obj.findHouse();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建代理类对象的流程

  1. 创建一个Enhancer的实例。Enhancer类是CGLib中的一个字节码增强器。
  2. 将被代理的目标类设置成父类。
  3. 设置回调为我们写的拦截器CGLibHouseAgent。
  4. 执行enhancer.create()动态生成一个代理类。
  5. 使用时,从Object强制转型成目标类型,以调用目标方法。

(备注:第3步设置回调,可以设置一个回调数组Callback[],里面可以包括拦截器CGLibHouseAgent、不处理NoOp.INSTANCE、FixedValue锁定返回固定值、懒加载LazyLoader。在第3步之后,还可设置回调过滤器CallbackFilter,与Callback[]配合,在CGLib回调时,对不同方法执行不同的回调逻辑,或者根本不执行任何操作。在JDK动态代理中并没有类似的功能,对InvocationHandler接口方法的调用对代理类内的所以方法都有效。)

###CGLib字节码的生成

总共会生成3个class文件:

  1. 代理类:Customer$$EnhancerByCGLIB$$9bb2c927.class
  2. 目标类的FastClass类:Customer$$FastClassByCGLIB$$1809976e.class
  3. 代理类的FastClass类:Customer$$EnhancerByCGLIB$$9bb2c927$$FastClassByCGLIB$$280b972c.class

在代理类中,有两点需要注意:

  1. 对于目标类中的每一个方法,代理类会生成两个方法与之对应。如目标类中的findHouse,在代理类中就会有CGLIB$findHouse$0和findHouse两个方法。
  2. 每一个目标方法,都会在静态块中经过MethodProxy.create生成对应的方法代理。

代理类 Customer$$EnhancerByCGLIB$$9bb2c927.class

public class Customer$$EnhancerByCGLIB$$9bb2c927 extends Customer implements Factory {
 
    //回调绑定标志,缺省值false。
    private boolean CGLIB$BOUND;
    
    //线程本地回调。
    //由静态方法CGLIB$STATICHOOK1()初始化,由newInstance方法内部的CGLIB$SET_THREAD_CALLBACKS方法set。
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    
    //静态回调数组引用变量,由newInstance方法内部的CGLIB$SET_THREAD_CALLBACKS方法set。
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    
    //Enchaner传入的MethodInterceptor对象,由构造方法进行初始化,由CGLIB$BIND_CALLBACKS赋值。
    private MethodInterceptor CGLIB$CALLBACK_0;
    
    //findHouse方法的引用变量,由静态方法CGLIB$STATICHOOK1()初始化赋值。
    private static final Method CGLIB$findHouse$0$Method;
    
    //findHouse的代理方法的引用,由静态方法CGLIB$STATICHOOK1()初始化赋值。
    private static final MethodProxy CGLIB$findHouse$0$Proxy;
    
    //空参数列表,因为findHouse没有形参,由静态方法CGLIB$STATICHOOK1()初始化。
    private static final Object[] CGLIB$emptyArgs;
    
    /* ...省略... */
    
    static {
        CGLIB$STATICHOOK1();
    }
    
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        //反射生成代理类对象
        Class var0 = Class.forName("com.tmr.proxy.dynamic.cglibproxy.Customer$$EnhancerByCGLIB$$9bb2c927");
        //实际类型是目标类
        Class var1;
        //反射获取目标类声明的所有方法
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        //反射获取目标类Customer声明的第0个方法,即findHouse方法
        CGLIB$findHouse$0$Method = ReflectUtils.findMethods(
            new String[]{"findHouse", "()V"}, 
            (var1 = Class.forName("com.tmr.proxy.dynamic.cglibproxy.Customer")).getDeclaredMethods()
        )[0];
        CGLIB$findHouse$0$Proxy = MethodProxy.create(var1, var0, "()V", "findHouse", "CGLIB$findHouse$0");
        /* ...省略... */
    }
    
    //会由methodProxy.invokeSuper调用的方法。
    final void CGLIB$findHouse$0() {
        super.findHouse();
    }
    
    //代理方法,绑定了回调
    public final void findHouse() {
        //获取回调,即我们自定义的CGLibHouseAgent类对象
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if (var10000 != null) {
            //若callback不为空,则调用MethodInterceptor类型成员对象的intercept()方法,我们重写增强了的。
            //此处相当于intercept(代理类对象,目标方法,目标方法参数,代理方法)
            var10000.intercept(this, CGLIB$findHouse$0$Method, CGLIB$emptyArgs, CGLIB$findHouse$0$Proxy);
        } else {
            //如果没有设置callback,则默认执行父类的方法 
            super.findHouse();
        }
    }
  
    /* ...省略... */
    
    //用于绑定回调,为成员变量CGLIB$CALLBACK_0赋值
    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        Customer$$EnhancerByCGLIB$$9bb2c927 var1 = (Customer$$EnhancerByCGLIB$$9bb2c927)var0;
        //判断绑定标志,首次调用该方法时,CGLIB$BOUND是缺省值false,能通过判断,进行回调绑定
        //绑定后,CGLIB$BOUND变为true,之后就不会再进行重复绑定
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            //若没有set线程本地回调,则使用静态回调
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                //若静态回调为null,则没有回调,不为成员CGLIB$CALLBACK_0赋值
                if (CGLIB$STATIC_CALLBACKS == null) {
                    return;
                }
            }
            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }
    }
    
    //构造方法
    public Customer$$EnhancerByCGLIB$$9bb2c927() {
        CGLIB$BIND_CALLBACKS(this);
    }
    
    //为线程本地回调CGLIB$THREAD_CALLBACKS赋值
    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }
    
    //为静态回调CGLIB$THREAD_CALLBACKS赋值
    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }
    
    /* ...省略... */
}  

可以看到,目标方法findHouse的代理方法CGLIB$findHouse 0 0 0Proxy,是通过MethodProxy.create方法创建的。

分析一下方法代理类 MethodProxy.java

方法代理的作用就是指明class1.sig1方法的代理方法是class2.sig2

public class MethodProxy {

    private Signature sig1; //目标方法签名,此处值为findHouse()V
    private Signature sig2; //代理方法签名,此处值为CGLIB$findHouse$0()V
    private MethodProxy.CreateInfo createInfo;
    private final Object initLock = new Object();
    private volatile MethodProxy.FastClassInfo fastClassInfo;

    //在代理类中调用时,create(目标类对象var1, 代理类对象var0, "()V", "findHouse", "CGLIB$findHouse$0")
    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
        return proxy;
    }

    //初始化fastClassInfo,静态内部类的双检锁单例
    private void init() {
        if (this.fastClassInfo == null) {
            Object var1 = this.initLock;
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    MethodProxy.CreateInfo ci = this.createInfo;
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                }
            }
        }
    }
    
    //根据CreateInfo动态生成FastClass对象
    private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
        Generator g = new Generator();
        g.setType(type);
        g.setClassLoader(ci.c2.getClassLoader());
        g.setNamingPolicy(ci.namingPolicy);
        g.setStrategy(ci.strategy);
        g.setAttemptLoad(ci.attemptLoad);
        return g.create();
    }
    
    /* ...省略... */
    
    //调用目标类的方法
    public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        } catch (IllegalArgumentException var5) {
            if (this.fastClassInfo.i1 < 0) {
                throw new IllegalArgumentException("Protected method: " + this.sig1);
            } else {
                throw var5;
            }
        }
    }
    
    //调用代理类的方法
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

    private static class CreateInfo {
        Class c1; //create时传入的var1,实际类型是目标类Customer
        Class c2; //create时传入的var0,实际类型是代理类Customer$$EnhancerByCGLIB$$9bb2c927
        NamingPolicy namingPolicy;
        GeneratorStrategy strategy;
        boolean attemptLoad;
        
        public CreateInfo(Class c1, Class c2) {
            this.c1 = c1;
            this.c2 = c2;
            AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
            if (fromEnhancer != null) {
                this.namingPolicy = fromEnhancer.getNamingPolicy();
                this.strategy = fromEnhancer.getStrategy();
                this.attemptLoad = fromEnhancer.getAttemptLoad();
            }
        }
    }

    private static class FastClassInfo {
        //目标类的FastClass对象,实际类型是Customer$$FastClassByCGLIB$$1809976e
        FastClass f1;
        //代理类的FastClass对象,实际类型是Customer$$EnhancerByCGLIB$$9bb2c927$$FastClassByCGLIB$$280b972c
        FastClass f2;
        int i1; //目标类的目标方法的索引 i1 = f1.getIndex("findHouse()V")
        int i2; //代理类的代理方法的索引 i2 = f2.getIndex("CGLIB$findHouse$0()V")

        private FastClassInfo() {}
    }
}    

可以看见,MethodProxy类中,有2个静态内部类CreateInfo和FastClassInfo:

  1. CreateInfo里的2个成员c1和c2,分别是目标类对象和代理类对象的引用。
  2. FastClassInfo里的2个成员f1和f2,是在调用helper方法时,动态生成的另外两个字节码类型的对象,f1是目标类的FastClass对象的引用,f2是代理类的FastClass对象的引用,可以理解为fast(c1)和fast(c2)。

(静态内部类是lazyInit的,CreateInfo在MethodProxy的构造函数中被创建,而FastClassInfo是调用invoke或invokeSuper中的init方法时才创建,并且用了单例模式。)

把intercept方法中的invokeSuper改成invoke会怎么样:死循环,OutOfMemory。

首先明确,invokeSuper方法和invoke方法的两个形参,obj和args,分别是传入intercept方法的代理类实例和目标方法参数。

我们理一下执行流程:

  1. 在main方法中,获取代理类的实例obj,调用obj.findHouse()。
  2. 进入代理类的findHouse方法,先检查是否有绑定回调,有则调用intercept方法,没有则直接调用目标类的目标方法。
  3. 进入intercept方法,我们在CGLibHouseAgent类中重写了的,调用methodProxy.invokeSuper(obj,args)方法,内部执行的是fci.f2.invoke(fci.i2,obj,args),即通过代理FastClass类中的索引,执行obj.CGLIB$findHouse$0()方法,方法体中是super.findHouse()语句,直接调用目标方法。
  4. 如果我们在重写的intercept方法中,调用methodProxy.invoke方法,内部执行的是fci.f1.invoke(fci.i1,obj,args),即通过目标FastClass类中的索引,执行obj.findHouse()方法,绕回到了第一步。

总结 MethodProxy和FastClass结合使用:

  1. methodProxy用于生成方法代理的关系绑定(class1.method1被class2.method2代理)
  2. fastClass用于完成方法代理的快速调用,通过方法签名拿到方法索引,直接调用对象的方法。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tmr1016/article/details/100579719

智能推荐

linux 使用go gin搭建web,[Go]基于Gin的Web服务端构建 · Wanmii’s Blog._腐国喵小姐的博客-程序员秘密

8种机械键盘轴体对比本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?关于Gin的基本使用方法可移步Gin中文文档本文以个人实际工程经验为基础,介绍了一种简易但实用的Web服务端构建方式,demo源码见文末。包含状态的HTTPServer封装要起一个Web服务,首先需要有一个server监听接口并接收http请求。Go在net/http包中有内置的http.server,结构包含了监听端口...

python能熔断吗_计算机领域中的熔断机制_weixin_39617484的博客-程序员秘密

计算机领域中的熔断机制诗书塞外 Python程序员这几天股市好热闹啊, 开年一周N次熔断, 把证监会吓到了, 急忙中止了熔断机制.熔断机制本身其实并不坏, 在计算机的世界里, 也大量地使用了熔断机制.在操作系统中, 某一个程序长时间占用CPU(俗称"卡死"), 就会触发操作系统的熔断机制. 这时操作系统就会弹出一个对话框, 让你选择等待还是直接停止运行.微信中, 一篇文章阅读过于火爆, 阅读人数...

使用scrollTo/scrollTop让页面元素滚动到指定位置, 并设置滚动动画_scrolltop 动画_小垚尧的博客-程序员秘密

如何让子元素滚动到指定父窗口的指定位置什么是滚动距离读取滚动距离如何设置滚动scrollTop属性scrollTo方法什么是滚动距离比如父元素设置了overflow: hidden;, 当元素里的内容超过元素本身的高度时, 就会出现滚动条, 那么鼠标滑动的距离就是其滚动距离.读取滚动距离&lt;head&gt; &lt;title&gt;test&lt;/title&gt; &lt;style&gt; ul { width: 200px;

pip安装 scikit-image包 出错 ERROR: Could not find a version that satisfies the requirement scikit-image_中南自动化学院“智能控制与优化决策“至渝的博客-程序员秘密

错误信息:ERROR: Could not find a version that satisfies the requirement scikit-image (from versions: none)ERROR: No matching distribution found for scikit-image原因:一般是网络的问题,需要使用国内的镜像源来加速解决办法pip install scikit-image -i http://pypi.douban.com/simple/ --

Windows Server 2003 为用户“NT AUTHORITY/NETWORK SERVICE”授予的权限不足,无法执行此操作。 (rsAccessDenied)_名贤集的博客-程序员秘密

在VISTA,Win7,Server 2008操作系统里建立SQL报表时使用reportview访问报表时提示下面错误: 为用户“NT AUTHORITY/NETWORK SERVICE”授予的权限不足,无法执行此操作。 (rsAccessDenied)解决方案:1,在IIS里访问http://localhost/Reports,在属性项里点击“新建角色分配”,在“组或用户名”填入

随便推点

Codeforces 786A Berzerk DP+博弈_codeforces 树上博弈_VampireWeekend的博客-程序员秘密

A. Berzerktime limit per test4 secondsmemory limit per test256 megabytesinputstandard inputoutputstandard outputRick and Morty are playing their own version

APK文件如何查看源代码_怎么查看apk源代码_克利斯提亚诺-梅西的博客-程序员秘密

"APK文件如何查看源代码":关键词:apk 文件 如何 查看 源代码今天在网上找到一个有效查看apk源代码的方法,经验证确实可行,拿来与大家分享。apk文件其实也是打的压缩包,只是class文件被编译为dex文件,我们很难将其打开来阅读,接下来各位跟着我做便能把这厮变成我们可见的摸样。首先把apk文件后缀改为zip,让后将其解压,在得到的解压文

python设计模式理解_Python设计模式概论_weixin_39687301的博客-程序员秘密

Python设计模式什么是设计模式设计模式是前辈们对开发经验的总结,是解决特定问题的一系列套路,它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。设计模式分类三类:1)创建型模式:单例、工厂、抽象工厂、建造者、原型2)结构型型模式:适配器、桥接、装饰器、组合、外观、享元、代理3)行为型模式:模板方法、命令、迭代器、观察者、中介、备忘录、解释器、状态、策略、...

微信企业号回调模式配置讲解 Java Servlet+Struts2版本 echostr校验失败解决_小帅丶的博客-程序员秘密

异常java.security.InvalidKeyException:illegal Key Size 也就是echostr校验失败,请您检查是否正确解密并输出明文echostr这个错误企业微信登陆地址http://qy.weixin.qq.com/配置成功以后官方回调页面的代码?123456

五小时构建云原生电商平台 | KubeCon SOFAStack Workshop 详解_支付宝技术团队的博客-程序员秘密

2019 年 6 月 25 日,在 KubeCon China 2019,全球知名开源组织云原生计算基金会 CNCF 宣布,蚂蚁金服正式成为 CNCF 黄金会员,蚂蚁金服表示将持续加大对开源项目的支持,包括 Kubernetes,Service Mesh,Serverless,安全容器等方向,并发挥自己的力量。在本次大会,蚂蚁金服也与数百名云原生爱好者用五个小时搭建了一个云原生的电商平台,具体...

统计重复元素个数_bluejad的博客-程序员秘密

 qt=[1,1, 2,2, 2, 3,3,3,3, 4, 4, 5, 5, 6,6,6,6,6,6]qt=[1,1,0, 2,2, 2, 3]qts=[]count=1maxcount=[]maxcount.append(count)ind=[10,11,12,13,14,15,16]inds=[]for i in range(len(qt)-1): if q...

推荐文章

热门文章

相关标签