spring-aop源码分析(3)完结_执行流程分析

news2024/11/15 12:10:51

本文详细介绍Spring AOP的执行阶段流程。

Cglib代理的代理拦截逻辑在DynamicAdvisedInterceptor中,JDK代理的拦截逻辑在JdkDynamicAopProxy中,本文将从这两个类入手分析Spring AOP的执行阶段流程。

DynamicAdvisedInterceptor

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {

	private final AdvisedSupport advised;

	public DynamicAdvisedInterceptor(AdvisedSupport advised) {
		this.advised = advised;
	}

	@Override
	public Object intercept(
        Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // ...
    }

intercept方法核心逻辑

Cglib实现的动态代理都是通过这个类的intercept方法进行拦截的:

  • 该类在创建时需要传入一个advised对象,这个对象内部封装着所有的Advisor集
  • 将advised对象中封装的Advisor集转换成MethodInterceptor链
  • 构建一个CglibMethodInvocation并执行拦截器链

代码:

Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
	if (this.advised.exposeProxy) {
		// Make invocation available if necessary.
		oldProxy = AopContext.setCurrentProxy(proxy);
		setProxyContext = true;
	}
	// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
	target = targetSource.getTarget();
	Class<?> targetClass = (target != null ? target.getClass() : null);
    // 1. 将Advisor集转换成MethodInterceptor链
	List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
	Object retVal;
	// Check whether we only have one InvokerInterceptor: that is,
	// no real advice, but just reflective invocation of the target.
	if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
		Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
		retVal = methodProxy.invoke(target, argsToUse);
	} else {
		// We need to create a method invocation...
        // 2. 构建一个CglibMethodInvocation并执行拦截器链
		retVal = new CglibMethodInvocation(
            proxy, target, method, args, targetClass, chain, methodProxy).proceed();
	}
	retVal = processReturnType(proxy, target, method, retVal);
	return retVal;
} finally {
	// 略
}

Advisor集

我们先debug看一下advised对象的Advisor集:

在这里插入图片描述

可以看到advisors的每个对象封装了如下通知对象:

  • before - AspectJMethodBeforeAdvice对象
  • after - AspectJAfterAdvice对象
  • afterReturn - AspectJAfterReturningAdvice对象
  • afterThrowing - AspectJAfterThrowingAdvice对象

getInterceptorsAndDynamicInterceptionAdvice方法

将advised对象的Advisor集转为MethodInterceptor集:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
    Method method, @Nullable Class<?> targetClass) {
	MethodCacheKey cacheKey = new MethodCacheKey(method);
	List<Object> cached = this.methodCache.get(cacheKey);
	if (cached == null) {
		cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
				this, method, targetClass);
		this.methodCache.put(cacheKey, cached);
	}
	return cached;
}

// advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
		Advised config, Method method, @Nullable Class<?> targetClass) {

	// This is somewhat tricky... We have to process introductions first,
	// but we need to preserve order in the ultimate list.
	AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
	Advisor[] advisors = config.getAdvisors();
	List<Object> interceptorList = new ArrayList<>(advisors.length);
	Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
	Boolean hasIntroductions = null;

	for (Advisor advisor : advisors) {
        // AOP执行的是这个分支,我们重点看这个分支
		if (advisor instanceof PointcutAdvisor) {
			// Add it conditionally.
			PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
			if (config.isPreFiltered() || 
                pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
				// 获取切入点匹配器对象
				MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
				boolean match;
				if (mm instanceof IntroductionAwareMethodMatcher) {
					if (hasIntroductions == null) {
						hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
					}
					match = ((IntroductionAwareMethodMatcher) mm).matches(
                        method, actualClass, hasIntroductions);
				} else {
                    // 切入点匹配
					match = mm.matches(method, actualClass);
				}
				if (match) {
                    // 这里把Advisor对象转为MethodInterceptor集
                    // 代码不复杂,此处不做记录了
					MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
					if (mm.isRuntime()) {
                        // 执行这个分支
                        // 使用InterceptorAndDynamicMethodMatcher类型封装表示在后续执行是仍需要进行一次匹配
						for (MethodInterceptor interceptor : interceptors) {
							interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
						}
					} else {
						interceptorList.addAll(Arrays.asList(interceptors));
					}
				}
			}
		} else if (advisor instanceof IntroductionAdvisor) {
			// 略
		} else {
			// 略
		}
	}

	return interceptorList;
}

转换之后的chain集合里面每个元素都是InterceptorAndDynamicMethodMatcher对象,该对象里面封装着MethodInterceptor实现类对象:
在这里插入图片描述

在此记录一下类型转换对应关系:

通知类型Advice类型MethodInterceptor类型
beforeAspectJMethodBeforeAdviceMethodBeforeAdviceInterceptor
afterAspectJAfterAdvice
afterReturnAspectJAfterReturningAdviceAfterReturningAdviceInterceptor
afterThrowingAspectJAfterThrowingAdvice

创建CglibMethodInvocation执行拦截链

retVal = 
    new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)
    .proceed();

CglibMethodInvocation的proceed方法:

public Object proceed() throws Throwable {
	// 此分支匹配时,调用目标方法
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		return invokeJoinpoint();
	}

	Object interceptorOrInterceptionAdvice =
			this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

	if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
		// Evaluate dynamic method matcher here: static part will already have
		// been evaluated and found to match.
		InterceptorAndDynamicMethodMatcher dm =
				(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
		Class<?> targetClass = 
            (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        // 此处又做了一次切入点匹配
		if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            // 执行拦截器方法
			return dm.interceptor.invoke(this);
		} else {
			// Dynamic matching failed.
			// Skip this interceptor and invoke the next in the chain.
			return proceed();
		}
	} else {
		// It's an interceptor, so we just invoke it: The pointcut will have
		// been evaluated statically before this object was constructed.
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}
}

MethodInterceptor实现类

此处简单介绍几个MethodInterceptor实现类:

  • ExposeInvocationInterceptor
  • MethodBeforeAdviceInterceptor
  • AspectJAfterAdvice
  • AfterReturningAdviceInterceptor
  • AspectJAfterThrowingAdvice

ExposeInvocationInterceptor

这个拦截器没有实际用处,只是为了把MethodInvocation放到thread local上,便于在代码中获取并使用。下面是API文档:

Interceptor that exposes the current MethodInvocation as a thread-local object. We occasionally need to do this; for example, when a pointcut (e.g. an AspectJ expression pointcut) needs to know the full invocation context.

Don’t use this interceptor unless this is really necessary. Target objects should not normally know about Spring AOP, as this creates a dependency on Spring API. Target objects should be plain POJOs as far as possible.

If used, this interceptor will normally be the first in the interceptor chain.

public Object invoke(MethodInvocation mi) throws Throwable {
	MethodInvocation oldInvocation = invocation.get();
	invocation.set(mi);
	try {
		return mi.proceed();
	} finally {
		invocation.set(oldInvocation);
	}
}

这个类对外提供一个静态方法,用于获取当前线程上的MethodInvocation对象:

private static final ThreadLocal<MethodInvocation> invocation =
		new NamedThreadLocal<>("Current AOP method invocation");

public static MethodInvocation currentInvocation() throws IllegalStateException {
	MethodInvocation mi = invocation.get();
	if (mi == null) {
		throw new IllegalStateException("No MethodInvocation found");
	}
	return mi;
}

MethodBeforeAdviceInterceptor

执行before切面方法:

public Object invoke(MethodInvocation mi) throws Throwable {
    // 调用before切面方法,里面的代码不展开记录了
	this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    // 继续执行调用链
    // 此处就回到了CglibMethodInvocation的proceed方法
    // 此时currentInterceptorIndex已经指向下一个Interceptor
	return mi.proceed();
}

AspectJAfterAdvice

执行after切面方法:

public Object invoke(MethodInvocation mi) throws Throwable {
	try {
        // 先执行调用链
		return mi.proceed();
	} finally {
        // 在finally中调用after切面方法
		invokeAdviceMethod(getJoinPointMatch(), null, null);
	}
}

AfterReturningAdviceInterceptor

执行afterReturn切面方法:

public Object invoke(MethodInvocation mi) throws Throwable {
    // 先执行调用链
	Object retVal = mi.proceed();
    // 执行afterReturn切面方法
	this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
	return retVal;
}

AspectJAfterThrowingAdvice

执行afterThrow切面方法:

public Object invoke(MethodInvocation mi) throws Throwable {
	try {
        // 先执行调用链
		return mi.proceed();
	} catch (Throwable ex) {
        // 执行afterReturn切面方法
		if (shouldInvokeOnThrowing(ex)) {
			invokeAdviceMethod(getJoinPointMatch(), null, ex);
		}
		throw ex;
	}
}

模拟MethodInvocation调用链

源代码里面的MethodInvocation调用链代码不是很好理解,为了方便理解,此处写了一段类似的代码,如下:

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

/**
 * 模拟spring aop调用链
 */
public class SpringAopAdvisorMain {

  /**
   * 模拟目标业务类
   */
  public static class Service {

    public String doService() {
      System.out.println("Service.doService()");
      return "ok";
//      throw new RuntimeException("error");
    }
  }

  /**
   * 模拟AOP调用链
   */
  public static class Invocation {

    // 拦截器集
    private final List<AdvisorInterceptor> interceptors;

    // 索引标识
    private int index = -1;

    // aop目标对象
    private final Object proxyObject;

    // aop目标方法
    private final Method proxyMethod;

    public Invocation(
        List<AdvisorInterceptor> interceptors, Object proxyObject,
        Method proxyMethod) {
      this.interceptors = interceptors;
      this.proxyObject = proxyObject;
      this.proxyMethod = proxyMethod;
    }

    /**
     * AOP调用链处理入口
     */
    public Object process() throws Exception {
      // 当所有拦截器都入调用栈之后,调用目标业务方法
      if (index == interceptors.size() - 1) {
        return this.proxyMethod.invoke(this.proxyObject);
      } else {
        // 调用拦截器方法
        AdvisorInterceptor interceptor = interceptors.get(++index);
        return interceptor.process(this);
      }
    }
  }

  /**
   * 模拟AOP调用链拦截器
   */
  interface AdvisorInterceptor {

    Object process(Invocation invocation) throws Exception;
  }

  /**
   * 纯粹是为了实验显示name值才创建了这个抽象类
   */
  public static abstract class AbstractAdvisorInterceptor implements AdvisorInterceptor {

    protected String name;

    public AbstractAdvisorInterceptor(String name) {
      this.name = name;
    }
  }

  /**
   * 前置通知拦截器
   */
  public static class BeforeAdvisorInterceptor extends AbstractAdvisorInterceptor {

    public BeforeAdvisorInterceptor(String name) {
      super(name);
    }

    @Override
    public Object process(Invocation invocation) throws Exception {
      this.before();
      return invocation.process();
    }

    private void before() {
      System.out.printf(">>> %s.%s\n", name, "before");
    }
  }

  /**
   * 返回通知拦截器
   */
  public static class AfterReturnAdvisorInterceptor extends AbstractAdvisorInterceptor {

    public AfterReturnAdvisorInterceptor(String name) {
      super(name);
    }

    @Override
    public Object process(Invocation invocation) throws Exception {
      Object ret = invocation.process();
      this.afterReturn();
      return ret;
    }

    private void afterReturn() {
      System.out.printf(">>> %s.%s\n", name, "afterReturn");
    }
  }

  /**
   * 异常通知拦截器
   */
  public static class AfterThrowingAdvisorInterceptor extends AbstractAdvisorInterceptor {

    public AfterThrowingAdvisorInterceptor(String name) {
      super(name);
    }

    @Override
    public Object process(Invocation invocation) throws Exception {
      try {
        return invocation.process();
      } catch (Exception e) {
        this.afterThrowing();
        throw e;
      }
    }

    private void afterThrowing() {
      System.out.printf(">>> %s.%s\n", name, "afterThrowing");
    }
  }

  /**
   * 后置通知拦截器
   */
  public static class AfterAdvisorInterceptor extends AbstractAdvisorInterceptor {

    public AfterAdvisorInterceptor(String name) {
      super(name);
    }

    @Override
    public Object process(Invocation invocation) throws Exception {
      try {
        return invocation.process();
      } finally {
        this.after();
      }
    }

    private void after() {
      System.out.printf(">>> %s.%s\n", name, "after");
    }
  }

  public static void main(String[] args) throws Exception {

    // 创建拦截器
    List<AdvisorInterceptor> interceptors = new ArrayList<>();

    interceptors.add(new BeforeAdvisorInterceptor("before1"));
    interceptors.add(new AfterAdvisorInterceptor("after1"));
    interceptors.add(new AfterReturnAdvisorInterceptor("afterReturn1"));
    interceptors.add(new AfterThrowingAdvisorInterceptor("afterThrowing1"));

    interceptors.add(new BeforeAdvisorInterceptor("before2"));
    interceptors.add(new AfterAdvisorInterceptor("after2"));
    interceptors.add(new AfterReturnAdvisorInterceptor("afterReturn2"));
    interceptors.add(new AfterThrowingAdvisorInterceptor("afterThrowing2"));

    // 目标业务层方法
    Service service = new Service();
    Method method = Service.class.getMethod("doService");

    // 调用链
    Invocation invocation = new Invocation(interceptors, service, method);
    Object result = invocation.process();
    System.out.println("result: " + result);

    /*
     * 正常输出:
     * >>> before1.before
     * >>> before2.before
     * Service.doService()
     * >>> afterReturn2.afterReturn
     * >>> after2.after
     * >>> afterReturn1.afterReturn
     * >>> after1.after
     * result: ok
     * */

    /*
     * 异常输出:
     * >>> before1.before
     * >>> before2.before
     * Service.doService()
     * >>> afterThrowing2.afterThrowing
     * >>> after2.after
     * >>> afterThrowing1.afterThrowing
     * >>> after1.after
     * */
  }
}

JdkDynamicAopProxy

他是InvocationHandler的实现类,在invoke方法中实现了JDK代理的拦截逻辑。

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;

	try {

		Object retVal;

		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}

		// Get as late as possible to minimize the time we "own" the target,
		// in case it comes from a pool.
		target = targetSource.getTarget();
		Class<?> targetClass = (target != null ? target.getClass() : null);

		// Get the interception chain for this method.
        // 1. 将Advisor集转换成MethodInterceptor链
        // 和Cglib代理一样
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

		// Check whether we have any advice. If we don't, we can fallback on direct
		// reflective invocation of the target, and avoid creating a MethodInvocation.
		if (chain.isEmpty()) {
			// We can skip creating a MethodInvocation: just invoke the target directly
			// Note that the final invoker must be an InvokerInterceptor so we know it does
			// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
		} else {
			// We need to create a method invocation...
            // 2. 构建一个ReflectiveMethodInvocation并执行拦截器链
			MethodInvocation invocation =
					new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
			// 执行调用链
			retVal = invocation.proceed();
		}

		// Massage return value if necessary.
		Class<?> returnType = method.getReturnType();
		if (retVal != null && retVal == target &&
				returnType != Object.class && returnType.isInstance(proxy) &&
				!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
			// Special case: it returned "this" and the return type of the method
			// is type-compatible. Note that we can't help if the target sets
			// a reference to itself in another returned object.
			retVal = proxy;
		} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
			throw new AopInvocationException("");
		}
		return retVal;
	} finally {
		// 略
	}
}

整体上和Cglib代理的拦截器逻辑一样。

后续ReflectiveMethodInvocation的proceed()方法也也是一样的。

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

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

相关文章

Modbus数据采集方案

目录 目标 Modbus协议简介 配置界面以及实例 概述 modbus协议应该是工业行业应用最广泛的协议&#xff0c;由于其协议简单、通讯标准、扩展性强的特点&#xff0c;被各个行业大量的应用。作为通讯网关机来说&#xff0c;设计一个便捷易懂的配置方式显得尤其重要。本方案基于…

多模态中的指令控制(InstructPix2Pix,SayCan)

InstructPix2Pix: Learning to Follow Image Editing Instructions 图像的语言指令生成。目的是遵循人工指令去编辑图像&#xff0c;即给定输入图像和一个如何编辑它的文本指令&#xff0c;模型尝试遵循这些指令来编辑图像。 这份论文与现有基于文本的图像编辑工作们最大的不同…

【JVM】方法区与永久代、元空间之间的关系

方法区与永久代、元空间之间的关系 方法区是JVM规范中定义的一块内存区域&#xff0c;用来存储类元数据、方法字节码、即时编译器需要的信息等 永久代是Hotspot虚拟机对JVM规范的实现(1.8之前) 元空间是Hotspot虚拟机对JVM规范的实现(1.8以后&#xff09;&#xff0c;使用本地…

java基于springboot高校学报论文在线投稿系统-计算机毕业设计

项目介绍 在新发展的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;高校只能以工作人员为导向&#xff0c;以稿件的持续创新作为高校最重要的竞争手段。 系统采用了J…

Java AQS

AQS 是什么 AQS 的全称为 AbstractQueuedSynchronizer&#xff0c;翻译过来的意思就是抽象队列同步器&#xff0c;这个类在 java.util.concurrent.locks 包下面Java 中的大部分同步类(Lock、Semaphore、ReentrantLock等) 都是基于 AQS 实现的AQS 是一种提供了原子式管理同步状…

SpringBoot - 整合WebSocket时@ServerEndpoint修饰的类属性注入为null问题

SpringBoot - 整合WebSocket时ServerEndpoint修饰的类属性注入为null问题前言一. 问题复现1.1 原因分析二. 问题解决前言 最近在做一个直播弹幕系统&#xff0c;前期准备先用WebSocket来试试水。我们都知道&#xff0c;使用WebSocket只需要给对应的类加上注解ServerEndpoint即…

Linux之定时任务--crontab命令解析学习

Corntab定时任务学习 一、crond服务 在学习crontab&#xff0c;命令之前&#xff0c;我觉得有必要学习了解一下crond服务&#xff0c;因为要在linux系统下使用crontab命令需要crond的支持。Crond是Linux下要用来周期执行某种任务或者等待处理某些事件的一个守护进程。和Windo…

项目——员工管理系统

开发环境&#xff1a;vmware ubuntu18.04 实现功能&#xff1a;基本功能包括管理者和普通员工用户的登录&#xff0c;管理者拥有操作所有员工信息的最高权限&#xff0c;可以进行增删改 查等操作&#xff0c;普通用户仅拥有查看、修改个人部分信息的权限 具体功能详解&…

python 学习笔记

解决执行python"ImportError: No module named requests"问题 #切换到python的安装目录执行如下命令 D:\Python27>pip install requestsImportError: No module named bs4错误解决方法 运行脚本时提示ImportError: No module named bs4错误&#xff0c;原因&…

[附源码]计算机毕业设计基于vue的软件谷公共信息平台Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Python——有限状态机

有限状态机&#xff08;Finite-state machine, FSM&#xff09;&#xff0c;又称有限状态自动机&#xff0c;简称状态机&#xff0c;是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM是一种算法思想&#xff0c;简单而言&#xff0c;有限状态机由一组状态…

【Lilishop商城】No3-5.模块详细设计,商品模块-1(商品分类、品牌管理、规格管理、参数、计量单位、店铺分类)的详细设计

仅涉及后端&#xff0c;全部目录看顶部专栏&#xff0c;代码、文档、接口路径在&#xff1a; 【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客 全篇会结合业务介绍重点设计逻辑&#xff0c;其中重点包括接口类、业务类&#xff0c;具体的结合源代码…

人脸特征点检测入门

基础 人脸特征点可以用来做脸型、眼睛形状、鼻子形状等分析&#xff0c;从而对人脸的特定位置进行修饰加工&#xff0c;实现人脸的特效美颜。人脸识别等算法可以通过对人脸的姿态进行对齐从而提高模型的精度。 68点标注是现今最通用的一种标注方案&#xff0c;早期在1999年的…

Effective C++条款30:透彻了解inlining的里里外外(Understand the ins and outs of inlining)

Effective C条款30&#xff1a;透彻了解inlining的里里外外&#xff08;Understand the ins and outs of inlining&#xff09;条款30&#xff1a;透彻了解inlining的里里外外1、inline函数的优缺点2、隐式内联和显式内联2.1 隐式内联2.2 显式内联3、函数模板必须inline么&…

自动驾驶之3D点云聚类算法调研

1. 方法 总共分为4类 基于欧式距离的聚类Supervoxel 聚类深度(Depth) 聚类Scanline Run 聚类 1.1 基于欧氏距离的聚类 思路: 在点云上构造kd-tree, 然后在某个半径阈值(例如0.5m), 则分割为一个实例。 相似算法: RBNN (radially bounded nearest neighbor graph), 2008. …

在 Ubuntu 上安装 Discourse 开发环境

本指南只针对 Discourse 开发环境的配置&#xff0c;如果你需要在生产环境中安装 Discourse &#xff0c;请访问页面&#xff1a;Install Discourse in production with the official, supported instructions - sysadmin - Discourse Meta 中的内容。 有关开发环境的设置英文原…

[Java EE初阶] 进程调度的基本过程

纪念Java EE初阶开篇文章,不放弃,不摆烂,踏平所有障碍吧!少年!奥利给!(操作系统这方面的所有文章均不作为操作系统的专业课知识学习) 文章目录1. 进程的概念2. PCB --- 进程控制块3. 并发与并行4. 进程调度的相关属性5. 内存管理总结1. 进程的概念 进程,就是跑起来的程序,我们…

【学习笔记】《Python深度学习》第七章:高级的深度学习最佳实践

文章目录1 Keras 函数式 API1.1 函数式 API 简介1.2 多输入模型1.3 多输出模型1.4 层组成的有向无环图1.5 共享层权重1.6 将模型作为层2 使用 Keras 回调函数 和 TensorBoard 检查并监控深度学习模型2.1 训练过程中将回调函数作用于模型2.2 TensorBoard 简介&#xff1a;Tensor…

【Lilishop商城】No3-4.模块详细设计,店铺店员(店铺店员、店铺部门、店铺角色)的详细设计

仅涉及后端&#xff0c;全部目录看顶部专栏&#xff0c;代码、文档、接口路径在&#xff1a; 【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客 全篇会结合业务介绍重点设计逻辑&#xff0c;其中重点包括接口类、业务类&#xff0c;具体的结合源代码…

exfat文件系统

DBR&#xff1a; DBR偏移量 字段长度&#xff08;字节&#xff09; 说明 0x40 - 0x47 8 分区的起始扇区号&#xff08;隐藏扇区数&#xff09; 0x48 - 0x4F 8 分区总扇区数 0x50 - 0x53 4 FAT表起始扇区号&#xff08;从DBR到FAT表的扇区个数&#xff09; 0x54 - 0x57 4…