spring高级篇(三)

1、Spring选择代理

1.1、Aspect和Advisor

        在Spring框架中,"Aspect" 和 "Advisor" 是两个关键的概念,它们都与AOP(面向切面编程)密切相关:

        如果要在Spring中定义一个Aop类,通常会:

@Component
@Aspect
public class MyAspect {@Before("execution()")public void before(){System.out.println("before");}@After("execution()")public void after(){System.out.println("after");}}

        这意味着可以有多个通知和切点。

        而Advisor是Advice的底层实现,Spring在执行Aop时,会将被@Aspect 注解修饰的类中的通知和切点拆分成多个Advisor,一个Advisor只能包含一个通知+一个切点。

1.2、模拟Advisor的实现

        要模拟Advisor的实现,首先需要准备切点,我们首先看下PointCut接口:

public interface Pointcut {Pointcut TRUE = TruePointcut.INSTANCE;ClassFilter getClassFilter();MethodMatcher getMethodMatcher();
}
  •  getClassFilter()方法返回一个ClassFilter对象,用于过滤类匹配的规则。ClassFilter用于确定哪些应该应用横切逻辑。
  • getMethodMatcher()方法返回一个MethodMatcher对象,用于确定哪些方法应该应用横切逻辑。MethodMatcher用于匹配类中的方法。

        PointCut接口也有很多实现类,典型的有根据注解进行切点匹配,以及根据包名、类名、参数等进行匹配。(个人喜好根据注解进行匹配,更加灵活)

        本次使用AspectJExpressionPointcut实现类进行演示:

AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");

        然后准备通知,我们使用org.aopalliance.intercept包下的MethodInterceptor接口(和CGLIB的MethodInterceptor同名),本质上是环绕通知:

 MethodInterceptor interceptor = new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("before");Object proceed = methodInvocation.proceed();System.out.println("after");return proceed;}
};

        还需要准备切面

DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);

        最后创建代理,使用ProxyFactory代理工厂类进行创建,它会自动选择代理模式:(此时使用的理论上应该是JDK动态代理,因为目标类Target实现了接口)

ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(new Target());
Target proxy = (Target) factory.getProxy();

         然而结果却是使用了CGLIB进行增强,这是什么原因?

1.3、选择代理的时机

        ProxyFactory代理工厂类选择何种代理方式,除了取决于目标类是否实现了接口,还与ProxyConfig的private boolean proxyTargetClass = false; 成员变量有关,一般有以下的情况:

  • proxyTargetClass = false 目标实现了接口,使用jdk实现
  • proxyTargetClass = false 目标没有实现接口,使用cglib实现
  • proxyTargetClass = true 无论目标是否实现接口,都是使用cglib实现
1.4、切点匹配

        我现在有三个类,一个接口,分别是:

public class F1 {public void foo() {System.out.println("foo");}@Transactionalpublic void bar() {}
}
@Transactional
public class F2 {public void foo() {System.out.println("foo");}public void bar() {}
}
@Transactional
interface F3 {void foo();
}
public class F4 implements F3 {@Overridepublic void foo() {System.out.println("foo");}
}

        此时如果我需要只增强F1类中的foo()方法,不增强bar()方法,则切点表达式需要根据包名、类名、方法等参数配置:

AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");

        底层是通过.matches() 方法进行判定方法/注解是否匹配的:

//底层是通过调用aspectj的.matches判断方法/注解是否匹配
System.out.println(pointcut.matches(F1.class.getMethod("foo"), F1.class));
System.out.println(pointcut.matches(F1.class.getMethod("bar"), F1.class));

         如果我需要只增强F1类中的bar()方法,不增强foo()方法,因为bar()方法上加了注解,我们可以在切点表达式配置注解类型:

AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
pointcut2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
System.out.println(pointcut2.matches(F1.class.getMethod("foo"), F1.class));
System.out.println(pointcut2.matches(F1.class.getMethod("bar"), F1.class));

        在上面的案例中 @Transactional 注解,可以加在方法上,表示某个方法被事务控制;也可以加在类上,表示这个类中所有的方法都被事务控制;还可以加在接口上,表示实现了这个接口的子类中的方法会被事务控制。

        而AspectJExpressionPointcut切点表达式,只能切方法,无法识别接口和类。所以我们需要换一种实现方式:

      StaticMethodMatcherPointcut pointcut3 = new StaticMethodMatcherPointcut() {@Overridepublic boolean matches(Method method, Class<?> aClass) {//检查方法上是否加了注解MergedAnnotations annotations = MergedAnnotations.from(method);if (annotations.isPresent(Transactional.class)){return true;}//检查类上是否加了注解 如果需要检查父类需要重写规则 MergedAnnotations.SearchStrategy.TYPE_HIERARCHYannotations = MergedAnnotations.from(aClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);if (annotations.isPresent(Transactional.class)){return true;}return false;}};
1.5、Advisor的工作原理

        AnnotationAwareAspectJAutoProxyCreator是用于自动创建代理以实现切面功能的Spring后处理器,我们可以从容器中获取:

AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);

        在AnnotationAwareAspectJAutoProxyCreator中,有两个重要的方法:

  • protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName):用于查找符合条件的切面通知器(Advisors)。
  • protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey):内部调用 .findEligibleAdvisors得到存放符合条件切面通知器的集合,如果集合不为空,就创建代理。

        下面通过一个案例模拟AnnotationAwareAspectJAutoProxyCreator的实现:

        定义一个Aspect切面:

@Aspect
public class MyAspect {@Before("execution(* foo())")public void before(){System.out.println("before");}@After("execution(* foo())")public void after(){System.out.println("after");}
}

        定义两个目标类:

public class Target1 {public void foo(){System.out.println("foo");}
}
public class Target2 {public void bar(){System.out.println("bar...");}
}

        定义一个Advisor切面:

@Configuration
public class Config {@Beanpublic Advisor advisor(MethodInterceptor advice){AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");return new DefaultPointcutAdvisor(pointcut,advice);}@Beanpublic MethodInterceptor advice(){return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("before");Object proceed = methodInvocation.proceed();System.out.println("after");return proceed;}};}}

        无论是Aspect切面还是Advisor切面,都要对foo()方法做增强。

        定义主类:

public class A17Application {public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.registerBean("MyAspect", MyAspect.class);context.registerBean("Config", Config.class);context.registerBean(ConfigurationClassPostProcessor.class);//自动创建代理以实现切面功能的后处理器context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);context.refresh();AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
//        //.findEligibleAdvisors找到目标类Target1.class中能匹配的高级/低级切面List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class,"target1");//此时会有四个,第一个是spring自带的,后面三个有两个是MyAspect中高级切面转换成两个低级切面,还有一个是Config中的低级切面for (Advisor advisor : advisors) {System.out.println(advisor);}}
}

        后三个切面,其中一个是Advisor中的,两个是Aspect分解出的:

//.wrapIfNecessary 内部调用 .findEligibleAdvisors 得到List<Advisor> 集合不空,就创建代理
Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
System.out.println(o1.getClass());
Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
System.out.println(o2.getClass());

        最终Target1被创建了代理,因为其中有foo()方法,可以被匹配上。

1.6、代理的创建时机
  • 如果没有循环依赖,是在初始化之后创建。
  • 如果存在循环依赖,是在实例创建后, 依赖注入前创建。

        没有循环依赖,Bean2等待Bean1初始化成功后再创建,注入的Bean1应该是增强后的,所以会在Bean1初始化之后创建代理:

        Bean1在初始化之前需要等待Bean2初始化完成,Bean2初始化前需要等待Bean1初始化完成,存在循环依赖的问题,代理会在Bean1实例创建后, 依赖注入前创建

 

补充:循环依赖

Spring 中的循环依赖问题是指两个或多个 Bean 之间相互依赖,形成了一个循环引用的情况。这种情况下,Spring 容器无法在初始化 Bean 的过程中解决依赖关系,从而导致应用程序启动失败或出现其他异常。

循环依赖通常发生在两个或多个 Bean 之间相互引用的情况下。例如,Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A。

循环依赖通常发生在单例作用域的 Bean 之间,因为单例 Bean 在容器启动时会被实例化并放入容器中,而在实例化的过程中,Spring 无法解决循环引用。

常见的解决方式:

  • 构造器注入:使用构造器注入可以在实例化 Bean 时解决循环依赖问题。这是因为 Spring 会优先实例化构造器参数所需的 Bean,然后再注入到对应的构造器中。
  • @Lazy 注解:可以使用 @Lazy 注解延迟注入 Bean,从而打破循环依赖。
1.7、高级切面转换低级切面

        可以通过解析高级切面类中@Before、@After等注解,生成低级切面,下面以解析@Before 注解为例:

        准备一个类,类中有两个标注了@Before 注解的方法:

public class Aspect {@Before("execution(* foo())")public void before1() {System.out.println("before1");}@Before("execution(* foo())")public void before2() {System.out.println("before2");}public void after() {System.out.println("after");}public void afterReturning() {System.out.println("afterReturning");}public void afterThrowing() {System.out.println("afterThrowing");}public Object around(ProceedingJoinPoint pjp) throws Throwable {try {System.out.println("around...before");return pjp.proceed();} finally {System.out.println("around...after");}}
}

        在测试类中,首先需要准备一个集合,存放解析到的切面,以及AspectInstanceFactory。AspectInstanceFactory是 Spring AOP 中的一个接口,用于创建切面(Aspect)的实例。我们选择SingletonAspectInstanceFactory实现(用于创建单例模式的切面实例,即每次获取切面实例时都会返回同一个对象。)

AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
//创建一个存放所有切面的集合
ArrayList<Advisor> advisors = new ArrayList<>();

        然后我们通过反射获取Aspect类中的所有方法,然后判断方法上是否加了 @Before注解,

并且拿到注解的value属性(切点表达式),就可以准备切点,通知然后放入切面中。

 //得到高级切面类Aspect的所有方法Method[] methods = Aspect.class.getMethods();for (Method method : methods) {//判断方法上是否有 @Before注解if (method.isAnnotationPresent(Before.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(Before.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}}

        此时集合中有两个元素。(Aspect的before1()before2()方法)

for (Advisor advisor : advisors) {System.out.println(advisor);
}
1.8、环绕通知转换

        在“1.7、高级切面转换低级切面”中,我们通过解析高级切面上加了@Before、@After等注解的方式,将高级切面中的每个方法拆解成了低级切面(实际是在创建代理时),最终将其放入了一个集合中:

//创建一个存放所有切面的集合
ArrayList<Advisor> advisors = new ArrayList<>();

        改造上面的案例,加入对@AfterReturning、@Around 注解的解析:

//得到高级切面类Aspect的所有方法Method[] methods = Aspect.class.getMethods();for (Method method : methods) {//判断方法上是否有 @Before注解if (method.isAnnotationPresent(Before.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(Before.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}else if (method.isAnnotationPresent(AfterReturning.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(AfterReturning.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}else if  (method.isAnnotationPresent(Around.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(Around.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}}

        遍历存放了低级切面的集合,可以得到:

  • AspectJMethodBeforeAdvice:@Before注解转换成的前置通知
  • AspectJAfterReturningAdvice:@AfterReturning注解转换成的AfterReturning通知
  • AspectJAroundAdvice:@Around注解转换成的环绕通知

        然后下一步就应该创建代理并且调用,无论 ProxyFactory 基于哪种方式创建代理, 最后干活(调用 advice)的是一个 MethodInvocation 对象。

         MethodInvocation 对象是一个调用链对象(调用链指的是将通知和目标串成链调用),除了需要调用所有的低级切面,还需要调用目标方法。所以环绕通知才适合作为 advice, 其他 before、afterReturning 都会被转换成环绕通知。(实现了MethodInterceptor接口的通知本身就是环绕通知

        统一转换为环绕通知, 体现的是设计模式中的适配器模式,转换时机是在执行方法时。 为了演示,我们使用proxyFactory.getInterceptorsAndDynamicInterceptionAdvice()方法查看转换结果:

        //创建代理Target target = new Target();ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTarget(target);proxyFactory.addAdvisors(advisors);//获取代理对象方法调用链中需要执行的拦截器和动态拦截通知,查看转换效果List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);for (Object o : methodInterceptorList) {System.out.println(o);}

        遍历methodInterceptorList集合,可以得到:

  • MethodBeforeAdviceInterceptor:实现了MethodInterceptor接口,是AspectJMethodBeforeAdvice转换成的环绕通知。
  • AfterReturningAdviceInterceptor:实现了MethodInterceptor接口,是AspectJAfterReturningAdvice转换成的环绕通知。
  • AspectJAroundAdvice:实现了MethodInterceptor接口,因为AspectJAroundAdvice自身就是环绕通知,无需转换。

        而统一进行转换的过程涉及到适配器设计模式:

  • MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor

  • AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor

        我们可以看一下MethodBeforeAdviceAdapter :

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {MethodBeforeAdviceAdapter() {}//检查是否支持适配public boolean supportsAdvice(Advice advice) {return advice instanceof MethodBeforeAdvice;}//进行适配public MethodInterceptor getInterceptor(Advisor advisor) {MethodBeforeAdvice advice = (MethodBeforeAdvice)advisor.getAdvice();return new MethodBeforeAdviceInterceptor(advice);}
}

         适配完成后,创建并执行调用链:

//创建并执行调用链 环绕通知+目标MethodInvocation methodInvocation = new ReflectiveMethodInvocation(null,target,Target.class.getMethod("foo"),new Object[0],Target.class,methodInterceptorList);methodInvocation.proceed();

        直接调用会报错,原因在于,调用链在调用时执行了不同的通知,有些通知的内部需要用到调用链的对象, 所以应该在最外层就将调用链准备完成。

        解决方法:在创建代理工厂时,将MethodInvocation放入当前线程:

proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);//准备把MethodInvocation放入当前线程
1.9、模拟实现调用链

        需要先明确一个概念:

MethodInterceptor接口和MethodInvocation接口的区别:

MethodInterceptor接口是 Spring AOP 中的拦截器接口,用于实现横切逻辑,可以在目标方法执行前后进行一些操作,例如记录日志、进行安全检查、性能监控等。

MethodInvocation接口代表了方法调用的上下文,它提供了对目标方法及其参数、目标对象等信息的访问,用于在代理对象中调用目标方法,并在调用前后执行拦截器的逻辑。

        准备一个目标类:

static class Target{public void foo(){System.out.println("foo");}
}

        准备环绕通知类:

static class Advice1 implements MethodInterceptor{@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("advice1.before");//调用链中的下一个通知,如果没有通知就会调用目标方法Object proceed = methodInvocation.proceed();System.out.println("advice1.after");return proceed;}}static class Advice2 implements MethodInterceptor{@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("advice2.before");Object proceed = methodInvocation.proceed();System.out.println("advice2.after");return proceed;}}

        创建一个类实现调用链接口(组合目标和环绕通知):在proceed() 方法中,牵涉到递归的思想,在执行目标方法前,首先应该调用完所有的通知(Advice1、Advice2)。

static class MyInvocation implements MethodInvocation{/*** 目标类*/private Object target;/*** 目标方法*/private Method method;/*** 目标方法的参数*/private Object[] args;/*** MethodInterceptor(环绕通知)的集合*/List<MethodInterceptor> methodInterceptorList;/*** 调用次数*/private int count = 1;public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {this.target = target;this.method = method;this.args = args;this.methodInterceptorList = methodInterceptorList;}@Overridepublic Method getMethod() {return method;}@Overridepublic Object[] getArguments() {return args;}/*** 调用环绕通知以及目标* @return* @throws Throwable*/@Overridepublic Object proceed() throws Throwable {if (count>methodInterceptorList.size()){//调用目标结束递归return method.invoke(target, args);}//调用次数+1MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);return methodInterceptor.invoke(this);}@Overridepublic Object getThis() {return target;}@Overridepublic AccessibleObject getStaticPart() {return method;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/2908.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32H7的LCD控制学习和应用

STM32H7的LCD控制 LTDC基础硬件框图LTDC时钟源选择LTDC的时序配置LTDC背景层、图层1、图层2和Alpha混合LTDC的水平消隐和垂直消隐LCD的DE同步模式和HV同步模式的区别区分FPS帧率和刷新率避免LTDC刷新撕裂感的解决方法 驱动示例分配栈的大小MPU和Cache配置初始化SDRAM初始化LCD应…

使用IOPaint实现图片擦除路人

IOPaint 是一个免费的开源的 inpainting/outpainting 工具&#xff0c;由最先进的 AI 模型提供支持。 IOPaint 中使用各种模型来修改图像&#xff1a; 擦除&#xff1a;删除任何不需要的物体、缺陷、水印、人物。修复&#xff1a;对图像的特定部分进行修改、添加新对象或替换…

vcontact2:病毒聚类(失败)

Bitbucket 安装 mamba create --name vContact2 biopython1.78 mamba install -c bioconda vcontact20.11.3vim ~/envs/vContact2/lib/python3.9/site-packages/vcontact2/exports/summaries.py 把 np.warnings.filterwarnings(ignore) 改成 import warnings warnings.filte…

Java虚拟机类加载机制详细总结

1、概述 Java虚拟机把描述类的数据从Class文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的Java类型&#xff0c;这个过程被称作虚拟机的类加载机制。 2、类加载的时机 一个类型从被加载到虚拟机内存中开始&#xff…

开曼群岛:Web3企业的乐园

开曼群岛&#xff1a;Web3企业的理想之地 开曼群岛&#xff0c;在数字革命中大放异彩。近年来&#xff0c;该地区成立的Web3企业数量显著增加&#xff0c;如果保持目前的发展速度&#xff0c;并持续优化立法&#xff0c;那么扩展的速度将无可限量。本文将探讨推动这一增长的关…

unity学习(88)——断线的原因--客户端队列死锁(头部异常为null)

客户端接受数据包的队列如下&#xff1a; 测试可以得到明显的溢出结果&#xff0c;肯定是有问题的&#xff01; 在catch中输出具体异常&#xff1a;Object reference not set to an instance of an object! 然后通过debug.log定位具体异常位置&#xff01;这也不算浪费时间&…

【QT学习】9.绘图,三种贴图,贴图的转换,不规则贴图(透明泡泡)

一。绘图的解释 Qt 中提供了强大的 2D 绘图系统&#xff0c;可以使用相同的 API 在屏幕和绘图设备上进行绘制&#xff0c;它主要基于QPainter、QPaintDevice 和 QPaintEngine 这三个类。 QPainter 用于执行绘图操作&#xff0c;其提供的 API 在 GUI 或 QImage、QOpenGLPaintDev…

互联网和嵌入式,哪个更吃香?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;显然&#xff0c;互联网更受青…

Mysql全局优化总结

Mysql全局优化总结 从上图可以看出SQL及索引的优化效果是最好的&#xff0c;而且成本最低&#xff0c;所以工作中我们要在这块花更多时间 服务端系统参数 官方文档&#xff1a;https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_connections…

MSE实现全链路灰度实践

技术架构包括以下基础设施和云服务&#xff1a; 1个地域&#xff1a;ACK集群、微服务应用、MSE实例均部署在同一地域下。 1个专有网络VPC&#xff1a;形成云上私有网络&#xff0c;确保核心云资源的网络环境&#xff0c;如容器服务ACK、微服务引擎MSE。 ACK集群&#xff1a;简单…

七星创客新零售系统:颠覆性商业模式的崛起

大家好&#xff0c;我是微三云周丽&#xff0c;今天给大家分析当下市场比较火爆的商业模式&#xff01; 小编今天跟大伙们分享什么是七星创客新零售系统&#xff1f; 随着经济的快速发展和科技的不断进步&#xff0c;商业模式的革新成为了企业发展的关键。在这个新旧动能转换、…

CentOS配置JDK8环境并安装配置neo4j

1. 工具安装 1. 安装wget yum install -y wget2. 安装vim yum install -y vim-enhanced2. 配置JDK 1. 安装JDK 先到官网下载jdk8的压缩包&#xff0c;点我传送&#xff0c;并将压缩包上传到虚拟机的/usr/local目录下。 cd /usr/local # 进入 /usr/local目录 tar -zxvf jdk-…

使用Perf诊断PostgreSQL性能问题

1 编译参数 使用perf获取完整的堆栈信息需要下面几个编译参数&#xff1a; -O0&#xff1a;编译器不做优化-ggdb3&#xff1a;增加了为GDB优化的调试信息&#xff0c;级别是3-g3&#xff1a;增加了调试信息&#xff0c;级别是3-fno-omit-frame-pointer&#xff1a;保留完成的…

「JavaEE」线程状态

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;JavaEE &#x1f387;欢迎点赞收藏加关注哦&#xff01; 线程状态 &#x1f349;start 和 run 的区别&#x1f349;终止线程&#x1f349;join & 阻塞状态&#x1f349;线程六大状态 &…

docker 基本命令

目录 一、docker 镜像操作命令 1.1.查询软件镜像 1.2.docker pull&#xff1a;下载镜像 1.3.docker push&#xff1a;上传镜像 1.4.docker images&#xff1a;查看本地镜像 1.5.docker inspect &#xff1a;获取镜像详细信息 1.6.docker tag&#xff1a;添加镜像标签 …

Git:使用conda命令切换虚拟环境

1. 问题 在win10电脑的Git中&#xff0c;无法使用conda list命令&#xff0c;报错&#xff08;bash&#xff1a;conda&#xff1a;command not found&#xff09;。也无法使用conda activate base命令激活虚拟环境&#xff0c;报错&#xff08;bash&#xff1a;conda&#xff…

共享单车数据分析与需求预测项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 项目背景 自动自行车共享系统是传统自行车租赁的新一代&#xff0c;整个会员、租赁和归还过程都变得自动化。通过这些系统&#xff0c;用户可以…

L1-098 再进去几个人 - java

L1-098 再进去几个人 代码长度限制 16 KB 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB 题目描述&#xff1a; 数学家、生物学家和物理学家坐在街头咖啡屋里&#xff0c;看着人们从街对面的一间房子走进走出。他们先看到两个人进去。时光流逝。他们又看到三个人出来。 物理…

锐捷网络闪耀高博会:智慧教育数字基座引领教育数字化新浪潮

4月15日,第61届中国高等教育博览会(简称“高博会”)在福州盛大开幕,在这次教育高端装备展示、教学改革成果交流、校企云集的行业盛会上,围绕构建智慧教育数字基座,锐捷网络携全场景智慧教育方案亮相,极简以太全光网、高校桌面云、5G多网融合等创新方案纷纷登场,吸引了众多观众驻…

【Python_PySide6学习笔记(三十六)】基于QGroupBox和QScrollArea实现带有滚动条的QGroupBox(分组框)

基于QGroupBox和QScrollArea实现带有滚动条的QGroupBox分组框 基于QGroupBox和QScrollArea实现带有滚动条的QGroupBox(分组框)前言正文1、创建QGroupBox并创建其内部布局2、创建QScrollArea并将QGroupBox设置为其内容3、将QScrollArea添加到主窗口的中心部件的布局中4、完整代码…