零基础学JavaWeb开发(二十一)之 spring框架(4)

news2024/11/15 3:58:47

3、AOP详解

3.1、Aop常用术语

1.连接点(Join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。类中的哪些方法可以被增强,这些方法就被称作为连接点。

2.切点(PointCut): 可以插入增强处理的连接点,实际被增强的方法就称作为切入点

3.通知(Advice): AOP 框架中的增强处理,通知描述了切面何时执行以及如何执行增强处理, 实际增强的业务逻辑,该过程就可以称作为通知 前置、后置、环绕通知

4.切面(Aspect): 切面是通知和切点的结合。 把通知应用到的过程 就是为切面

5.引入(Introduction):允许我们向现有的类添加新的方法或者属性。

6.织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的代理对象

1.连接点 该类中哪些方法需要被增强,这些方法就可以称作连接点

3.切点 实际被增强的方法

2.通知 在方法前后执行代码

前置通知 调用方法之前处理...

后置通知 调用完该方法之后处理

环绕通知 在我们被代理方法前后执行

异常通知

最终通知

4.切面 把通知应用到的过程 就是为切面

3.2、Aop环境准备

1.Spring框架一般都是基于AspectJ实现AOP操作

(1)什么是AspectJ

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件,AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作.

2.基于AspectJ实现AOP

(1)基于xml配置文件实现

(2)基于注解方式(偏多的)

3.在项目工程目录引入AOP依赖

3.2.1、maven依赖

        <!-- aspectj支持 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.geronimo.bundles</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8_2</version>
        </dependency>
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

3.2.2、切入点表达式

具体那个类中的那个方法来实现增强

需要描述 该类中哪些方法是需要被增强-----切入点规则

execution( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]));

[权限修饰符

1.public.String.com.mayikt.service.MayiktService.addUser(..) --拦截的是

MayiktService类中addUser方法名称 所有参数 返回值String

2.* com.mayikt.service.MayiktService.*(..)拦截我们的

MayiktService类中的所有方法

3.* com.mayikt.service.*.*(..)拦截就是我们 com.mayikt.service.包下

的所有的类所有的方法。

(1)语法接口:
execution( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]));

//举例1:对com.mayikt.service.MayiktService类里面的 add() 进行增强 execution(*com.mayikt.service.MayiktService.add(..)); // * 表示所有, .. 表示参数列表

//举例2:对com.mayikt.service.MayiktService类里面的 所有方法 进行增强 execution(*com.mayikt.service.MayiktService.*(..));

//举例3:对com.mayikt.service.MayiktService所有类里面的 所有方法 进行增强 execution(*com.mayikt.service.MayiktService.*.*(..));

3.2.3、切入点表达式测试代码

package com.mayikt.service;

import org.springframework.stereotype.Component;

@Component
public class MayiktService {
    public String addMayikt() {
        System.out.println("addMayikt...");
        return "ok";
    }
}


package com.mayikt.proxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component
@Aspect// aop 代理
public class UserProxy {
    /**
     * 前置通知
     */
    @Before("execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void before() {
        System.out.println("前置通知...");
    }

    /**
     * 后置通知
     */
    @After("execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void after() {
        System.out.println("后通知...");
    }

    /**
     * 环绕通知
     */
    @Around(value = "execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知...");
        System.out.println("目标方法之前开始执行...");
        Object result = proceedingJoinPoint.proceed();
        System.out.println("目标方法之后开始执行...");
        return result;
    }

    //@AfterReturning表达后置通知/返回通知,表达方法返回结果之后执行
    @AfterReturning(value = "execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void afterReturning() {
        System.out.println("afterReturning");
    }

    //@AfterThrowing表达异常通知
    @AfterThrowing(value = "execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

}

3.2.4、开启springaop

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
      ">
    <!--开启注解方式 -->
    <context:component-scan base-package="com.mayikt"></context:component-scan>
    <!--开启 aspectj 生成代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
        ClassPathXmlApplicationContext app =
                new ClassPathXmlApplicationContext("spring_07.xml");
        MayiktService mayiktService = app.getBean("mayiktService", MayiktService.class);
        mayiktService.addMayikt();

3.3、spring框架种使用 cglib?jdk动态代理?

spring aop 底层基于 代理封装?

如果我们 被代理类 没有实现接口的情况下 则使用 cglib动态代理

如果我们被代理类 有实现接口的情况下 则使用 jdk动态代理

3.4、Aop实现统一日志输出

3.4.1、注解定义

package com.cjs.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemControllerLog {

    String description() default "";

    boolean async() default false;

}

package com.cjs.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemServiceLog {

    String description() default "";

    boolean async() default false;

}

3.4.2、定义一个类包含所有需要输出的字段

package com.cjs.example.service;

import lombok.Data;
import java.io.Serializable;

@Data
public class SystemLogStrategy implements Serializable {

    private boolean async;

    private String threadId;

    private String location;

    private String description;

    private String className;

    private String methodName;

    private String arguments;

    private String result;

    private Long elapsedTime;


    public String format() {
        return "线程ID: {}, 注解位置: {}, 方法描述: {}, 目标类名: {}, 目标方法: {}, 调用参数: {}, 返回结果: {}, 花费时间: {}";
    }

    public Object[] args() {
        return new Object[]{this.threadId, this.location, this.description, this.className, this.methodName, this.arguments, this.result, this.elapsedTime};
    }

}

3.4.3、定义切面

package com.cjs.example.aspect;

import com.alibaba.fastjson.JSON;
import com.cjs.example.annotation.SystemControllerLog;
import com.cjs.example.annotation.SystemRpcLog;
import com.cjs.example.annotation.SystemServiceLog;
import com.cjs.example.enums.AnnotationTypeEnum;
import com.cjs.example.service.SystemLogStrategy;
import com.cjs.example.util.JsonUtil;
import com.cjs.example.util.ThreadUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

@Aspect
public class SystemLogAspect {

    private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class);

    private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class);

    @Pointcut("execution(* com.ourhours..*(..)) && !execution(* com.ourhours.logging..*(..))")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object doInvoke(ProceedingJoinPoint pjp) {
        long start = System.currentTimeMillis();

        Object result = null;

        try {
            result = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            LOG.error(throwable.getMessage(), throwable);
            throw new RuntimeException(throwable);
        } finally {
            long end = System.currentTimeMillis();
            long elapsedTime = end - start;

            printLog(pjp, result, elapsedTime);

        }

        return result;
    }

    /**
     * 打印日志
     * @param pjp   连接点
     * @param result    方法调用返回结果
     * @param elapsedTime   方法调用花费时间
     */
    private void printLog(ProceedingJoinPoint pjp, Object result, long elapsedTime) {
        SystemLogStrategy strategy = getFocus(pjp);

        if (null != strategy) {
            strategy.setThreadId(ThreadUtil.getThreadId());
            strategy.setResult(JsonUtil.toJSONString(result));
            strategy.setElapsedTime(elapsedTime);
            if (strategy.isAsync()) {
                new Thread(()->LOG.info(strategy.format(), strategy.args())).start();
            }else {
                LOG.info(strategy.format(), strategy.args());
            }
        }
    }

    /**
     * 获取注解
     */
    private SystemLogStrategy getFocus(ProceedingJoinPoint pjp) {
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Object[] args = pjp.getArgs();
        String targetClassName = pjp.getTarget().getClass().getName();
        try {
            Class<?> clazz = Class.forName(targetClassName);
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (methodName.equals(method.getName())) {
                    if (args.length == method.getParameterCount()) {

                        SystemLogStrategy strategy = new SystemLogStrategy();
                        strategy.setClassName(className);
                        strategy.setMethodName(methodName);

                        SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.class);
                        if (null != systemControllerLog) {
                            strategy.setArguments(JsonUtil.toJSONString(args));
                            strategy.setDescription(systemControllerLog.description());
                            strategy.setAsync(systemControllerLog.async());
                            strategy.setLocation(AnnotationTypeEnum.CONTROLLER.getName());
                            return strategy;
                        }
                        SystemServiceLog systemServiceLog = method.getAnnotation(SystemServiceLog.class);
                        if (null != systemServiceLog) {
                            strategy.setArguments(JsonUtil.toJSONString(args));
                            strategy.setDescription(systemServiceLog.description());
                            strategy.setAsync(systemServiceLog.async());
                            strategy.setLocation(AnnotationTypeEnum.SERVICE.getName());
                            return strategy;
                        }
                        
                        return null;
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            LOG.error(e.getMessage(), e);
        }
        return null;
    }

}

3.4.4、配置

这里也可以用组件扫描,执行在Aspect上加@Component注解即可,但是这样的话有个问题。

就是,如果你的这个Aspect所在包不是Spring Boot启动类所在的包或者子包下就需要指定@ComponentScan,因为Spring Boot默认只扫描和启动类同一级或者下一级包。

package com.cjs.example.config;

import com.cjs.example.aspect.SystemLogAspect;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@AutoConfigureOrder(2147483647)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnClass(SystemLogAspect.class)
@ConditionalOnMissingBean(SystemLogAspect.class)
public class SystemLogAutoConfiguration {

    @Bean
    public SystemLogAspect systemLogAspect() {
        return new SystemLogAspect();
    }
}

3.4.5、自动配置(resources/META-INF/spring.factories)

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ourhours.logging.config.SystemLogAutoConfiguration

3.4.6、其它工具类

3.4.6.1. 获取客户端IP

package com.cjs.example.util;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class HttpContextUtils {

    public static HttpServletRequest getHttpServletRequest() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return servletRequestAttributes.getRequest();
    }

    public static String getIpAddress() {
        HttpServletRequest request = getHttpServletRequest();
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        }else if (ip != null && ip.length() > 15) {
            String[] ips = ip.split(",");
            for (int index = 0; index < ips.length; index++) {
                String strIp = (String) ips[index];
                if (!("unknown".equalsIgnoreCase(strIp))) {
                    ip = strIp;
                    break;
                }
            }
        }
        return ip;
    }
}

3.4.6.2. 格式化成JSON字符串

package com.cjs.example.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class JsonUtil {

    public static String toJSONString(Object object) {
        return JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);
    }

}

3.4.6.3. 存取线程ID

package com.cjs.example.util;

import java.util.UUID;

public class ThreadUtil {

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static String getThreadId() {
        String threadId = threadLocal.get();
        if (null == threadId) {
            threadId = UUID.randomUUID().toString();
            threadLocal.set(threadId);
        }
        return threadId;
    }

}

3.4.7、同时还提供静态方法

package com.cjs.example;

import com.cjs.example.util.JsonUtil;
import com.cjs.example.util.ThreadUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log {

    private static Logger LOGGER = null;

    private static class SingletonHolder{
        public static Log instance = new Log();
    }

    private Log(){}

    public static Log getInstance(Class<?> clazz){
        LOGGER = LoggerFactory.getLogger(clazz);
        return SingletonHolder.instance;
    }

    public void info(String description, Object args, Object result) {
        LOGGER.info("线程ID: {}, 方法描述: {}, 调用参数: {}, 返回结果: {}", ThreadUtil.getThreadId(), description, JsonUtil.toJSONString(args), JsonUtil.toJSONString(result));
    }

    public void error(String description, Object args, Object result, Throwable t) {
        LOGGER.error("线程ID: {}, 方法描述: {}, 调用参数: {}, 返回结果: {}", ThreadUtil.getThreadId(), description, JsonUtil.toJSONString(args), JsonUtil.toJSONString(result), t);
    }

}

3.4.8、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cjs.example</groupId>
    <artifactId>cjs-logging</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>cjs-logging</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <aspectj.version>1.8.13</aspectj.version>
        <servlet.version>4.0.0</servlet.version>
        <slf4j.version>1.7.25</slf4j.version>
        <fastjson.version>1.2.47</fastjson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3.4.9、工程结构

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

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

相关文章

详解动态规划01背包问题--JavaScript实现

对其他动态规划问题感兴趣的&#xff0c;也可以查看详解动态规划最少硬币找零问题--JavaScript实现详解动态规划最长公共子序列--JavaScript实现一开始在接触动态规划的时候&#xff0c;可能会云里雾里&#xff0c;似乎能理解思路&#xff0c;但是又无法准确地表述或者把代码写…

车辆占用应急车道识别抓拍系统 opencv

车辆占用应急车道识别抓拍系统通过opencvpython人工智能识别技术&#xff0c;对高速公路应急车道进行不间断实时监测&#xff0c;当监测到应急车道上有车辆违规占用时&#xff0c;立即告警提醒后台人员及时处理避。OpenCV的全称是Open Source Computer Vision Library&#xff…

【18】C语言 | 数组详解

目录 1、数组的格式 2、下列有什么区别 3、维数组的使用 4、*p 和 int* p arr 的含义 5、二维数组&#xff1a;打印一个二维数组 6、二维数组在数组中的存储 7、数组作为函数参数 8、数组名是数组首元素的地址 1、数组的格式 数组是一组相同类型元素的集合。 数组的创…

20230123英语学习

Interesting Studies to Spark Your Interest in the Research Field 科研也可以很有趣&#xff01;盘点那些好玩的研究 When it comes to picking studies worth reading, what scientists deem an interesting science article might be perceived differently by a person…

【Datewhale一起吃瓜 Task2】啃瓜第三章

文章目录线性模型关键&#xff1a;找到合适的w和b如何找到合适的 w和b&#xff1f;偏导为什么可以&#xff1f;推广线性模型 任务&#xff1a;找出一条线能够对数据进行划分或预测趋势 关键&#xff1a;找到合适的w和b 更适合于连续性的数值&#xff0c;如果数据是离散的如色…

AcWing 1020. 潜水员(二维费用背包)

一、问题 二、思路 这道题其实很容易看出是一个二维费用背包的变形&#xff0c;如果我们将氧气看作体积&#xff0c;将氮气看作价值的话&#xff0c;这道题就变成了从iii个物品里面选&#xff0c;体积至少为mmm&#xff0c;价值至少为nnn的条件下&#xff0c;所携带的物品的最…

Maplab:一个用于视觉惯性建图和定位研究的开源框架

摘要 鲁棒且精确的视觉惯性估计是当今机器人领域的重要挑战。能够用先验地图(prior map)进行定位(localize)并获得准确且无漂移的姿态估计&#xff0c;可以推动该系统的适应性。然而&#xff0c;目前大多数可用的解决方案都集中在单次使用&#xff0c;缺乏定位能力或端到端流水…

Java基本类型和包装类什么情况下判断相等(“==“或“equals“)?

[1] 先讨论一个面试题 int a 1; Integer b 1; Integer c new Integer(1); Integer d Integer.valueOf(1); int e d; int f d.intValue();请问以下式子的值&#xff1f;为什么&#xff1f; a b // true a c // true b c // false[2] ""与"equals"…

C++设计新思维(泛型编程与设计模式之应用)之常整数映射为类别(2.4)

技术 模板偏特化&#xff0c;模板全特化 应用 1、有必要根据一个编译期常数调用一个或数个不同的函数 2、有必要在编译器实施"分派"(dispatch) 例子 如果打算在执行期进行分派(dispatch)&#xff0c;可使用if-else或switch语句。大部分时候其执行期成本都微不足…

windows权限维持方法详解

权限维持在获取服务器权限后&#xff0c;为了防止服务器管理员发现和修补漏洞而导致对服务器权限的丢失&#xff0c;测试人员往往需要采取一些手段来实现对目标服务器的持久化访问。权限持久化&#xff08;权限维持&#xff09;技术就是包括任何可以被测试人员用来在系统重启、…

Allegro如何通过飞线判断同一个网络连接位是否在同一直线操作指导

Allegro如何通过飞线判断同一个网络连接位是否在同一直线操作指导 Allegro可以通过飞线判断同一个网络的连接位是否在同一条直线上,如下图 当飞线是类似三角形的时候,可以判定两个连接点位是在同一条直线上 具体设置操作如下 选择Setup选择Design Parameter

React源码之render过程中发生了什么?

理解JSX 对于我们直接书写jsx语法&#xff0c;我们的浏览器是不理解我们这种语法的&#xff0c;所以需要babel来去转义&#xff0c;那么可以通过plugin-transform-react-jsx来转译jsx语法&#xff0c;使得浏览器可以识别我们的Jsx语法&#xff0c;例如&#xff1a; <div&g…

关于xshell简答使用

xshell是一个远程工具下载 官网地址&#xff1a;https://www.xshell.com/zh/xshell/直接下载即可~选择免费的授权页面 下载 不然要收费 也不要用盗版。运行后的xshell界面我们要建立服务器的连接点击加号 新建连接 即可 输入连接地址后 要输入 账号 和 秘密 OK 好了 可以使用了…

【Linux】Linux编译器gcc、g++

文章目录&#x1f3aa; Linux编译器gcc、g&#x1f680;1. 程序的编译⭐1.1 预处理⭐1.2 编译⭐1.3 汇编⭐1.4 链接⭐1.5 gcc/g常用指令&#x1f680;2. 函数库⭐2.1 静态库⭐2.2 动态库⭐2.3 动静态库对比&#x1f3aa; Linux编译器gcc、g 我们这以gcc为例&#xff0c;g编译器…

【学习笔记之数据结构】树的认识

树的概念&#xff1a; 树是一种非线性的数据结构&#xff0c;它由n&#xff08;n可以为0&#xff09;个有限的节点组成一个具有层次关系的结合。之所以把它称之为树是因为它的逻辑结构形似一个倒着的树。它的根在上面&#xff0c;叶子在下面。   有一个特殊的节点&#xff0c…

WebRTC系列-Qos系列之接收NACK

文章目录 1. 主要调用流程1.1 RTCP和RTP包区分1.2 查找丢失包2. RTX在文章 WebRTC系列-Qos系列之RTP/RTCP源码分析-RTP/RTCP包解析-3的2.3.3章节介绍了NACK包在WebRTC中解析方式及RFC规定协议的定义。 这篇文章开始,详细分析其接受RTCP包到解析nack的处理流程; 1. 主要调用流…

Kotlin中空安全操作符,异常处理和自定义异常,以及先决条件函数详解

博主前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住也分享一下给大家 &#x1f449;点击跳转到教程 一、Kotlin的可空性 null 在java中我们司空见惯的空指针异常NullPointerException,带给了我们很多麻烦。 Kotlin作为更强…

STM32编写OLED显示屏驱动

文章目录前言一、OLED的器件地址二、编写写数据和写命令函数三、编写初始化OLED屏幕函数四、其他功能函数编写五、显示字符和数字函数编写总结前言 这篇文章将带大家学习如何编写OLED显示屏的驱动程序。这里我使用的是HAL库的硬件IIC&#xff0c;OLED屏幕使用的是SSD1306的。 …

sbt编程语言scala的构建工具配置及项目构建(附带网盘下载)

SBT简介 SBT 是 Scala 的构建工具&#xff0c;全称是 Simple Build Tool&#xff0c; 类似 Maven 或 Gradle。 Java可以用Maven快速构建项目&#xff0c;scala用SBT快速构建一个Scala项目。 sbt下载官网 百度网盘链接&#xff1a;https://pan.baidu.com/s/1eJkdWndZ0izcd3w…

Elasticsearch7.8.0版本高级查询——桶聚合查询文档

目录一、初始化文档数据二、桶聚合查询文档2.1、概述2.2、terms 聚合&#xff0c;分组统计的示例2.3、在 terms 分组下再进行聚合的示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求…