Spring源码解析(25)之AOP的BeanDefinitiion准备

一、AOP代码准备

        aop.xml文件准备,代码如下:

<?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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--    <bean class="com.mashibing.MyBeanFactoryPostProcessorBySelf" ></bean>--><bean id="logUtil" class="com.mashibing.aop.xml.util.LogUtil" ></bean><bean id="myCalculator" class="com.mashibing.aop.xml.service.MyCalculator" ></bean><aop:config><aop:aspect ref="logUtil"><aop:pointcut id="myPoint" expression="execution( Integer com.mashibing.aop.xml.service.MyCalculator.*  (..))"/><aop:around method="around" pointcut-ref="myPoint"></aop:around><aop:before method="start" pointcut-ref="myPoint"></aop:before><aop:after method="logFinally" pointcut-ref="myPoint"></aop:after><aop:after-returning method="stop" pointcut-ref="myPoint" returning="result"></aop:after-returning><aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"></aop:after-throwing></aop:aspect></aop:config><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

        以上是常规xml配置aop文件准备,下面是具体的切面java类准备。

package com.mashibing.aop.xml.util;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;import java.util.Arrays;//@Aspect
//@Component
//@Order(200)
public class LogUtil {//    @Pointcut("execution(public Integer com.mashibing.service.MyCalculator.*(Integer,Integer))")public void myPointCut(){}//    @Pointcut("execution(* *(..))")public void myPointCut1(){}//    @Before(value = "myPointCut()")private int start(JoinPoint joinPoint){//获取方法签名Signature signature = joinPoint.getSignature();//获取参数信息Object[] args = joinPoint.getArgs();System.out.println("log---"+signature.getName()+"方法开始执行:参数是"+Arrays.asList(args));return 100;}//    @AfterReturning(value = "myPointCut()",returning = "result")public static void stop(JoinPoint joinPoint,Object result){Signature signature = joinPoint.getSignature();System.out.println("log---"+signature.getName()+"方法执行结束,结果是:"+result);}//    @AfterThrowing(value = "myPointCut()",throwing = "e")public static void logException(JoinPoint joinPoint,Exception e){Signature signature = joinPoint.getSignature();System.out.println("log---"+signature.getName()+"方法抛出异常:"+e.getMessage());}//    @After("myPointCut()")public static void logFinally(JoinPoint joinPoint){Signature signature = joinPoint.getSignature();System.out.println("log---"+signature.getName()+"方法执行结束。。。。。over");}//     @Around("myPointCut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {Signature signature = pjp.getSignature();Object[] args = pjp.getArgs();Object result = null;try {System.out.println("log---环绕通知start:"+signature.getName()+"方法开始执行,参数为:"+Arrays.asList(args));//通过反射的方式调用目标的方法,相当于执行method.invoke(),可以自己修改结果值result = pjp.proceed(args);
//            result=100;System.out.println("log---环绕通知stop"+signature.getName()+"方法执行结束");} catch (Throwable throwable) {System.out.println("log---环绕异常通知:"+signature.getName()+"出现异常");throw throwable;}finally {System.out.println("log---环绕返回通知:"+signature.getName()+"方法返回结果是:"+result);}return result;}
}

        以上的例子就比较简单,就是在xml里面配置对应的aop切点、切面,接下来我们看下在spring AOP源码中是如何运行的。

二、Spring AOP 配置文件解析

        在之前的spring 标签解析源码中我们有介绍到spring 配置文件解析是在refresh方法中,具体可见:Spring源码解析(15)之refresh源码分析(三)配置文件解析_refresh本地配置文件-CSDN博客文章浏览阅读476次。一、前言在前面的博客中我们有介绍到refresh方法中的otainFreshBeanFaction方法,这个方法的作用就是获取bean工厂,在这期间还会完成配置文件的加载,生成BeanDefinition,需要注意的是这里生成的BeanFactory默认的是DefaultListableBeanFactory,需要注意的是,我们自定的一些xsd约束文件也是在这里完成解析,通过实现BeanDefitionParser接口,并且实现parse方法,自定义的标签也通过实现NamespaceHandlerSup_refresh本地配置文件https://blog.csdn.net/jokeMqc/article/details/123029526        启动启动类代码:

    public static void main(String[] args) throws NoSuchMethodException {AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();ac.register(SpringConfiguration.class);ac.refresh();MyCalculator bean = ac.getBean(MyCalculator.class);System.out.println(bean.add(1, 1));}

        在AbstractApplicationContext.refresh中的obtainFreshBeanFactory解析对应xml文件文件。

@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing./*** 前戏,做容器刷新前的准备工作* 1、设置容器的启动时间* 2、设置活跃状态为true* 3、设置关闭状态为false* 4、获取Environment对象,并加载当前系统的属性值到Environment对象中* 5、准备监听器和事件的集合对象,默认为空的集合*/prepareRefresh();// Tell the subclass to refresh the internal bean factory.// 创建容器对象:DefaultListableBeanFactory// 加载xml配置文件的属性值到当前工厂中,最重要的就是BeanDefinitionConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.// beanFactory的准备工作,对各种属性进行填充prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 子类覆盖方法做额外的处理,此处我们自己一般不做任何扩展工作,但是可以查看web中的代码,是有具体实现的postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.// 调用各种beanFactory处理器invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.// 注册bean处理器,这里只是注册功能,真正调用的是getBean方法registerBeanPostProcessors(beanFactory);// Initialize message source for this context.// 为上下文初始化message源,即不同语言的消息体,国际化处理,在springmvc的时候通过国际化的代码重点讲initMessageSource();// Initialize event multicaster for this context.// 初始化事件监听多路广播器initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 留给子类来初始化其他的beanonRefresh();// Check for listener beans and register them.// 在所有注册的bean中查找listener bean,注册到消息广播器中registerListeners();// Instantiate all remaining (non-lazy-init) singletons.// 初始化剩下的单实例(非懒加载的)finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.// 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.// 为防止bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件beandestroyBeans();// Reset 'active' flag.// 重置active标志cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

        进入到obtainFreshBeanFactory中的refreshBeanFactory()中的loadDefinitions中解析对应的配置文件。

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {// 初始化BeanFactory,并进行XML文件读取,并将得到的BeanFactory记录在当前实体的属性中refreshBeanFactory();// 返回当前实体的beanFactory属性return getBeanFactory();}@Overrideprotected final void refreshBeanFactory() throws BeansException {// 如果存在beanFactory,则销毁beanFactoryif (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {// 创建DefaultListableBeanFactory对象DefaultListableBeanFactory beanFactory = createBeanFactory();// 为了序列化指定id,可以从id反序列化到beanFactory对象beanFactory.setSerializationId(getId());// 定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖customizeBeanFactory(beanFactory);// 初始化documentReader,并进行XML文件读取及解析,默认命名空间的解析,自定义标签的解析loadBeanDefinitions(beanFactory);this.beanFactory = beanFactory;}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}}

        最后我们知道是在parseBeanDefinitions解析对应的标签,而parseDefaultElement解析的是bean,import、alias等默认标签,而我们的aop标签是在parseCustomerElement中解析,继续端点跟进去看下具体的解析过程。

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}

	@Nullablepublic BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取对应的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间找到对应的NamespaceHandlerspringNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 调用自定义的NamespaceHandler进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}

         获取对应命名空间然后获取对应handler去解析,就会进入到ConfigBeanDefinitionParser对象中。

        这里就能够看得到对应的aop标签的解析,包括:pointcut、advisor、aspect标签的解析。

	public BeanDefinition parse(Element element, ParserContext parserContext) {CompositeComponentDefinition compositeDef =new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));parserContext.pushContainingComponent(compositeDef);// 注册自动代理模式创建器,AspectjAwareAdvisorAutoProxyCreatorconfigureAutoProxyCreator(parserContext, element);// 解析aop:config子节点下的aop:pointcut/aop:advice/aop:aspectList<Element> childElts = DomUtils.getChildElements(element);for (Element elt: childElts) {String localName = parserContext.getDelegate().getLocalName(elt);if (POINTCUT.equals(localName)) {parsePointcut(elt, parserContext);}else if (ADVISOR.equals(localName)) {parseAdvisor(elt, parserContext);}else if (ASPECT.equals(localName)) {parseAspect(elt, parserContext);}}parserContext.popAndRegisterContainingComponent();return null;}

         在解析对象这些标签之前首先先看下对应的configureAutoProxyCreator,主要是往IOC容器中注入切面主动代理类:AbstractAutoPorxyCreator对象,自动代码构造器对象。

	private void configureAutoProxyCreator(ParserContext parserContext, Element element) {AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);}public static void  registerAspectJAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {// 注册名为org.springframework.aop.config.internalAutoProxyCreator的beanDefinition,其中的class类为`AspectJAwareAdvisorAutoProxyCreator`,其也会被注册到bean工厂中BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));// 如果指定proxy-target-class=true,则使用CGLIB代理,否则使用JDK代理// 其实其为AspectJAwareAdvisorAutoProxyCreator类的proxyTargetClass属性useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);// 注册到spring的bean工厂中,再次校验是否已注册registerComponentIfNecessary(beanDefinition, parserContext);}@Nullablepublic static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);}@Nullableprivate static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");// 如果已经存在了自动代理创建器且存在的自动代理创建器与现在不一致,那么需要根据优先级来判断到底需要使用哪个if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);if (!cls.getName().equals(apcDefinition.getBeanClassName())) {int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());int requiredPriority = findPriorityForClass(cls);if (currentPriority < requiredPriority) {// 改变bean所对应的className的属性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;}

        我们来看下他实际注入的这个AspectJAwareAdvisorAutoProxyCreator的继承关系,可以看得到实际上他是BPP,我们知道BPP他是针对bean的一个后置处理器,也就是在这个后置处理器中生成对应的代理对象,在后续会看得到。

         我们继续往下看他配置文件解析,目前是解析AspectJ标签,所以会进入到parseAspect方法中。

/*** 解析切面* @param aspectElement* @param parserContext*/private void parseAspect(Element aspectElement, ParserContext parserContext) {// <aop:aspect> id属性String aspectId = aspectElement.getAttribute(ID);// aop ref属性,必须配置。代表切面String aspectName = aspectElement.getAttribute(REF);try {this.parseState.push(new AspectEntry(aspectId, aspectName));List<BeanDefinition> beanDefinitions = new ArrayList<>();List<BeanReference> beanReferences = new ArrayList<>();// 解析<aop:aspect>下的declare-parents节点// 采用的是DeclareParentsAdvisor作为beanClass加载List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);for (int i = METHOD_INDEX; i < declareParents.size(); i++) {Element declareParentsElement = declareParents.get(i);beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));}// We have to parse "advice" and all the advice kinds in one loop, to get the// ordering semantics right.// 解析其下的advice节点NodeList nodeList = aspectElement.getChildNodes();boolean adviceFoundAlready = false;for (int i = 0; i < nodeList.getLength(); i++) {Node node = nodeList.item(i);// 是否为advice:before/advice:after/advice:after-returning/advice:after-throwing/advice:around节点if (isAdviceNode(node, parserContext)) {// 校验aop:aspect必须有ref属性,否则无法对切入点进行观察操作if (!adviceFoundAlready) {adviceFoundAlready = true;if (!StringUtils.hasText(aspectName)) {parserContext.getReaderContext().error("<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",aspectElement, this.parseState.snapshot());return;}beanReferences.add(new RuntimeBeanReference(aspectName));}// 解析advice节点并注册到bean工厂中AbstractBeanDefinition advisorDefinition = parseAdvice(aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);beanDefinitions.add(advisorDefinition);}}AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);parserContext.pushContainingComponent(aspectComponentDefinition);// 解析aop:point-cut节点并注册到bean工厂List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);for (Element pointcutElement : pointcuts) {parsePointcut(pointcutElement, parserContext);}parserContext.popAndRegisterContainingComponent();}finally {this.parseState.pop();}}

        这里会后去AspectJ下面的子标签然后去解析对应的Advice对象。

        然后进入parseAdvice解析具体的advice对应,我们来看下advice方法的解析逻辑。

	/*** 解析通知类并注册到bean工厂** Parses one of '{@code before}', '{@code after}', '{@code after-returning}',* '{@code after-throwing}' or '{@code around}' and registers the resulting* BeanDefinition with the supplied BeanDefinitionRegistry.* @return the generated advice RootBeanDefinition*/private AbstractBeanDefinition parseAdvice(String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {try {this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));// create the method factory bean// 解析advice节点中的"method"属性,并包装为MethodLocatingFactoryBean对象RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);methodDefinition.getPropertyValues().add("targetBeanName", aspectName);methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));methodDefinition.setSynthetic(true);// create instance factory definition// 关联aspectName,包装为SimpleBeanFactoryAwareAspectInstanceFactory对象RootBeanDefinition aspectFactoryDef =new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);aspectFactoryDef.setSynthetic(true);// register the pointcut// 涉及point-cut属性的解析,并结合上述的两个bean最终包装为AbstractAspectJAdvice通知对象AbstractBeanDefinition adviceDef = createAdviceDefinition(adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,beanDefinitions, beanReferences);// configure the advisor// 最终包装为AspectJPointcutAdvisor对象RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);advisorDefinition.setSource(parserContext.extractSource(adviceElement));advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);if (aspectElement.hasAttribute(ORDER_PROPERTY)) {advisorDefinition.getPropertyValues().add(ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));}// register the final advisorparserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);return advisorDefinition;}finally {this.parseState.pop();}}

        然后在createAdviceDefinition创建对应需要的advice对象,我们跟进去看具体的逻辑。 

private AbstractBeanDefinition createAdviceDefinition(Element adviceElement, ParserContext parserContext, String aspectName, int order,RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {// 首先根据adviceElement节点分析出是什么类型的AdviceRootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));adviceDefinition.setSource(parserContext.extractSource(adviceElement));// 设置aspectName属性和declarationOrder属性adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);// 解析节点是否含有`returning`/`throwing`/`arg-names`,有则设置if (adviceElement.hasAttribute(RETURNING)) {adviceDefinition.getPropertyValues().add(RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));}if (adviceElement.hasAttribute(THROWING)) {adviceDefinition.getPropertyValues().add(THROWING_PROPERTY, adviceElement.getAttribute(THROWING));}if (adviceElement.hasAttribute(ARG_NAMES)) {adviceDefinition.getPropertyValues().add(ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));}// 设置构造函数的入参变量// Method/AspectJExpressionPointcut/AspectInstanceFactory三个入参ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);// 解析point-cut属性Object pointcut = parsePointcutProperty(adviceElement, parserContext);if (pointcut instanceof BeanDefinition) {cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);beanDefinitions.add((BeanDefinition) pointcut);}else if (pointcut instanceof String) {RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);beanReferences.add(pointcutRef);}cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);return adviceDefinition;}

        然后我们可以看得到对应的getAdviceClass,这里就是获取对应的代理切点的class,我们继续跟进去看下具体的获取逻辑。

	/*** Gets the advice implementation class corresponding to the supplied {@link Element}.*/private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {String elementName = parserContext.getDelegate().getLocalName(adviceElement);if (BEFORE.equals(elementName)) {return AspectJMethodBeforeAdvice.class;}else if (AFTER.equals(elementName)) {return AspectJAfterAdvice.class;}else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {return AspectJAfterReturningAdvice.class;}else if (AFTER_THROWING_ELEMENT.equals(elementName)) {return AspectJAfterThrowingAdvice.class;}else if (AROUND.equals(elementName)) {return AspectJAroundAdvice.class;}else {throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");}}

         其实我们可以看得到对应的advice对象的创建他需要三个参数,第一个是对应的method方法,也就是MethodLocatingFactoryBean,第二就是对应的Pointcut对象也就是对应的AspectJExpressionPointcut对象,第三个参数就是对应的BeanFactory对象,也就是SimpleBeanFactoryAwareAspectInstanceFactory对象。

        此时Advice对象的Definition对象已经创建好了,然后最终包装为AspectJPointcutAdvisor对象,所以解析AOP的xml文件提前生成的BeanDefinition对象如下图:

        以上是AOP需要提前准备的BeanDefinition,有了这些BeanDefinition之后,那么他是在哪里开始创建这些Bean对象,后续我们会继续分析,这里只分析AOP的xml配置文件解析。 

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

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

相关文章

汇川技术|CANlink、CANopen、Profibus-DP网络编辑器的使用

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 本节学习CANlink、CANopen、Profibus-DP网络编辑器的使用。 以下为学习笔记。 01 CANlink编辑器 在AC810的【网络组态】中未看到CANlink主站的功能&#xff0c;所以先简单了解&#xff0c;等具体使用时再具体查看。 …

2025上海国际显示技术及应用创展览会

DIC EXPO2025中国&#xff08;上海&#xff09;国际显示技术及应用创展览会 时间&#xff1a;2025年8月7-9日 地点&#xff1a;上海新国际博览中心 主办单位&#xff1a; 中国光学光电子行业协会液晶分会 联合主办&#xff1a; 中国电子材料行业协会 中国电子商会 韩国…

【iOS】——持久化

在iOS开发中&#xff0c;数据持久化是非常重要的&#xff0c;因为它允许应用程序在不同会话之间保存用户数据、设置、偏好等信息。 为什么数据持久化 数据保存&#xff1a; 目的&#xff1a;将应用程序中的数据保存到非易失性存储中&#xff0c;以便在应用程序关闭或重启后仍…

对零基础想转行网络安全同学的一点建议

最近有同学在后台留言&#xff0c;0基础怎么学网络安全&#xff1f;0基础可以转行做网络安全吗&#xff1f;以前也碰到过类似的问题&#xff0c;想了想&#xff0c;今天简单写一下。 我的回答是先了解&#xff0c;再入行。 具体怎么做呢&#xff1f; 首先&#xff0c;你要确…

WIFI7在游戏领域引发的变革

随着无线技术的快速进步&#xff0c;游戏体验正变得愈加丰富、复杂和逼真。现在最新的WIFI 7技术将带来新的飞跃&#xff0c;不仅有望重新定义网络游戏的体验&#xff0c;还有可能彻底革新整个游戏产业。可以想象一下&#xff0c;在未来&#xff0c;游戏世界不再有延迟和连接中…

VirtualFlow案例 | 油箱燃油晃动模拟,高效分析管路及油箱内油面变化

在探索流体行为模拟的领域&#xff0c;CFD技术为油箱燃油晃动模拟带来了革命性的转变。通过高精度的数值模拟&#xff0c;它不仅揭示了燃油在不同工况下的复杂动态&#xff0c;还为油箱设计的优化提供了关键洞察。这一技术在航空航天、汽车制造、船舶与海洋工程等多个行业中展现…

阿里云SSO和RAM权限控制及TLS安全设计实践

阿里云SSO和RAM概述 随着企业上云的趋势越来越明显&#xff0c;阿里云提供的各类服务成为了许多企业的首选。为了确保云上资源的安全管理&#xff0c;阿里云提供了单点登录&#xff08;SSO&#xff09;和资源访问管理&#xff08;RAM&#xff09;两种权限控制机制。 企业在使…

深入浅出消息队列----【延迟消息的实现原理】

深入浅出消息队列----【延迟消息的实现原理】 粗说 RocketMQ 的设计细说 RocketMQ 的设计这样实现是否有什么问题&#xff1f; 本文仅是文章笔记&#xff0c;整理了原文章中重要的知识点、记录了个人的看法 文章来源&#xff1a;编程导航-鱼皮【yes哥深入浅出消息队列专栏】 粗…

记录下Xjar部署问题

记录下 java Xjar部署问题 XjarXjar是什么&#xff1f;静态资源问题处理 项目是部署在客户端springboot项目&#xff0c;打包jar后静态资源都范文异常 net::ERR_CONTENT_LENGTH_MISMATCH 200 Xjar Xjar是什么&#xff1f; 无需侵入代码&#xff0c;只需要把编译好的JAR包通过…

springboot家校共育平台-计算机毕业设计源码54235

摘 要 采用高效的SpringBoot框架&#xff0c;家校共育平台为家长与教师提供了便捷的沟通渠道。该平台整合了丰富的教育资源&#xff0c;实现了家校之间的即时信息互通&#xff0c;从而助力协同教育。 为进一步方便用户访问和使用&#xff0c;平台与微信小程序进行了深度整合。家…

测试人生 | 招聘严峻期从面试无力感,到一天2个offer的一些经验分享(内附美团、字节、快手等面试题)

本人是霍格沃兹北京测试开发学社线下3期学员&#xff0c;拥有3年测试工作经验&#xff0c;之前一直在某大厂外包做软件测试&#xff0c;期间主要是以功能测试为主。 经过一个月的高强度找工作奋战&#xff0c;最终拿下了3家公司offer&#xff0c;选择了一家自己很满意的公司。…

4000元投影仪性价比之王:爱普生TW5750极米RS10还是当贝X5S?

买投影很多人会倾向于买大品牌或者是销量最好的那几款&#xff0c;首先是大品牌售后更有保障&#xff0c;口碑和销量也间接证明了这款投影是否值得买。这几年国内投影市场中爱普生、极米、当贝这三家投影品牌无论是在产品、口碑、售后服务等方面都是最好的&#xff0c;被用户们…

点对点的RPC通信功能测试(bug修复)

1.发现问题 处理rpc调用client客户端存在一些问题&#xff0c;数据反序列化的问题 rpc的调用方&#xff0c;数据的处理有些问题&#xff0c;我们返回的是true&#xff0c;应该是1&#xff0c;不是0. 返回值已经写道response里面。发回给调用方&#xff08;calluserservice.&…

有向图的拓扑排序以及判断是否有环

拓扑序列是顶点活动网中将活动按发生的先后次序进行的一种排列。 拓扑排序&#xff0c;是对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序&#xff0c;是将G中所有顶点排成一个线性序列&#xff0c;使得图中任意一对顶点u和v&#xff0c;若边(u,v)∈E(G)&#x…

【数据分享】全国县市2000-2022年教育、卫生和社会保障数据(免费获取)

《中国县域统计年鉴》是一部全面反映我国县域社会经济发展状况的资料性年鉴&#xff0c;收录了上一年度全国2000多个县域单位的基本情况、综合经济、农业、工业、教育、卫生、社会保障等方面的资料。 在之前的文章中&#xff0c;我们分享过基于2001-2023年《中国县域统计年鉴》…

idea自定义模版、快捷键

原文地址&#xff1a;【IDEA】常用插件、设置、注释_idea注释插件-CSDN博客 创建模版组&#xff1a;MyTemplates 创建模版&#xff1a;forThread&#xff1a;循环打印出10个线程 第四步 for (int i 1; i < 10; i) {new Thread(() -> {$END$}, String.valueOf(i)).star…

pytorch-广播机制

Broadcasting Key idea A[4,3] B[3] 在第一个维度前面插入一个维度 [3] > [1,3]将维度1扩展到与B维度1一样的尺寸 [1,3] > [4,3] broadcasting unsqueeze expand 为什么要使用broadcasting&#xff1f; 1、for example [class, student, scores]add bias for ever…

ESP8266 8x8点阵LED控制系统 日志2024/7/31

手机app: 内置主页配置 唯一不好的就是有一点问题就得全改一遍,来回修改格式很烦啊喂!~ 为什么要留个 主页控制? 有些人不是喜欢程序员的浪漫嘛,把index.html上传上去下次就是表白页面咯! 当然这只是鸡肋娱乐,真实功能其实就是用来美化html的, 如果不满意html 自己美化之…

JAVA后端拉取gitee仓库代码项目并将该工程打包成jar包

公司当前有一个系统用于导出项目&#xff0c;而每次导出的项目并不可以直接使用&#xff0c;需要手动从gitee代码仓库中获取一个模板代码然后将他们整合到一起它才是一个完整的项目&#xff0c;所以目前我的任务就是编写一个java程序可以自动地从gitee仓库拉取下来那个模板代码…

git学习准备阶段

准备阶段 ubantu下载安装git sudo apt-get install git查看git版本 git -v注册用户名 git config --global user.name [name][name]填入自己的名字&#xff0c;如果没有空格的情况下&#xff0c;可以不加引号,–global是在全局下操作&#xff0c;如果没有这个参数就只是在本…