Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

Stella981
• 阅读 679

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

SpringAOP原理探究

思考:springAOP底层运用了什么设计模式?

生成代理类:代理设计模式、底层五个通知形成调用链采用:责任链设计模式

下面我们回顾下SpringAop实现流程:

1、配置@EnableAspectJAutoProxy:开启AOP权限

2、@Import(AspectJAutoProxyRegistrar.class):往IOC容器中注入SpringAOP切面类

3、registerAspectJAnnotationAutoProxyCreatorIfNecessary():注册切面类

4、AnnotationAwareAspectJAutoProxyCreator.class:注册到IOC容器中,【AOP的入口】

5、AnnotationAwareAspectJAutoProxyCreator:祖宗是BeanPostProcessor接口,而实现BeanPostProcessor接口后,当Spring加载这个Bean会在实例化前调用其后置处理器实现增强

6、postProcessAfterInitialization:后置处理器【AOP实现核心逻辑】

####6.1、wrapIfNecessary()判断该对象是否在AOP的扫包范围内,真正创建代理类的地方

#########6.1.1、getAdvicesAndAdvisorsForBean创建代理对象包括获取增强方法和根据获取的增强进行代理

#########6.1.2、createAopProxy()判断被代理类是否实现了接口,如果有实现了接口的化,是采用JDK动态代理,否则情况下就使用CGLIB代理

####6.2、根据条件判断使用JdkDynamicAopProxy或者JdkDynamicAopProxy方法实现代理

####6.3、最终执行目标方法的时候,就会进入到JdkDynamicAopProxy 的invoke方法或者JdkDynamicAopProxy的****intercept方法

####6.5、****底层使用集合存放使用通知,然后再使用责任链设计模式循环的调用

创建代理

我们先来总结下JDK和CGLIB方式

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
  • 如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换

JDK动态代理和CGLIB字节码生成的区别?

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或者方法最好不要声明为final

获取代理

1.JDK代理使用示例

public interface UserService { ** //目标方法** public abstract void add(); }

public class UserServiceImpl implements UserService { /** * dynamic.proxy.UserService#add() */ public void add() { System.out.println("add......."); } }

public class MyInvocationHandler implements InvocationHandler {

**//目标对象**
private Object target;

/\*\*
 \* **构造函数**
 \* @param target:目标对象
 \*/
public MyInvocationHandler(Object target) {
    super();
    this.target = target;
}

/\*\*
 \* **执行目标对象的方法**
 \* @param proxy
 \* @param method
 \* @param args
 \* @return
 \* @throws Throwable
 \*/
public Object **invoke**(Object proxy, Method method, Object\[\] args) throws Throwable {
    System.out.println("-------------before-----------");
    Object result = method.invoke(target, args);
    System.out.println("-------------after------------");
    return result;
}

public Object getProxy(){
    return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            target.getClass().getInterfaces(),this);
}

}

public class ProxyTest { public static void main(String[] args) { //实例化目标对象 UserService userService = new UserServiceImpl(); //实例化InvocationHandler MyInvocationHandler invocationHandler = new MyInvocationHandler(userService); //根据目标对象生成代理对象 UserService proxy = (UserService) invocationHandler.getProxy(); //调用代理对象的方法 proxy.add(); } }

输出结果:

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

JdkProxy使用示例源码分析

我们再次回顾下JDK代理方式,在整个创建过程中,对于InvocationHandler的创建是最为核心的,在自定义的InvocationHandler中需要重写三个函数

构造函数,将代理的对象传入

invoke方法,此方法中实现了AOP增强的所有逻辑

getProxy方法,此方法千篇一律,但是必不可少。

那么我们看看Spring中的JDK代理实现是不是也是这么做的呢?继续之前的跟踪,到达JdkDynamicAopProxy的getProxy方法。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) { Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));//如果循环遍历找到需要代理的对象,则会进入到这里创建代理 this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } else { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } } else { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } }

protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass); }

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
    if (this.shouldProxyTargetClass(beanClass, beanName)) {
        proxyFactory.setProxyTargetClass(true);
    } else {
        this.evaluateProxyInterfaces(beanClass, proxyFactory);
    }
}

Advisor\[\] **advisors** \= this.buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
this.customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (this.advisorsPreFiltered()) {
    proxyFactory.setPreFiltered(true);
}

return proxyFactory.**getProxy**(this.getProxyClassLoader());

}

获取了五个通知:

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

public Object getProxy(@Nullable ClassLoader classLoader) { return this.createAopProxy().getProxy(classLoader); }

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); }

Class<?>\[\] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);

}

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

我们可以推断出,在JdkDynamicAopProxy中一定会有个invoke函数,并且JdkDynamicAopProxy会把AOP的核心逻辑写在里面。我们查看代码发现确实是有invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Object target = null;

Class var9;
try {
    if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
        Boolean var19 = this.equals(args\[0\]);
        return var19;
    }

    if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
        Integer var18 = this.hashCode();
        return var18;
    }

    if (method.getDeclaringClass() != DecoratingProxy.class) {
        Object retVal;
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
            return retVal;
        }

            //有时候目标对象内部的自我调用将无法实施切面中的增强则需要通过此属性暴露代理 if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; }

        target = targetSource.getTarget();
        Class<?> targetClass = target != null ? target.getClass() : null;

            //获取当前方法的拦截器链 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);                //如果没有发现任何拦截器那么直接调用切点方法 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else {                 //将拦截器封装在ReflectiveMethodInvocation,以便于使用其proceed进行链接 MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);                //执行拦截器链 retVal = invocation.proceed(); }

        Class<?> returnType = method.getReturnType();

            //返回结果 if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); }

        Object var13 = retVal;
        return var13;

.... }

上面函数中主要是 创建了一个拦截器链,并使用ReflectiveMethodInvocation类进行了链的封装,而在ReflectiveMethodInvocation类的proceed方法中是怎么实现前置增强和后置增强

public Object proceed() throws Throwable { if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return this.invokeJoinpoint(); } else {         //获取下一个需要执行的拦截器 Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {             //动态匹配 InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;             //匹配则执行拦截器,不匹配不执行拦截器 return dm.methodMatcher.matches(this.method, this.targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed(); } else {             //普通拦截,直接调用拦截器,将this作为参数传递以保证当前实例中调用链的执行 return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this); } } }

proceed方法中,ReflectiveMethodInvocation中的主要职责是维护了链接调用的计数器,记录当前调用链接的位置,以便链可以有序的进行下去。

CGLIB使用示例源码分析

public class EnhancerDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(EnhancerDemo.class); enhancer.setCallback(new MethodInterceptorImpl());

    EnhancerDemo demo = (EnhancerDemo) enhancer.create();
    demo.test();
    System.out.println(demo);

}

private void test() {
    System.out.println("EnhancerDemo #test()....");
}

private static class MethodInterceptorImpl implements **MethodInterceptor**{

    @Override
    public Object **intercept**(Object o, Method method, Object\[\] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("------before invoke----"+method);
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("-----after invoke------"+method);
        return result;
    }
}

}

运行结果

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

生成的对象为:com.xiaoxu.cglib.EnhancerDemo$$EnhancerByCGLIB$$c9092bbe@78e03bb5的实例,这个类是运行时由CGLIB产生的。

我们看下Spring源码对CGLIB的实现:

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource()); }

try {
    Class<?> rootClass = this.advised.getTargetClass();
    Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
    Class<?> proxySuperClass = rootClass;
    int x;
    if (ClassUtils.isCglibProxyClass(rootClass)) {
        proxySuperClass = rootClass.getSuperclass();
        Class<?>\[\] additionalInterfaces = rootClass.getInterfaces();
        Class\[\] var5 = additionalInterfaces;
        int var6 = additionalInterfaces.length;

        for(x = 0; x < var6; ++x) {
            Class<?> additionalInterface = var5\[x\];
            this.advised.addInterface(additionalInterface);
        }
    }

      //验证class this.validateClassIfNecessary(proxySuperClass, classLoader);      //创建及配置Enhancer Enhancer enhancer = this.createEnhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); if (classLoader instanceof SmartClassLoader && ((SmartClassLoader)classLoader).isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } }

    enhancer.setSuperclass(proxySuperClass);
    enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new CglibAopProxy.ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

      //设置拦截器 Callback[] callbacks = this.getCallbacks(rootClass); Class<?>[] types = new Class[callbacks.length];

    for(x = 0; x < types.length; ++x) {
        types\[x\] = callbacks\[x\].getClass();
    }

    enhancer.setCallbackFilter(new CglibAopProxy.ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
    enhancer.setCallbackTypes(types);
    return this.createProxyClassAndInstance(enhancer, callbacks);
} catch (IllegalArgumentException | CodeGenerationException var9) {
    throw new AopConfigException("Could not generate CGLIB subclass of class \[" + this.advised.getTargetClass() + "\]: Common causes of this problem include using a final class or a non-visible class", var9);
} catch (Throwable var10) {
    throw new AopConfigException("Unexpected AOP exception", var10);
}

}

这里最重要的是通过getCallbacks方法设置拦截器链

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {     //对属性expose-proxy处理 boolean exposeProxy = this.advised.isExposeProxy(); boolean isFrozen = this.advised.isFrozen(); boolean isStatic = this.advised.getTargetSource().isStatic();     //将拦截器封装在DynamicAdvisedInterceptor中 Callback aopInterceptor = new CglibAopProxy.DynamicAdvisedInterceptor(this.advised); Object targetInterceptor; if (exposeProxy) { targetInterceptor = isStatic ? new CglibAopProxy.StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : new CglibAopProxy.DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()); } else { targetInterceptor = isStatic ? new CglibAopProxy.StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : new CglibAopProxy.DynamicUnadvisedInterceptor(this.advised.getTargetSource()); }

Callback targetDispatcher = isStatic ? new CglibAopProxy.StaticDispatcher(this.advised.getTargetSource().getTarget()) : new CglibAopProxy.SerializableNoOp();
Callback\[\] mainCallbacks = new Callback\[\]{aopInterceptor, (Callback)targetInterceptor, new CglibAopProxy.SerializableNoOp(), (Callback)targetDispatcher, this.advisedDispatcher, new CglibAopProxy.EqualsInterceptor(this.advised), new CglibAopProxy.HashCodeInterceptor(this.advised)};
Callback\[\] callbacks;
if (isStatic && isFrozen) {
    Method\[\] methods = rootClass.getMethods();
    Callback\[\] fixedCallbacks = new Callback\[methods.length\];
    this.fixedInterceptorMap = new HashMap(methods.length);

    for(int x = 0; x < methods.length; ++x) {
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods\[x\], rootClass);
        fixedCallbacks\[x\] = new CglibAopProxy.FixedChainStaticTargetInterceptor(chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
        this.fixedInterceptorMap.put(methods\[x\].toString(), x);
    }

      //将拦截器加入到Callback中 callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length]; System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length); System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length); this.fixedInterceptorOffset = mainCallbacks.length; } else { callbacks = mainCallbacks; } return callbacks; }

getCallbacks中spring考虑了很多情况,但是对于我们来说,只需要理解最常用的就可以了,CGLIB中对于方法的拦截是通过将自定义的拦截器(实现MethodInterceptor接口)加入到Callback中

并在调用代理时直接激活拦截器中的intercept方法来实现。由此可推断,对于CGLIB方式实现的代理,其核心逻辑必然在DynamicAdvisedInterceptor中的intercept中。

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; Object target = null; TargetSource targetSource = this.advised.getTargetSource();

Object var16;
try {
    if (this.advised.exposeProxy) {
        oldProxy = AopContext.setCurrentProxy(proxy);
        setProxyContext = true;
    }
    target = targetSource.getTarget();
    Class<?> targetClass = target != null ? target.getClass() : null;

       //获取拦截器链 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); Object retVal; if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);             //如果拦截器链为空则直接激活源原方法 retVal = methodProxy.invoke(target, argsToUse); } else {             //进入链 retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed(); } retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal); var16 = retVal; } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); } } return var16; }

上述的实现与JDK方式实现代理中的invoke方法大同小异,首先是构造链,然后封装此链进行串联调用,区别在于JDK中直接构造ReflectiveMethodInvocation,而在CGLIB中使用CglibMethodInvocation.

CglibMethodInvocation继承自ReflectiveMethodInvocation,但是process方法并没有重写。

SpringBean的AOP

主要靠的是后置处理器BeanPostProcessor:在Bean对象初始化前后做一些增强

AnnotationAwareAspectJAutoProxyCreator的祖宗是BeanPostProcessor

纯手写SpringAop调用链思路

【0】环绕通知之前执行→【1】前置通知→目标方法→【2】后置通知→【3】环绕通知之后执行

责任链设计模式,底层通过递归算法+责任链

如何存放起来:使用集合存放这些通知,集合当中不存放我们的方法,只存放链,那么如何插入我们的目标方法?

纯手写SpringAop调用链

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

public interface MethodInterceptor { /** * 定义共同通知骨架 */ public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException; }

public class BeforMethodInterceptor implements MethodInterceptor { public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException { System.out.println(">>>>前置通知<<<<"); // 执行我们的目标方法 methodInvocation.process();// 递归调用 } }

public class AfterMethodInterceptor implements MethodInterceptor { public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException { // 执行我们的前置通知 methodInvocation.process(); System.out.println(">>>后置通知<<<"); } }

public class AroundMethodInterceptor implements MethodInterceptor { public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException { System.out.println("环绕通知在目标方法之前执行.."); methodInvocation.process(); System.out.println("环绕通知在目标方法之后执行.."); } }

public class UserService { public void login(String userName, Integer age) { System.out.println("userName:" + userName + ",age:" + age); } }

MethodInvocation 能够把链串起来

public interface MethodInvocation {     //调用链形成 public void process() throws InvocationTargetException, IllegalAccessException;

}

public class DefaultMethodInvacation implements MethodInvocation { /** * 存放所有的通知 */ private List listMethodInterceptor; private Object target;// 目标对象 private Method method;// 目标方法 private Object args[];// 目标参数 // 最终使用反射机制执行目标方法 private int index;// 记录当前链调用的位置 public DefaultMethodInvacation(List listMethodInterceptor, Object target, Method method, Object[] args) { this.listMethodInterceptor = listMethodInterceptor; this.target = target; this.method = method; this.args = args; } /** * 调用链形成 */ @Override public void process() throws InvocationTargetException, IllegalAccessException { if (index == listMethodInterceptor.size()) { method.invoke(target, args); // 执行目标 return; } MethodInterceptor methodInterceptor = listMethodInterceptor.get(index++); methodInterceptor.invoke(this); } }

执行结果:

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

>>>>前置通知<<<<
环绕通知在目标方法之前执行..
userName:mayikt,age:12
环绕通知在目标方法之后执行..
>>>后置通知<<<

本文参考

参考书籍:Spring源码深度解析

蚂蚁课堂:http://www.mayikt.com/

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这