spring之aop底层实现

news2025/1/16 17:06:56

1.aop之ajc增强

  • 什么是ajc增强?

    • ajc是aop的另外一种实现, 通过aspectj编码器来改动class源文件实现aop

      image-20221230223934157

image-20221230223945914

image-20221230224031682

image-20221230224108681

2.aop之agent增强

  • 什么是agent增强?

    • agent是aop的另外一种实现,是通过类加载时改动class类

      image-20221230224306281

image-20221230224423572

3.aop之proxy增强-jdk代理

  • aop最重要的实现基于代理的实现
public class JdkProxyDemo {
    interface Foo{
        void foo();
    }
    static class Target implements Foo{

        @Override
        public void foo() {
            System.out.println("target foo");
        }
    }
    // jdk 只能针对接口代理
    public static void main(String[] args) {
        Target target = new Target();
        ClassLoader loader = JdkProxyDemo.class.getClassLoader(); // 用来加载运行时动态生成的字节码
        Foo foo = (Foo)Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (p, method, args1) -> {
            System.out.println("before...");
            // 反射调用 方法.invoke(目标、参数)
            Object invoke = method.invoke(target, args1);
            System.out.println("after...");
            return invoke;
        });
        foo.foo();
    }
}

4.aop之proxy增强-cglib代理

public class CglibProxyDemo {

    static class Target{
        public void foo(){
            System.out.println("target foo");
        }
    }
    // cglib的目标对象和方法不能被final修饰,因为代理类和目标类的父子关系,jdk代理类和目标类的同级关系
    public static void main(String[] params) {
        Target target = new Target();
        Target target1 = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {
            System.out.println("before...");
            //   method.invoke(target,args);    // 用方法反射目标
            //   methodProxy.invokeSuper(p,args); // methodProxy内部没有调用反射,需要代理
            Object invoke = methodProxy.invoke(target, args); // methodProxy内部没有调用反射,需要目标
            System.out.println("after..");
            return invoke;
        });
        target1.foo();
    }
}

5.jdk代理原理实现

public class ProxySourceDemo {

    interface Foo {
        void foo();
        int  bar();
    }

    static class Target implements Foo {

        @Override
        public void foo() {
            System.out.println("target foo");
        }

        @Override
        public int  bar() {
            System.out.println("target bar");
            return 100;
        }
    }

    interface InvocationHandler {
        Object invoke(Object proxy, Method method,Object[] args) throws Throwable;
    }

    public static void main(String[] args) {
        Foo foo = new $Proxy0(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("代理增强");
                Object result = method.invoke(new Target(), args);
                return result;
            }
        });
        foo.foo();
        foo.bar();
    }
}

public class $Proxy0 implements ProxySourceDemo.Foo {
    private ProxySourceDemo.InvocationHandler invocationHandler;

    public $Proxy0(ProxySourceDemo.InvocationHandler invocationHandler) {
        this.invocationHandler = invocationHandler;
    }

    @Override
    public void foo() {
        try {
            invocationHandler.invoke(this,foo,new Object[0]);
        } catch (RuntimeException | Error e) {
            throw e;
        }catch (Throwable e){
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public int bar() {
        try {
           return (int) invocationHandler.invoke(this,bar,new Object[0]);
        } catch (RuntimeException | Error e) {
            throw e;
        }catch (Throwable e){
            throw new UndeclaredThrowableException(e);
        }
    }

    static Method foo;
    static Method bar;
    static {
        try {
            foo = ProxySourceDemo.Foo.class.getMethod("foo");
            bar = ProxySourceDemo.Foo.class.getMethod("bar");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
}
  • jdk反射优化 前16次都是通过反射调用 性能比较低,17次的时候已经把反射调用变成正常调用,代价是为了一个方法的反射调用生成了一个代理类

    image-20221231105646197

6.cglib代理原理实现

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

    public void save1(int i){
        System.out.println("save1(int)");
    }

    public void save2(long i){
        System.out.println("save2(long)");
    }
}

public class Proxy extends Target{

    private MethodInterceptor methodInterceptor;

    public Proxy(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    static Method save0;
    static Method save1;
    static Method save2;
    static MethodProxy save0Proxy;
    static MethodProxy save1Proxy;
    static MethodProxy save2Proxy;

    static {
        try {
            save0 = Target.class.getMethod("save0");
            save1 = Target.class.getMethod("save1",int.class);
            save2 = Target.class.getMethod("save2",long.class);
            // c1=目标类 c2=代理类
            save0Proxy = MethodProxy.create(Target.class,Proxy.class,"()V","save0","save0Super");
            save1Proxy = MethodProxy.create(Target.class,Proxy.class,"(I)V","save1","save1Super");
            save2Proxy = MethodProxy.create(Target.class,Proxy.class,"(J)V","save2","save2Super");
        } catch (NoSuchMethodException e) {
            throw  new UndeclaredThrowableException(e);
        }
    }
    // 原始功能的方法
    public void save0Super(){
        super.save0();
    }

    public void   save1Super(int i){
        super.save1(i);
    }

    public void save2Super(long l){
         super.save2(l);
    }

    // 增强功能的方法
    @Override
    public void save0() {
        try {
            methodInterceptor.intercept(this,save0,new Object[0],save0Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save1(int i) {
        try {
            methodInterceptor.intercept(this,save1,new Object[]{i},save1Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save2(long l) {
        try {
            methodInterceptor.intercept(this,save2,new Object[]{l},save2Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}


public class CglibSourceDemo {
    public static void main(String[] args) {
        Proxy proxy = new Proxy(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("cglib代理增强");
                Object invoke = method.invoke(new Target(),args);
                return invoke;
            }
        });
        proxy.save0();
        proxy.save1(1);
        proxy.save2(1l);
    }
}

image-20221231111827394

  • 无反射调用原理

    • 首次使用MethodProxy方法时就会创建代理Class和目标Class类,因为MethodProxy记录了方法签名(方法名,入参,返回参数)所以getIndex就能获取到方法的编号,之后MethodProxy调用invoke就会间接调用TargetFastClass中的invoke方法,传入方法编号以及目标对象和参数完成无需反射调用

    image-20221231143457020

image-20221231144504846

image-20221231144357656

  • 对比jdk代理的优势?
    • jdk调16次,调17次的时候会针对一个方法产生一个代理类,使之后的反射变成无调用反射,cglib是methodProxy一调用就会产生代理不需要16次调用,一上来就无需反射,cglib一个代理类会对应2个FastClass(一个代理class,一个目标class)一个代理类可以匹配多个方法

7.spring的代理选择

public class SpringProxyDemo {
    public static void main(String[] args) {
        /*
               两个切面概念:
             aspect=
                 通知1(advice) + 切点1 (pointcut)
                 通知2(advice) + 切点2 (pointcut)
                 通知3(advice) + 切点3 (pointcut)

             advisor = 更细粒度点切面,包含一个通知和切点
         */

        // 准备好切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        // 准备好通知
        MethodInterceptor advice = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("before..");
                Object proceed = invocation.proceed();
                System.out.println("after...");
                return proceed;
            }
        };
        // 准备好切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
        // 创建代理
        /*
              ProxyConfig
              proxyTargetClass:false 目标实现接口 使用jdk实现
              proxyTargetClass:false 目标没有实现接口 使用cglib实现
              proxyTargetClass:true  总是使用cglib实现
         */
        Target1 target1 = new Target1();
        ProxyFactory factory = new ProxyFactory();
        factory.addAdvisor(advisor);
        factory.setTarget(target1);
//        factory.setInterfaces(target1.getClass().getInterfaces());
        factory.setProxyTargetClass(true);
        T1 t1 = (T1) factory.getProxy();
        System.out.println(t1.getClass());
        t1.foo();
        t1.bar();
    }

    interface T1 {
        void foo();

        void bar();
    }

    static class Target1 implements T1 {

        @Override
        public void foo() {
            System.out.println("foo....");
        }

        @Override
        public void bar() {
            System.out.println("bar....");
        }
    }
}

8.切点匹配

  • 使用 AspectJExpressionPointcut.setExpression(“execution(* foo())”)或者@annatation只能匹配方法无法匹配类上或者继承链路上是否使用注解.

image-20221231162548579

9.AnnotationAwareAspectJAutoProxyCreator

package com.liubo.spring.a09;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator;
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * @author lb
 * @date 2022/12/31 4:36 下午
 */
public class AnnotationAwareAspectJAutoProxyCreatorDemo {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        context.registerBean("aspect", Aspect1.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        /*
        AnnotationAwareAspectJAutoProxyCreator作用:
                1.找到容器中所有的切面,如果是高级切面会转换成低级切面
                2.根据切面创建代理对象
         */
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
				/*
					 创建--->(*)依赖注入--->初始化(*)
					 a.代理的创建时机
					 			1.初始化之后(无循环依赖)
					 			2.实例创建后,依赖注入前(有循环依赖时)并暂存于二级缓存
					 b.依赖注入与初始化不应该被增强,任应被施加于原始对象
				*/
        context.refresh();
        AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        /*
         creator.findEligibleAdvisors(Target1.class, "target1"):找到有资格的Advisors
         creator.wrapIfNecessary(new Target1(),"target1","target1"):内部调用findEligibleAdvisors,只要返回集合不为空,则表示需要创建代理
         */
        Method method = AbstractAdvisorAutoProxyCreator.class.getDeclaredMethod("findEligibleAdvisors", Class.class, String.class);
        method.setAccessible(true);
        List<Advisor> advisors = (List<Advisor>) method.invoke(creator, Target1.class, "target1");
        for (Advisor advisor : advisors) {
            System.out.println(advisor);
        }

        Method method2 = AbstractAutoProxyCreator.class.getDeclaredMethod("wrapIfNecessary", Object.class, String.class, Object.class);
        method2.setAccessible(true);
        Object o1 = method2.invoke(creator, new Target1(), "target1", "target1");
        System.out.println(o1.getClass());
        Object o2 = method2.invoke(creator, new Target2(), "target2", "target2");
        System.out.println(o2.getClass());

    }

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

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

    @Aspect   // 高级切面类
    static class Aspect1 {
        @Before("execution(* foo())")
        public void before() {
            System.out.println("before1 ....");
        }

        @Before("execution(* foo())")
        public void after() {
            System.out.println("after1 ....");
        }
    }

    @Configuration
    static 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 invocation) throws Throwable {
                    System.out.println("before2..");
                    Object proceed = invocation.proceed();
                    System.out.println("after2...");
                    return proceed;
                }
            };
        }
    }
}
  • 高级切面转低级切面过程

    image-20221231205015826

10.统一转为环绕通知

public class A18 {

    static 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");
        }

        @AfterReturning("execution(* foo())")
        public void afterReturning() {
            System.out.println("afterReturning");
        }

        @AfterThrowing("execution(* foo())")
        public void afterThrowing(Exception e) {
            System.out.println("afterThrowing " + e.getMessage());
        }

        @Around("execution(* foo())")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            try {
                System.out.println("around...before");
                return pjp.proceed();
            } finally {
                System.out.println("around...after");
            }
        }
    }

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

    @SuppressWarnings("all")
    public static void main(String[] args) throws Throwable {

        AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
        // 1. 高级切面转低级切面类
        List<Advisor> list = new ArrayList<>();
        for (Method method : Aspect.class.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Before.class)) {
                // 解析切点
                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);
                list.add(advisor);
            } else if (method.isAnnotationPresent(AfterReturning.class)) {
                // 解析切点
                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);
                list.add(advisor);
            } else if (method.isAnnotationPresent(Around.class)) {
                // 解析切点
                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);
                list.add(advisor);
            }
        }
        for (Advisor advisor : list) {
            System.out.println(advisor);
        }

        /*
            @Before 前置通知会被转换为下面原始的 AspectJMethodBeforeAdvice 形式, 该对象包含了如下信息
                a. 通知代码从哪儿来
                b. 切点是什么
                c. 通知对象如何创建, 本例共用同一个 Aspect 对象
            类似的通知还有
                1. AspectJAroundAdvice (环绕通知)
                2. AspectJAfterReturningAdvice
                3. AspectJAfterThrowingAdvice (环绕通知)
                4. AspectJAfterAdvice (环绕通知)
         */

        // 2. 通知统一转换为环绕通知 MethodInterceptor
        /*

            其实无论 ProxyFactory 基于哪种方式创建代理, 最后干活(调用 advice)的是一个 MethodInvocation 对象
                a. 因为 advisor 有多个, 且一个套一个调用, 因此需要一个调用链对象, 即 MethodInvocation
                b. MethodInvocation 要知道 advice 有哪些, 还要知道目标, 调用次序如下

                将 MethodInvocation 放入当前线程
                    |-> before1 ----------------------------------- 从当前线程获取 MethodInvocation
                    |                                             |
                    |   |-> before2 --------------------          | 从当前线程获取 MethodInvocation
                    |   |                              |          |
                    |   |   |-> target ------ 目标   advice2    advice1
                    |   |                              |          |
                    |   |-> after2 ---------------------          |
                    |                                             |
                    |-> after1 ------------------------------------
                c. 从上图看出, 环绕通知才适合作为 advice, 因此其他 before、afterReturning 都会被转换成环绕通知
                d. 统一转换为环绕通知, 体现的是设计模式中的适配器模式
                    - 对外是为了方便使用要区分 before、afterReturning
                    - 对内统一都是环绕通知, 统一用 MethodInterceptor 表示

            此步获取所有执行时需要的 advice (静态)
                a. 即统一转换为 MethodInterceptor 环绕通知, 这体现在方法名中的 Interceptors 上
                b. 适配如下
                  - MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
                  - AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
         */
        Target target = new Target();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE); // 准备把 MethodInvocation 放入当前线程
        proxyFactory.addAdvisors(list);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);
        for (Object o : methodInterceptorList) {
            System.out.println(o);
        }

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



        /*
            学到了什么
                a. 无参数绑定的通知如何被调用
                b. MethodInvocation 编程技巧: 拦截器、过滤器等等实现都与此类似
                c. 适配器模式在 Spring 中的体现
         */

    }
}

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

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

相关文章

Mac系统入门之电脑卡死怎么办

当你兴冲冲的从菜鸡驿站提回来一台新的电脑,你欣喜若狂,迫不及待的拆开快递箱,里面是一台苹果电脑,这时,你不禁抓耳挠腮:Mac系统怎么用啊? 下面,这篇专栏教你如何入门Mac系统 https://blog.csdn.net/cyyyyds857/category_12163999.html –––––前言 你正兴致勃勃的写着…

mysql中字符串拼接、填充和切片

一、本文主要结构 在编程过程往往会遇到&#xff0c;多个字符串需要进行拼接或者填充固定值或者截取部分数据&#xff0c;本文主要实战下面四个函数 concat(str1, str2,…)&#xff1a;字符串进行拼接 lpap&#xff08;&#xff09;&#xff1a;左边填充 rpad&#xff08;&…

【C语言】指针经典题分析

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;《初识C语言》 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、指针与数组经典题解析二、经…

创新的概念、设计和生产鞋类和鞋类软件丨Jevero及Botcha 3D功能简介

Jevero功能简介 重新定义鞋类发展 Jevero是图案工程师、鞋类开发人员和设计师的优秀支持。从设计到生产都在一个工具中完成。 产品功能及优势 01、更快的开发&#xff0c;缩短上市时间 Jevero使您的图案工程师、鞋类开发人员、工业设计师之间能够进行协作。利用Rhino平台产…

两数相加 java语言

leetcode地址&#xff1a;两数相加描述&#xff1a;给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。你可以假设除…

DevData Talks | 张乐、茹炳晟、应阔浩、任晶磊:研发效能实践的2022年复盘和展望

跌宕起伏的 2022 年已经成为过去时。在这一年&#xff0c;我们既看到外部环境变幻莫测&#xff0c;也看到研发效能行业沉下心来稳步发展&#xff0c;从宏大的概念和价值&#xff0c;转向具体的问题&#xff0c;和务实、可行动的解决方案。 在新一年的开端上回望&#xff0c;20…

靶机测试CyNix笔记

靶机测试CyNix笔记 靶机描述 Level: Intermediate-HardUser flag: user.txtRoot flag: root.txtDescription: It’s a Boot2Root machine. The machine is VirtualBox compatible but can be used in VMWare as well (not tested but it should work). The DHCP will assign …

webpack中模块加载器Loader、插件plugins、optimization属性

目录 模块加载器&#xff08;Loader&#xff09; 导入css文件 加载图片 方法一 方法二 转换es6&#xff08;向下兼容es5&#xff09; html代码组件导入导出 导入less文件 自定义loader&#xff08;Markdown文件加载器&#xff09; markdown-loader.js文件 webpack.c…

【Linux】程序的翻译过程(图示详解)

因为淋过雨&#xff0c;所以懂的为别人撑伞&#xff1b;因为迷茫过&#xff0c;所以懂得为别人指路。 我们都知道写好代码后&#xff0c;编译器会帮助我们把代码生成可执行程序&#xff0c;细加了解又会知道程序的生成又分为四步&#xff1a;预处理、编译、汇编、链接。那么这四…

STM32MP157驱动开发——Linux IIO驱动(上)

STM32MP157驱动开发——Linux IIO驱动&#xff08;上 &#xff09;0.前言一、IIO 子系统简介1.iio_dev 结构体2.iio_dev 申请与释放3.iio_dev 注册与注销4.iio_info5.iio_chan_spec二、驱动开发1. ICM20608 的 IIO 驱动框架搭建2.IIO 设备申请与初始化3.基于以上驱动框架开发 I…

[JavaEE初阶] 线程安全问题的原因和解决方案

努力努力,月薪过亿!!! 格局打开~~~ 文章目录前言1. 线程安全问题的概念2. 线程安全问题的原因3. 线程安全问题解决--加锁3. synchronized4. 死锁4.1 产生死锁的情况4.3 产生死锁的必要条件4.4 避免死锁的方法前言 线程安全这里可能会出道面试题,在日常工作中也是很重要的内容.…

MathType公式对齐不正确

MathType公式对齐不正确1.软件环境⚙️2.问题描述&#x1f50d;3.解决方法&#x1f421;4.1.通过标尺对齐4.2.通过输入具体的制表符位置对齐1.软件环境⚙️ Windows10 教育版64位 Word 2021 MathType 7 2.问题描述&#x1f50d; 在使用Word写论文的时候&#xff0c;总是避免不…

JavaScript 模块:理解模块系统

前言 现代JavaScript开发毋庸置疑会遇到代码量大和广泛使用第三方库的问题。解决这个问题的方案通常需要把代码拆分成很多部分&#xff0c;然后再通过某种方式将它们连接起来。 在ECMAScript 6模块规范出现之前&#xff0c;虽然浏览器原生不支持模块的行为&#xff0c; 但也迫…

ssh连接ubuntu报错

记录问题&#xff1a;1我在本机windows用ssh rootubuntu连接失败 显示端口21啥的2 打开Ubuntu系统&#xff0c;输入ps -e|grep ssh&#xff0c;发现只有agent&#xff0c;没有server3 安装ssh server&#xff0c;输入sudo apt-get install openssh-server&#xff0c;发现报错信…

仅需一个注解,实现 SpringBoot 项目中的隐私数据脱敏!

这两天在整改等保测出的问题&#xff0c;里面有一个“用户信息泄露”的风险项&#xff08;就是后台系统里用户的一些隐私数据直接明文显示了&#xff09;&#xff0c;其实指的就是要做数据脱敏。数据脱敏&#xff1a;把系统里的一些敏感数据进行加密处理后再返回&#xff0c;达…

一键自动化 | Salesforce发布Automation Anywhere自动化组合!

2022年12月1日&#xff0c;Salesforce推出了一个新的Automation Everywhere Bundle&#xff0c;以加速端到端的工作流编排&#xff08;Workflow Orchestration&#xff09;、跨系统自动化&#xff0c;以及在任何地方嵌入数据和AI驱动的工作流。 该捆绑包完全集成到Salesforce F…

acwing第84场周赛(4788,4789,4890)题解

4788. 最大数量 某商场在一天中一共来了 nn 个客人。 每个客人进入商场的具体时刻&#xff08;精确到分钟&#xff09;已知。 请你计算并输出在同一时刻&#xff08;精确到分钟&#xff09;进入商场的最大客人数量。 输入格式 第一行包含整数 nn。 接下来 nn 行&#xff…

二叉搜索树比起二叉树又有什么不一样呢?

二叉搜索树比起二叉树又有什么不一样呢&#xff1f;&#x1f3d0;什么是二叉搜索树&#x1f3d0;二叉搜索树的实现&#x1f3c0;节点类:&#x1f3c0;构造函数&#x1f3c0;析构函数&#x1f3c0;插入insert⚽非递归版本⚽递归版本&#x1f3c0;查找find⚽非递归版本⚽递归版本…

spring boot 八:SpringBoot响应返回xml数据

spring boot 八&#xff1a;SpringBoot响应返回xml数据 1 前言 根据DispatcherServlet源码分析&#xff0c;研究SpringBoot的Controller返回xml数据的一些方法&#xff0c;包含单独配置和全局配置返回xml数据两种方式。 依赖的SpringBoot版本&#xff1a; <parent>&l…

u盘有病毒怎么办?修复U盘,3个方法解决

U盘和外部的驱动器相比&#xff0c;它的体积更小&#xff0c;携带更加方便&#xff0c;可以轻松地与他人分享文件。虽然U盘使用很方便&#xff0c;但是有时会出现中病毒的情况。u盘有病毒怎么办&#xff1f;如果您也受到此问题的影响&#xff0c;我们可以提供一种有效的方法来修…