spring AOP源码阅读分析

news2024/11/20 14:24:05
理论知识

AOP是面向切面编程(Aspect Oriented Programming)的意思。定义一些切点(pointcut),然后可以在切点织入一些通知(advice),对切点方法进行代理增强,与核心业务逻辑分离开来,以提高系统的可维护性、可扩展性和重用性。

AOP的核心思想是将系统中的功能模块按照不同的关注点进行横切划分,然后通过一种称为“切面”的手段,将这些关注点与主要业务逻辑进行解耦。在Spring AOP中,我们可以通过定义切面、切点和通知三个元素来实现AOP的功能:

  • 切面(Aspect):一组横切关注点的集合,它描述了在何时、何地、何种情况下执行横切关注点。
  • 切点(PointCut):使用表达式语言,匹配目标对象中的连接点(Join Point),从而识别出需要执行横切关注点的地方。
  • 通知(Advisor):它定义了在连接点处执行的代码逻辑,通常包括前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)、异常通知(After Throwing Advice)和最终通知(After Returning Advice)等,这些通知组成了切面的核心实现逻辑

在aspectjweaver-1.9.7.jar包里有常用的切面注解。

开启切面代理

需要添加@EnableAspectJAutoProxy注解,会引入AspectJAutoProxyRegistrar类。该类会往BeanDefinitionRegistry里注册一个(org.springframework.aop.config.internalAutoProxyCreator,AnnotationAwareAspectJAutoProxyCreator)类型的beanDef。前面bean的实例化过程知道beanDef会被初始化成一个bean。

具体位置AopConfigUtils.registerOrEscalateApcAsRequired方法

private static BeanDefinition registerOrEscalateApcAsRequired(
      Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
   if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {//如果这个beanName已注册
      BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
      if (!cls.getName().equals(apcDefinition.getBeanClassName())) {//和当前指定的class不一致
         /**
         查找两个类的优先级
         这里有三个ProxyCreator:
         InfrastructureAdvisorAutoProxyCreator
         AspectJAwareAdvisorAutoProxyCreator
         AnnotationAwareAspectJAutoProxyCreator
         我们通过@EnableAspectJAutoProxy是引入的AnnotationAwareAspectJAutoProxyCreator,这里就是判断如果容器已经指定了creator,如果是其它的两个,这里会被替换掉,也是方法名Escalate升级的意思。Annotation这个优先级最高
         **/
         int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
         int requiredPriority = findPriorityForClass(cls);
         if (currentPriority < requiredPriority) {
            apcDefinition.setBeanClassName(cls.getName());
         }
      }
      return null;
   }

   RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
   beanDefinition.setSource(source);
   //优先级最高
   beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
   beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
   return beanDefinition;
}

来看下AnnotationAwareAspectJAutoProxyCreator类的结构

在这里插入图片描述

我们发现他实现了BeanPostProcessor接口,是一个bean后置处理器。当一个bean初始化时,会调用beanpostPorocessor的before和after模板方法。其实代理也是在post的这两个方法实现的。具体在AbstractAutoProxyCreator类。

Aspect的解析

后置处理器的AbstractAutoProxyCreator#before方法

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
   Object cacheKey = getCacheKey(beanClass, beanName);

   if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
      if (this.advisedBeans.containsKey(cacheKey)) {//已经处理过
         return null;
      }
      //shouldSkip方法会判断该bean是否需要aop切面
      if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
         this.advisedBeans.put(cacheKey, Boolean.FALSE);
         return null;
      }
   }

   return null;
}

shouldSkip会调用多次,这里面主要时判断当前bean有没有匹配的切面织入点,这里shouldSkip要看AspectJAwareAdvisorAutoProxyCreator子类的实现

protected boolean shouldSkip(Class<?> beanClass, String beanName) {
   //shouldSkip-1 findCandidateAdvisors方法在类AnnotationAwareAspectJAutoProxyCreator
   List<Advisor> candidateAdvisors = findCandidateAdvisors();
   //这里找到的advisor都是InstantiationModelAwarePointcutAdvisorImpl类型,for循环不成立
   for (Advisor advisor : candidateAdvisors) {
      if (advisor instanceof AspectJPointcutAdvisor &&
            ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
         return true;
      }
   }
   return super.shouldSkip(beanClass, beanName);
}

shouldSkip-1调用的AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors方法,先获取配置的所有advisor。也就是@Aspect注解标记的类

protected List<Advisor> findCandidateAdvisors() {
   //先从缓存查找
   List<Advisor> advisors = super.findCandidateAdvisors();
   // 不存在构建解析
   if (this.aspectJAdvisorsBuilder != null) {
      advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
   }
   return advisors;
}

第一次从缓存中无法找到,会走下面的BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors方法去解析advisors

public List<Advisor> buildAspectJAdvisors() {
   List<String> aspectNames = this.aspectBeanNames;

   if (aspectNames == null) {
      synchronized (this) {
         aspectNames = this.aspectBeanNames;
         if (aspectNames == null) {
            List<Advisor> advisors = new ArrayList<>();
            aspectNames = new ArrayList<>();
            //从这里开始,先拿出所有的bean来
            String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                  this.beanFactory, Object.class, true, false);
            for (String beanName : beanNames) {
            //是否有@Aspect注解,调用AnnotationUtils.findAnnotation(clazz, Aspect.class)判断
               if (this.advisorFactory.isAspect(beanType)) {
                  aspectNames.add(beanName);
                  AspectMetadata amd = new AspectMetadata(beanType, beanName);
                  if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                     MetadataAwareAspectInstanceFactory factory =
                           new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                     //buildAspect-2 查找所有的advisor,获取所有的bean方法,过滤掉所有带@Pointcut注解的,然后依次在方法上查找切面的注解
                     List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                     if (this.beanFactory.isSingleton(beanName)) {
                     //放入缓存 key是beanName,value是所有的切面advisor方法
                        this.advisorsCache.put(beanName, classAdvisors);
                     }
                     else {
                        this.aspectFactoryCache.put(beanName, factory);
                     }
                     advisors.addAll(classAdvisors);
                  }
                  else {//非单例}
               }
            }
            //解析完一次后,就存起来,下次不用拿出所有的bean进行判断是不是AspectBean了
            this.aspectBeanNames = aspectNames;
            return advisors;
         }
      }
   }
if (aspectNames.isEmpty()) {
			return Collections.emptyList();
		}
		//第二次aspectNames不是空,直接到这里拿出所有的advisor返回
		List<Advisor> advisors = new ArrayList<>();
		for (String aspectName : aspectNames) {
			List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
			if (cachedAdvisors != null) {
				advisors.addAll(cachedAdvisors);
			}
			else {
				MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
				advisors.addAll(this.advisorFactory.getAdvisors(factory));
			}
		}
		return advisors;
}

从上面的advisor解析看到,查找@Aspect是从beanFacotry里查找bean。所以我们的@Aspect也要是一个bean,否则只有@Aspect注解是无效的

buildAspect-2处查找Aspect切面的方法AbstractAspectJAdvisorFactory#findAspectJAnnotationOnMethod

Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
			Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
//在方法上查找所有切面注解
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
   for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
      AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
      if (foundAnnotation != null) {
         return foundAnnotation;
      }
   }
   return null;
}
切面方法匹配

上面before方法看完,只是对Aspect进行了解析。没有具体使用,那么继续看after方法。还是从beanpost的after方法

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         //是否需要包装代理
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	    //
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		//不需要代理的类
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
        //又调用一次shouldSkip
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		//after-1 寻找适配这个类的所有advisor
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

after-1处寻找适配的Advisor方法最后会调到findEligibleAdvisors方法

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
   List<Advisor> candidateAdvisors = findCandidateAdvisors();//拿出所有的advisor
   //findEligible-1 找出这个类可以使用的advisor
   List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
   extendAdvisors(eligibleAdvisors);
   if (!eligibleAdvisors.isEmpty()) {
   //对通知进行排序,使用的PartialOrder
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
   }
   return eligibleAdvisors;
}

findEligible-1 找匹配的advisor方法

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
   if (candidateAdvisors.isEmpty()) {
      return candidateAdvisors;
   }
   List<Advisor> eligibleAdvisors = new ArrayList<>();
   for (Advisor candidate : candidateAdvisors) {//这一步是干什么不知道,好像都不匹配
      if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
         eligibleAdvisors.add(candidate);
      }
   }
   boolean hasIntroductions = !eligibleAdvisors.isEmpty();
   for (Advisor candidate : candidateAdvisors) {
      if (candidate instanceof IntroductionAdvisor) {
         // already processed
         continue;
      }
      //会走这里
      if (canApply(candidate, clazz, hasIntroductions)) {
         eligibleAdvisors.add(candidate);
      }
   }
   return eligibleAdvisors;
}

canApply方法

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
   //...
   //拿出所有的类方法与切点规则进行匹配,只要有一个匹配则返回true
   for (Class<?> clazz : classes) {
      Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
      for (Method method : methods) {
         if (introductionAwareMethodMatcher != null ?
         //matches怎么匹配的,看不懂不看了。反正就是拿出来joinPoint切面表达式和方法Signature进行匹配
               introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
               methodMatcher.matches(method, targetClass)) {
            return true;
         }
      }
   }

   return false;
}
代理创建

如果找到切面能匹配当前bean,则该bean需要被代理

创建代理主要在createProxy方法,这里会将所有相关的信息构造成一个ProxyFactory对象,最后调用proxyFactory.getProxy方法获取代理对象。proxyFactory内有一个DefaultAopProxyFactory,其createAopProxy方法用来创建代理对象。

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (!NativeDetector.inNativeImage() &&
         (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
      Class<?> targetClass = config.getTargetClass();
      //判断目标是否是接口
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {//使用jdk创建动态代理
         return new JdkDynamicAopProxy(config);
      }//使用cglib代理
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      return new JdkDynamicAopProxy(config);
   }
}

这里可以看到接口类会使用jdk动态代理,其它使用cglib进行创建代理。cglib不太懂,看JdkDynamicAopProxy。这个类实现了InvocationHandler接口。

方法的执行

以jdk代理为例。执行的开始就是handler的invoker方法了。

来看JdkDynamicAopProxy的invoker方法

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;
      //这个是根据@EnableAspectJAutoProxy注解的exposeProxy属性判断是否暴露原被代理对象,放到ThreadLocal里,在advisor方法逻辑里就可以获取到被代理对象了。
      if (this.advised.exposeProxy) {
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }
      target = targetSource.getTarget();
      Class<?> targetClass = (target != null ? target.getClass() : null);
      //invoke-1 从可用advisors中获取拦截器链
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      if (chain.isEmpty()) {
  		//拦截器链为空,直接使用反射调用
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      }
      else {
         //invoke-2 根据拦截器链构造MethodInvocation
         MethodInvocation invocation =
               new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         //invoke-3 执行责任链上的方法
         retVal = invocation.proceed();
      }
      return retVal;
   }
   finally {
      //...
      if (setProxyContext) {
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}

invoke-1创建拦截器链最后实际处理逻辑在DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice中.前面切点发现的时候说了,某个bean只要有一个方法符合切面匹配。就会被代理。这里实际调用某个方法的时候,还需要再判断一次切面规则是否匹配当前代理类被调用方法。有可能一个类中有的方法符合规则有的不符合规则。

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
      Advised config, Method method, @Nullable Class<?> targetClass) {
   /**intercept-1
    这里获取registry,默认会创建一个DefaultAdvisorAdapterRegistry类型register。
    里面初始化三个adapter:
    	public DefaultAdvisorAdapterRegistry() {
          registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
          registerAdvisorAdapter(new AfterReturningAdviceAdapter());
          registerAdvisorAdapter(new ThrowsAdviceAdapter());
        }
        对应三个切面类型
   */
   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) {//拿出所有的advisor进行方法匹配
      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) {//intercept-2这里根据不同的advisor获取不同类型的拦截器
               MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
               interceptorList.addAll(Arrays.asList(interceptors));
            }
         }
      }
      else if (advisor instanceof IntroductionAdvisor) {
         IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
         if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
            Interceptor[] interceptors = registry.getInterceptors(advisor);
            interceptorList.addAll(Arrays.asList(interceptors));
         }
      }
      else {
         Interceptor[] interceptors = registry.getInterceptors(advisor);
         interceptorList.addAll(Arrays.asList(interceptors));
      }
   }

   return interceptorList;
}

intercept-2从advisorAdapter获取拦截器

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
   List<MethodInterceptor> interceptors = new ArrayList<>(3);
   Advice advice = advisor.getAdvice();
   if (advice instanceof MethodInterceptor) {
      interceptors.add((MethodInterceptor) advice);
   }
   //这里的adapter对应上面intercept-1初始化的三个切面类型adapter
   for (AdvisorAdapter adapter : this.adapters) {
      if (adapter.supportsAdvice(advice)) {//类型匹配 根据advice获取对应的intecept
         interceptors.add(adapter.getInterceptor(advisor));
      }
   }
   if (interceptors.isEmpty()) {
      throw new UnknownAdviceTypeException(advisor.getAdvice());
   }
   return interceptors.toArray(new MethodInterceptor[0]);
}

invoke-3 proceed方法是执行的关键。这里会递归的调用proceed方法进行所有拦截器invoke

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) {
 	//...
      //proceed 执行拦截器invoke方法
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}

这里不同的拦截器内部invoke方法不一样,来看一下:

1-ExposeInvocationInterceptor

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

这个没有什么逻辑,就是继续调用下一个。也没注意什么时候生成的,就是threadlocal里执行前后换了下值。

2-MethodBeforeAdviceInterceptor

public Object invoke(MethodInvocation mi) throws Throwable {
   this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
   return mi.proceed();
}

@Before注解对应拦截器。会先执行before advice方法,然后继续执行下一个拦截器。也就是我们要植入的方法。

before方法的实现会到AspectJMethodBeforeAdvice类里

public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
   invokeAdviceMethod(getJoinPointMatch(), null, null);
}

3-AspectJAfterAdvice

public Object invoke(MethodInvocation mi) throws Throwable {
   try {
      return mi.proceed();
   }
   finally {
      invokeAdviceMethod(getJoinPointMatch(), null, null);
   }
}

@After注解对应的拦截器。最后在执行advice方法。我们看到advice放到的执行放到了finally里,所以@after标记的切面方法即使被代理原方法抛异常也会被执行

4-AspectJAfterThrowingAdvice

public Object invoke(MethodInvocation mi) throws Throwable {
   try {
      return mi.proceed();
   }
   catch (Throwable ex) {
      if (shouldInvokeOnThrowing(ex)) {
         invokeAdviceMethod(getJoinPointMatch(), null, ex);
      }
      throw ex;
   }
}

@AfterThrowing对应拦截器,advisor方法在catch块里,异常时候会执行advisor方法

使用例子

配置开启aop

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
@ComponentScan(basePackages = {"com.cpx.service.aop"})
public class AopConfig {}

切面处理类:

@Component
@Aspect
@Slf4j
public class LogTestAspect {

    @Pointcut("execution(* com.cpx.service.*.*(..))")
    public void point(){};

    @After(value = "point()")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("after method execute");
    }

    @AfterThrowing(value = "point()")
    public void afterThrow(JoinPoint joinPoint){
        log.info(" exception happen.............");
    }

    public void test(){
        log.info("hhh");
    }

    @Before(value = "point()")
    public void methodBefore(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        log.info("before method execute");
    }
}

启动测试

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class);
UserService userService = (UserService) ctx.getBean("userService");
userService.sayHello();
ctx.close();
总结

Spring AOP实现是通过bean后置处理器去实现。在后置处理器方法找到所有的Advisor,即@Aspect注解标注的bean。然后每个被创建的bean都会拿出所有的类方法与解析到的Advisors切点规则进行匹配,只要有一个规则匹配,则该bean会被通过JDK动态代理或cglib方式代理包装返回代理对象。通过代理执行原方法时,会计算出当前方法匹配的所有Advisor规则组成Intecepters依次调用。

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

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

相关文章

网工内推 | 网络工程师,软考证书优先,六险一金,包吃

01 科力信息 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责蚌埠项目的设备安装及调试&#xff1b; 2、对边界网络运行中的监控、故障排除、问题处理。 任职要求&#xff1a; 1、2年及以上网络相关工作经验&#xff0c;有交通管理网络运维经验优先&#xff1b…

【移动端测试工具】Appium自动化测试工具安装与配置

文章目录 一、JAVA环境配置检查是否已安装java jdk 二、android SDK安装1.下载android sdk压缩包2.解压压缩包3.安装SDK Manager4.sdk环境变量配置5.验证sdk是否安装成功 三、node JS安装1.下载node.js安装包2.安装node.js3.环境配置4.测试完成验证5.安装淘宝镜像并检验是否安装…

Android MeasureSpec测量规格

文章目录 Android MeasureSpec测量规格概述MeasureSpec组成常用APIMeasureSpec源码分析getChildMeasureSpec源码分析总结 Android MeasureSpec测量规格 概述 MeasureSpec指View的测量规格&#xff0c;MeasureSpec是View的一个静态内部类。 View的MeasureSpec是根据自身的布局…

SoloX:Android和iOS性能数据的实时采集工具

SoloX&#xff1a;Android和iOS性能数据的实时采集工具 github地址&#xff1a;https://github.com/smart-test-ti/SoloX 最新版本&#xff1a;V2.7.6 一、SoloX简介 SoloX是开源的Android/iOS性能数据的实时采集工具&#xff0c;目前主要功能特点&#xff1a; 无需ROOT/越狱…

Java调用操作系统命令的输出乱码问题解决

本篇解决的问题 使用Java 的Runtime调用操作系统的命令&#xff0c;出现异常时使用getErrorStream()获取错误信息的字节流&#xff0c;转换该字节流为字符串显示时&#xff0c;出现乱码。 Java调用操作系统命令 这里以Windows 操作系统为例&#xff0c; 调用cd 命令切换路径…

SAP 销售订单审批状态参数设置

定义权限码 BS52 Spro->控制->内部订单->订单主数据->状态管理->定义状态管理授权码 创建状态参数文件 BS02 SPRO->销售与分销->销售->销售凭证->定义并分配状态参数文件->定义状态参数文件 1)命名&#xff0c;描述 设置对象类型&#xff1a;销…

记录一个iOS UITableView 正在刷新的时候修改数据源导致的崩溃

首先看一下崩溃堆栈信息 由于tableview 调用layoutsubViews 执行到代理方法 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 由于是崩溃在系统方法里面的&#xff0c;我们无法直接看到是因为调用哪个方法导致的崩溃 后来…

秦时明月沧海手游礼包码,秦时明月沧海兑换码

在玩《秦时明月沧海》手游时&#xff0c;你可能会遭到礼包码的诱惑。如果你还没找到可用的兑换码&#xff0c;这里有一些可供使用的礼包码&#xff0c;赶快领取吧&#xff01; 关注【娱乐天梯】&#xff0c;获取内部福利号 1. 礼包码&#xff1a;QIN0809 包含&#xff1a;金镒…

面试打底稿⑤ 项目一的第一部分

简历原文 抽查部分 项目描述 该项目旨在服务广州地区的快递物流&#xff0c;实现了下单、快递员取派件、订单转运单、线路规划、网点设置等功能。 责任描述 登录系统优化&#xff0c;双token三验证模式实现设置token状态、提高登录安全性的效果 模拟问答 1.能简单介绍一下…

2023-9-27 JZ18 删除链表的结点

题目链接&#xff1a; 删除链表的结点 import java.util.*;/** public class ListNode {* int val;* ListNode next null;* public ListNode(int val) {* this.val val;* }* }*/public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请…

【C++】友元函数 ( 友元函数简介 | 友元函数声明 | 友元函数语法 | 友元函数声明不受访问控制限制 | 友元函数参数要求 )

文章目录 一、友元函数简介二、友元函数声明1、友元函数语法2、友元函数声明不受访问控制限制3、友元函数参数要求4、友元函数示例 三、完整代码示例 - 友元函数 一、友元函数简介 在 C 语言中 , " 友元函数 " 是 与 类 相关联的函数 , " 友元函数 " 不是…

【Verilog 教程】6.6Verilog 仿真激励

关键词&#xff1a;testbench&#xff0c;仿真&#xff0c;文件读写 Verilog 代码设计完成后&#xff0c;还需要进行重要的步骤&#xff0c;即逻辑功能仿真。仿真激励文件称之为 testbench&#xff0c;放在各设计模块的顶层&#xff0c;以便对模块进行系统性的例化调用进行仿真…

对象存储,从单机到分布式的演进

关于数据存储的相关知识,请大家关注“数据存储张”,各大平台同名。 通过《什么是云存储?从对象存储说起》我们对对象存储的历史、概念和基本使用有了一个大概的认识。而且我们以Minio为例,通过单机部署的模式实际操作了一下对象存储的GUI,感受了一下对象存储的用法。 在上…

word中使用latex多行公式,矩阵公式

\eqarray{H& [h(x_1)^T,\cdots,h(x_N)^T]^T \\ & [\matrix{g(w_1 x_1b_1) & \cdots & g(w_L x_1b_L) \\ \vdots & \ddots & \vdots \\ g(w_1 x_Nb_1) & \cdots & g(w_L x_Nb_L)}]_{N \times L}}&的引起的那条竖线可以通过backspace或者del…

人工智能AI 全栈体系(七)

第一章 神经网络是如何实现的 神经网络不仅仅可以处理图像&#xff0c;同样也可以处理文本。由于处理图像讲起来比较形象&#xff0c;更容易理解&#xff0c;所以基本是以图像处理为例讲解的。 七、词向量 图像处理之所以讲起来比较形象&#xff0c;是因为图像的基本元素是像…

VB6.0开发文件管理小数据库-基于ACCESS

今天的客户也是小客户&#xff0c;需要对文件的一些操作记录在数据库里面&#xff0c;这里采用的数据库ACCCESS&#xff0c;用的是VB自带的可视化数据管理器创建的mdb数据库文件。实现了数据的增删改查。简单方便。基本可以用这套代码模板实现大部分的数据库功能了。想研发或学…

自定义ElementPlus主题颜色

构建工具采用Vite CSS预处理器采用Sass 一.准备定制化的样式文件 1.安装Sass npm i sass -D 2.创建好文件目录 3.书写样式 ElementPlus默认样式. //index.scss/* 只需要重写你需要的即可 */ forward element-plus/theme-chalk/src/common/var.scss with ($colors: (prim…

腾讯云秒杀活动是什么?如何参与?

腾讯云是国内知名的云计算服务提供商之一&#xff0c;为了吸引更多的用户&#xff0c;腾讯云会不定期地推出各种各样的优惠活动&#xff0c;其中最受大家欢迎的就是“腾讯云秒杀活动”。本文将为大家详细介绍腾讯云秒杀活动参与方式以及购买攻略。 一、腾讯云秒杀活动是什么&am…

【操作系统】处理机调度的基本概念和三个层次、进程调度的时机和方式、调度器、闲逛线程

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 操作系统 一、处理机调度基本概念二、处理机…

【论文阅读】UniDiffuser: Transformer+Diffusion 用于图、文互相推理

而多模态大模型将能够打通各种模态能力&#xff0c;实现任意模态之间转化&#xff0c;被认为是通用式生成模型的未来发展方向。 最近看到不少多模态大模型的工作&#xff0c;有医学、金融混合&#xff0c;还有CV&NLP。 今天介绍&#xff1a; One Transformer Fits All Di…