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() {
            @Override
            public 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");
    }

    @Transactional
    public 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 {
    @Override
    public 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() {
            @Override
            public boolean matches(Method method, Class<?> aClass) {
                //检查方法上是否加了注解
                MergedAnnotations annotations = MergedAnnotations.from(method);
                if (annotations.isPresent(Transactional.class)){
                    return true;
                }
                //检查类上是否加了注解 如果需要检查父类需要重写规则 MergedAnnotations.SearchStrategy.TYPE_HIERARCHY
                annotations = 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 {

    @Bean
    public Advisor advisor(MethodInterceptor advice){
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        return new DefaultPointcutAdvisor(pointcut,advice);
    }

    @Bean
    public MethodInterceptor advice(){
        return new MethodInterceptor() {
            @Override
            public 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{

        @Override
        public 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{

        @Override
        public 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;
        }

        @Override
        public Method getMethod() {
            return method;
        }

        @Override
        public Object[] getArguments() {
            return args;
        }

        /**
         * 调用环绕通知以及目标
         * @return
         * @throws Throwable
         */
        @Override
        public Object proceed() throws Throwable {
            if (count>methodInterceptorList.size()){
                //调用目标结束递归
                return method.invoke(target, args);
            }
            //调用次数+1
            MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
            return methodInterceptor.invoke(this);
        }

        @Override
        public Object getThis() {
            return target;
        }

        @Override
        public AccessibleObject getStaticPart() {
            return method;
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

山与路远程控制 一个基于electron和golang实现的远控软件

山与路远程控制 &#x1f3a5;项目演示地址 还在制作… ♻️项目基本介绍 山与路远程控制是基于electron(vue3)和golang实现的远程控制软件(项目界面主要模仿向日葵远程软件,如有侵权请告知),代码可能有点臃肿毕竟只花了一周左右写的无聊项目,如果对其感兴趣的大佬可以fork自…

【Qt 学习笔记】Qt常用控件 | 显示类控件 | Label的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 显示类控件 | Label的使用及说明 文章编号&#xff1a;Q…

根据表格该列数据的长度动态变化该列的宽度;

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、代码前言 在使用elementui的表格将数据展示出来时,我们想根据表格该列数据的长度动态变化该列的宽度; 1.看了一下elementui文档有一个 width 的属性,可用它来修改对应列。 2.那么我们需要拿到该列的所有数据去比较…

Go栈内存管理源码解读

基本介绍 栈内存一般是由Go编译器自动分配和释放&#xff0c;其中存储着函数的入参和局部变量&#xff0c;这些参数和变量随着函数调用而创建&#xff0c;当调用结束后也会随之被回收。通常开发者不需要关注内存是分配在堆上还是栈上&#xff0c;这部分由编译器在编译阶段通过…

使用Nexus搭建npm私服库

优质博文&#xff1a;IT-BLOG-CN 【1】下载nexus http://www.sonatype.com/download-oss-sonatype解压到本地即可&#xff1b; 【2】打开nexus-3.2.0-01-win64\nexus-3.2.0-01\bin&#xff1b;打开cmd&#xff08;必须使用cmd&#xff09; 执行nexus.exe /run&#xff1b;需要使…

数据结构 之 哈希表

&#x1f389;欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨ &#x1f389;感谢各位读者在百忙之中抽出时间来垂阅我的文章&#xff0c;我会尽我所能向的大家分享我的知识和经验&#x1f4d6; &#x1f389;希望我们在一篇篇的文章中能够共同进步&#xff01;&#xff01;&…

爆炸之linux-nacos2.0系列集群安装部署

一、环境配置 1、新建磁盘分区 fdisk /dev/vdb 2、创建文件系统 mkfs.xfs /dev/vdb13、创建挂载点&#xff1a; 在 / 目录下创建一个新的目录作为挂载点。/afc 目录 mkdir /afc4、挂载磁盘&#xff1a; 使用 mount 命令将磁盘挂载到新创建的目录。 mount /dev/vdb /afc5、…

2022年新华三杯决赛题目

2022年新华三杯决赛题目 拓扑图 请考生根据以上拓扑,自行在 HCL 中创建设备。 注意: 本拓扑图中的交换机与防火墙,所显示的接口标示比实际设备中的少了一位。如图中的 GE_0/1, 实际是 GE1/0/1。 配置需求 本网络模拟一个大型企业网络,需要使用 BGP/MPLS VPN 技术来隔离不同的 …

2024_GAMES101作业环境配置Mac(intel)_VSCode_Clion

目录 VSCodeClionCMakeList.txt VSCode brew install cmake 更换下载源为阿里云下载 opencv&#xff0c;不然会很慢 cd "$(brew --repo)" git remote -v cd "$(brew --repo)" git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git…

一举颠覆Transformer!最新Mamba结合方案刷新多个SOTA,单张GPU即可处理140k

还记得前段时间爆火的Jamba吗&#xff1f; Jamba是世界上第一个生产级的Mamba大模型&#xff0c;它将基于结构化状态空间模型 (SSM) 的 Mamba 模型与 transformer 架构相结合&#xff0c;取两种架构之长&#xff0c;达到模型质量和效率兼得的效果。 在吞吐量和效率等关键衡量指…

串联滞后校正及matlab实现

syms b_1 Z[]; P[0,-10,-5]; K1500; G_0zpk(Z,P,K); %G_0为校正前系统开环传递函数 [num,den]tfdata(G_0); %求解b&#xff0c;T [Gm,Pm,wg_0,wc_0]margin(G_0); %Pm为校正前的幅值裕度, gamma60; %确定校正后的相角裕度 Phi_c-6; %校正后的截止频率下Gc(s)的相角&#xff0c;一…

可视化看板有那么多应用场景,该如何快速搭建?可视化工具该如何选择?

在当今的信息化时代&#xff0c;数据已经成为了现代决策的核心。无论是企业战略规划、运营管理&#xff0c;还是个人生活决策&#xff0c;数据都扮演着至关重要的角色。随着数据分析技术和工具的不断进步&#xff0c;数据在决策中的作用将变得更加突出&#xff0c;对组织和个人…

IDEA中Docker相关操作的使用教程

一、引言 Docker作为当前最流行的容器化技术&#xff0c;极大地简化了应用的部署和管理。而IntelliJ IDEA作为一款强大的集成开发环境&#xff0c;也提供了对Docker的集成支持。本文将介绍如何在IDEA中配置和使用Docker&#xff0c;包括远程访问配置、服务连接、Dockerfile编写…

mysql基础4——增删改查表中的数据

添加数据 1&#xff09;插入数据记录 insert into demo.test1 (barcode,goodsname,price) values (0001, book, 3); 2&#xff09;插入查询结果 insert into table1 &#xff08;字段名&#xff09; select 字段名或值 from table2 where condition; //将表2中查询到的某条…

分享三个转换速度快、准确率高的视频转文字工具

想要直接将视频转换成文字&#xff0c;转换工具很重要&#xff01;给大家分享三个转换速度快、准确率高的视频转文字工具&#xff0c;轻松完成转换。 1.网易见外 https://sight.youdao.com/ 网易家的智能转写翻译服务工作站&#xff0c;网页端就可以直接使用&#xff0c;支持视…

Cesium快速上手3-Billboard/Label/PointPrimitives图元使用讲解

Billboard&Cesium.BillboardCollection 面朝屏幕的图片,用于添加图标等集合 特点&#xff1a; 始终面朝屏幕&#xff0c;即使旋转也面朝屏幕注意创建的集群对象 Cesium.BillboardCollection 先看展示效果 function setBillboardProperties() {Sandcastle.declare(setBi…

【c++】vector模拟实现与深度剖析

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 vector涉及到许多细节问题&#xff0c;比如双层深拷贝&#xff0c;迭代器失效等&#xff0c;本篇文章我们通过模拟实现来深度理解这块的内容 目录 1.基本框架2.构造和销毁3.元素访问4.获取…

STM32-DMA(软件出发、硬件触发)

DMA --为cpu减负 DMA简介 直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预&#xff0c;数据可以通过DMA快速地移动&#xff0c;这就节省了CPU的资源来做其他操作。两个DMA控制器有12个通道(DMA1有7个通道&#xff0c;DMA2…

广西桂林最大的模板厂——贵港市能强优品木业有限公司

贵港市能强优品木业有限公司是广西桂林地区最大的建筑模板厂家&#xff0c;拥有着25年的丰富生产经验。该公司以生产高品质的建筑覆膜板而闻名&#xff0c;其产品质量稳定&#xff0c;使用寿命长&#xff0c;深受广大客户的一致好评。 作为一家知名的建筑模板生产厂家&#xff…