当前位置: 首页 > news >正文

Spring基础(1):两个概念

最近看了点Spring的源码,于是来稍微扯一扯,希望能帮一部分培训班出身的朋友撕开一道口子,透透气。

广义上的Spring指的是Spring整个项目,包含SpringBoot、SpringCloud、SpringFramework、SpringData等等,

本系列文章只讨论狭义上的Spring,也就是SpringFrameWork。

主要内容:

  • 盲点
  • Spring说,万物皆可定义
  • 默默付出的后置处理器
  • 利用后置处理器返回代理对象

盲点

如果你恰好非科班转行且从未独立看过源码,那么你很可能至今都不曾注意某两个概念。

你以为我会说IOC和AOP?NO。

看到这里,一部分读者心里一惊:卧槽,说的啥玩意,Spring不就IOC和AOP吗?!这两个都不说,你这篇文章为啥能写这么长?

不错,我就是这么长。其实我要讲的是:

  • BeanDefinition
  • BeanPostProcessor

大部分人一听到“请你谈谈对Spring的理解”,就会下意识地搬出IOC和AOP两座大山,赶紧糊弄过去。大概是这样的:

IOC

所谓的控制反转。通俗地讲,就是把原本需要程序员自己创建和维护的一大堆bean统统交由Spring管理。

也就是说,Spring将我们从盘根错节的依赖关系中解放了。当前对象如果需要依赖另一个对象,只要打一个@Autowired注解,Spring就会自动帮你安装上。

AOP

所谓的面向切面编程。通俗地讲,它一般被用来解决一些系统交叉业务的织入,比如日志啦、事务啥的。打个比方,UserService的method1可能要打印日志,BrandService的method2可能也需要。亦即:一个交叉业务就是要切入系统的一个方面。具体用代码展示就是:

AOP图一:这个切面,可以是日志,也可以是事务

交叉业务的编程问题即为面向切面编程。AOP的目标就是使交叉业务模块化。做法是将切面代码移动到原始方法的周围:

AOP图二

原先不用AOP时(图一),交叉业务的代码直接硬编码在方法内部的前后,而AOP则是把交叉业务写在方法调用前后。那么,为什么AOP不把代码也写在方法内部的前后呢?两点原因:

  • 首先,这与AOP的底层实现方式有关:动态代理其实就是代理对象调用目标对象的同名方法,并在调用前后加增强代码。

InvocationHandler介于代理对象和目标对象中间,作用有两个:1.衔接调用链 2.存放增强代码

  • 其次,这两种最终运行效果是一样的,所以没什么好纠结的。

而所谓的模块化,我个人的理解是将切面代码做成一个可管理的状态。比如日志打印,不再是直接硬编码在方法中的零散语句,而是做成一个切面类,通过通知方法去执行切面代码。

我相信大部分培训班出来的朋友也就言尽于此,讲完上面内容就准备打卡下班了。

怎么说呢,IOC按上面的解释,虽然很浅,但也马马虎虎吧。然而AOP,很多人对它的认识是非常片面的...

这样吧,我问你一个问题,现在我自己写了一个UserController,以及UserServiceImpl implements UserService,并且在UserController中注入Service层对象:

@Autowired
private UserService userService;

那么,这个userService一定是我们写的UserServiceImpl的实例吗?

如果你听不懂我要问什么,说明你对Spring的AOP理解还是太少了。

实际上,Spring依赖注入的对象并不一定是我们自己写的类的实例,也可能是userServiceImpl的代理对象。下面分别演示这两种情况:

  • 注入userServiceImpl对象

注入的是UserServiceImpl类型

  • 注入userServiceImpl的代理对象(CGLib动态代理)

注入的是CGLib动态代理生成的userServiceImpl的代理对象

为什么两次注入的对象不同?

因为第二次我给UserServiceImpl加了@Transactional 注解。

此时Spring读取到这个注解,便知道我们要使用事务。而我们编写的UserService类中并没有包含任何事务相关的代码。如果给你,你会怎么做?

动态代理嘛!

上面说了,InvocationHandler作用有两个:1.衔接调用链, 2.存放增强代码。

用动态代理在InvocationHandler的invoke()中开启关闭事务即可完成事务控制。

看到这里,我仿佛听到有一部分兄弟默默说了句:卧槽,原来是这样...

但是,上面对IOC和AOP的理解,也仅仅是应用级别,是一个面。仅仅了解到这个程度,对Spring的了解还是非常扁平的,不够立体。


Spring说,万物皆可定义

上帝说,要有光。于是特斯拉搞出了交流电。

Java说,万物皆对象。但是Spring另外搞了BeanDefinition...

什么BeanDefinition呢?其实它是bean定义的一个顶级接口:

正如BeanDefinition接口的注释所言:一个BeanDefinition是用来描述一个bean实例的

哎呀卧槽,啥玩意啊。描述一个bean实例?我咋想起了Class类呢。

其实,两者并没有矛盾。

BeanDefinition的实现类很多,这里仅以AbstractBeanDefinition为例,它实现了BeanDefinition

Class只是描述了一个类有哪些字段、方法,但是无法描述如何实例化这个bean!如果说,Class类描述了一块猪肉,那么BeanDefinition就是描述如何做红烧肉:

  • 单例吗?
  • 是否需要延迟加载?
  • 需要调用哪个初始化方法/销毁方法?
在容器内部,这些bean定义被表示为BeanDefinition对象,包含以下元数据:

1.包限定的类名:通常,定义bean的实际实现类。
2.Bean行为配置:它声明Bean在容器中的行为(范围、生命周期回调,等等)。
3.Bean依赖:对其他Bean的引用。
4.对当前Bean的一些设置:例如,池的大小限制或在管理连接池的bean中使用的连接数。
——Spring官方文档

大部分初学者以为Spring解析<bean/>或者@Bean后,就直接搞了一个bean存到一个大Map中,其实并不是。

  • Spring首先会扫描解析指定位置的所有的类得到Resources(可以理解为读取.Class文件)
  • 然后依照TypeFilter和@Conditional注解决定是否将这个类解析为BeanDefinition
  • 稍后再把一个个BeanDefinition取出实例化成Bean

就好比什么呢?你从海里吊了一条鱼,但是你还没想好清蒸还是红烧,那就干脆先晒成鱼干吧。一条咸鱼,其实蕴藏着无限可能,因为它可能会翻身!


默默付出的后置处理器

接下来,我们讨论一下咸鱼如何翻身。

最典型的例子就是AOP。上面AOP的例子中我说过了,如果不加@Transactional,那么Controller层注入的就是普通的userServiceImpl,而加了注解以后返回的实际是代理对象。

为什么Spring要返回代理对象?因为我们压根就没在UserServiceImpl中写任何commit或者rollback等事务相关的代码,但是此时此刻代理对象却能完成事务操作。毫无疑问,这个代理对象已经被Spring加了佐料(事务增强代码)。

那么Spring是何时何地加佐料的呢?说来话长,我们先绕个弯子。

大部分人把Spring比作容器,其实潜意识里是将Spring完全等同于一个Map了。其实,真正存单例对象的Map,只是Spring中很小很小的一部分,仅仅是BeanFactory子类的一个字段,我更习惯称它为“单例池”。

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

这里的ApplicationContext和BeanFactory是接口,实际上都有各自的子类。比如注解驱动开发时,Spring中最关键的就是AnnotationConfigApplicationContext和DefaultListableBeanFactory。

所以,很多人把Spring理解成一个大Map,还是太肤浅了。就拿ApplicationContext来讲,它也实现了BeanFactory接口,说明它其实也是一个容器。但是同为容器,与BeanFactory不同的是,ApplicationContext主要用来包含各种各样的组件,而不是存bean:

ApplicationContext的部分组件示意图(包括Bean工厂)

那么,Spring是如何给咸鱼加佐料(事务代码的织入)的呢?关键就在于后置处理器。

后置处理器其实可以分好多种,属于Spring的扩展点之一:

前三个BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是后置处理器,这里篇幅有限,暂且先只介绍一下BeanPostProcessor。

BeanFactoryPostProcessor是处理BeanFactory的,所以存在ApplicationContext中。而BeanPostProcessor是处理Bean的,所以存在BeanFactory中,请务必注意!

BeanFactoryPostProcessor是用来干预BeanFactory创建的,而BeanPostProcessor是用来干预Bean的实例化。不知道大家有没有试过在普通Bean中注入ApplicationContext实例?你第一时间想到的是:

@Autowired
ApplicationContext annotationConfigApplicationContext;

除了利用Spring本身的IOC容器自动注入以外,你还有别的办法吗?

我们可以让Bean实现ApplicationContextAware接口:

实现ApplicationContextAware接口,并实现setApplicationContext()方法,用成员变量去接收形参applicationContext

后期,Spring会调用setApplicationContext()方法传入ApplicationContext实例。

Spring官方文档:
一般来说,您应该避免使用它,因为它将代码耦合到Spring中,并且不遵循控制反转样式。

这是我认为Spring最牛逼的地方:代码具有高度的可扩展性,甚至你自己都懵逼,为什么实现了一个接口,这个方法就被莫名其妙调用,还传进了一个对象...

这其实就是后置处理器的工作!

什么意思呢?

也就是说,虽然表面上在我们只要让Bean实现一个接口就能完成ApplicationContext组件的注入,看起来很简单,但是背地里Spring做了很多事情。Spring会在框架的某一处搞个for循环,遍历当前容器中所有的BeanPostProcessor,其中就包括一个叫ApplicationContextAwareProcessor的后置处理器,它的作用是:处理实现了ApplicationContextAware接口的Bean。

上面这句话有点绕,大家停下来多想几遍。

Spring Bean的生命周期,创建过程必然经过BeanPostProcessor

要扩展的类(Bean)是不确定的,但是处理扩展类的流程(循环BeanPostProcessor)是写死的。因为一个程序,再怎么高度可扩展,总有一个要定下来吧。也就是说,在这个Bean实例化的某一紧要处,必然要经过很多BeanPostProcessor。但是,BeanPostProcessor也不是谁都处理,有时也会做判断。比如:

if (bean instanceof Aware) {if (bean instanceof EnvironmentAware) {((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());}if (bean instanceof EmbeddedValueResolverAware) {((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);}if (bean instanceof ResourceLoaderAware) {((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);}if (bean instanceof ApplicationEventPublisherAware) {((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);}if (bean instanceof MessageSourceAware) {((MessageSourceAware) bean).setMessageSource(this.applicationContext);}if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);}
}

所以,此时此刻一个类实现ApplicationContextAware接口,有两层含义:

  • 作为后置处理器的判断依据,只有你实现了该接口我才处理你
  • 提供被后置处理器调用的方法


利用后置处理器返回代理对象

大致了解Spring Bean的创建流程后,接下来我们尝试着用BeanPostProcessor返回当前Bean的代理对象。

pom.xml

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.12.RELEASE</version></dependency>
</dependencies>

AppConfig

@Configuration //JavaConfig方式,即当前配置类相当于一个applicationContext.xml文件
@ComponentScan //不写路径,则默认扫描当前配置类(AppConfig)所在包及其子包
public class AppConfig {}

Calculator

public interface Calculator {public void add(int a, int b);
}

CalCulatorImpl

@Component
public class CalculatorImpl implements Calculator {public void add(int a, int b) {System.out.println(a+b);}
}

后置处理器MyAspectJAutoProxyCreator

使用步骤:

  1. 实现BeanPostProcessor
  2. @Component加入Spring容器
@Component
public class MyAspectJAutoProxyCreator implements BeanPostProcessor {public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {final Object obj = bean;//如果当前经过BeanPostProcessors的Bean是Calculator类型,我们就返回它的代理对象if (bean instanceof Calculator) {Object proxyObj = Proxy.newProxyInstance(this.getClass().getClassLoader(),bean.getClass().getInterfaces(),new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始计算....");Object result = method.invoke(obj, args);System.out.println("结束计算...");return result;}});return proxyObj;}//否则返回本身return obj;}
}

测试类

public class TestPostProcessor {public static void main(String[] args) {System.out.println("容器启动成功!");AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();//打印当前容器所有BeanDefinitionfor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}System.out.println("============");//取出Calculator类型的实例,调用add方法Calculator calculator = (Calculator) applicationContext.getBean(Calculator.class);calculator.add(1, 2);
}

先把MyAspectJAutoProxyCreator的@Component注释掉,此时Spring中没有我们自定义的后置处理器,那么返回的就是CalculatorImpl:

把@Component加上,此时MyAspectJAutoProxyCreator加入到Spring的BeanPostProcessors中,会拦截到CalculatorImpl,并返回代理对象:

代理对象的add()方法被增强:前后打印日志


本文是Spring源码系列的第一篇,仅仅是介绍了两个重要概念:BeanDefinition和BeanPostProcessor。更详细的内容, 比如Bean的生命周期流程及其它后置处理器的介绍,以后有机会再慢慢更新。通过本文,大家只要有朦胧的Spring Bean生命周期的概念,以及知道BeanDefinition和BeanPostProcessor即可。

相关文章:

Spring基础(1):两个概念

最近看了点Spring的源码&#xff0c;于是来稍微扯一扯&#xff0c;希望能帮一部分培训班出身的朋友撕开一道口子&#xff0c;透透气。 广义上的Spring指的是Spring整个项目&#xff0c;包含SpringBoot、SpringCloud、SpringFramework、SpringData等等&#xff0c; 本系列文章…...

国产化精密划片机已得到国内更多厂家青睐

国产化精密划片机在近年来得到了国内许多厂家的青睐&#xff0c;这是因为精密划片机在工业生产中有着重要作用。这种设备主要用于高精密切割加工&#xff0c;适用于多种材料&#xff0c;包括硅、石英、氧化铝、氧化铁等。 以精密晶圆划片机为例&#xff0c;这种设备采用了自主研…...

Voice Control for ChatGPT简单高效的与ChatGPT进行交流学习。

快捷又不失灵活性 日常生活中&#xff0c;我们与亲人朋友沟通交流一般都是喜欢语音的形式来完成的&#xff0c;毕竟相对于文字来说语音就不会显的那么的苍白无力&#xff0c;同时最大的好处就是能解放我们的双手吧&#xff0c;能更快实现两者间的对话&#xff0c;沟通便更高效…...

flutter生态一统甜夏 @Android @ios @windowse @macos @linux @Web

(愿景)G o o g l e 中 国flutter生态一统天下(IT) Web Android ios Windowse Macos Linux Google中国https://space.bilibili.com/64169458 https://pub-web.flutter-io.cn 构建 Flutter Web 应用 构建 Flutter Web 应用 - Flutter 中文文档 - Flutter 中文开发者网站 …...

计算机基础知识49

三板斧的使用(views.py) 三个方法&#xff1a;HttpResponse: 返回的是字符串render : 返回html文件redirect : 返回加载HTML页面的 def html(request):print(from html)# return HttpResponse(request) # 它返回的是字符串return render(request,html.html) # 返回html# ret…...

el-table给某一行加背景色

数据列表中总价大于100的一行背景色为红色&#xff0c;效果图如下&#xff1a; 代码示例&#xff1a; <template><div id"app"><!-- 测试区域&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&am…...

搭建 Makefile+OpenOCD+CMSIS-DAP+Vscode arm-none-eabi-gcc 工程模板

STM32F407-GCC-Template Arm-none-eabi-gcc MakefileOpenOCDCMSIS-DAPVscode工程模板 一、本次环境搭建所用的软硬件 1&#xff09;Windows or Linux (本文以Windows为主) 2&#xff09;JLink、Daplink、Wch-Link烧录器 3&#xff09;GNU Arm Embedded Toolchain交叉编译…...

Unity场景ab包加载压缩(LZ4,LZMA)格式的测试

情况 最近场景越来越大&#xff0c;大概800M的场景加载时间可能长达40秒左右&#xff0c;所以需要测试看看发生了什么。 测试环境 测试环境Win10&#xff0c;21thI5-12600KF&#xff0c;32GRam &#xff0c; Nvidia GF RTX2060 32G Scene1大小&#xff1a;741M 加载代码 首…...

私有化部署大模型:5个.Net开源项目

从零构建.Net前后端分离项目 今天一起盘点下&#xff0c;10月份推荐的5个.Net开源项目&#xff08;点击标题查看详情&#xff09;。 1、BootstrapBlazor企业级组件库&#xff1a;前端开发的革新之路 BootstrapBlazor是一个用于构建现代Web应用程序的开源框架&#xff0c;它基…...

安卓系统手机便签app使用哪一款?

在现代快节奏的生活中&#xff0c;我们经常会遇到各种繁忙的事务和容易遗忘的备忘事项。为避免大家遗忘重要的事情&#xff0c;大家可以在常用的手机上安装记录备忘事项的工具&#xff0c;为了帮助安卓用户高效地记录和管理这些信息&#xff0c;今天我将向大家推荐一款功能强大…...

SpringCloud-Gateway无法使用Feign服务(2021.X版本)

Spring Cloud Gateway 2021.x版本&#xff0c;无法使用Feign调用其他服务接口。 问题原因&#xff1a; 在官网的 issue 里面找到了相关的问题。 How to call another micro-service on GatewayFilterFactory ? Issue #1090 spring-cloud/spring-cloud-gateway GitHubHel…...

基于SSM的建筑装修图纸管理平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…...

Apache Doris (五十二): Doris Join类型 - Broadcast Join

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. Broadcast Join原理...

Docker从入门到上天系列第四篇:docker平台入门图解与平台架构图解

大神推荐:作者有幸结识技术大神孙哥为好友获益匪浅,现在把孙哥作为朋友分享给大家。 孙哥链接:孙哥个人主页 作者简介:一个颜值99分,只比孙哥差一点的程序员。 本专栏简介:话不多说,让我们一起干翻Docker 本文章简介:话不多说,让我们讲清楚Docker的平台入门图解和平台…...

安全防御——四、防火墙理论知识

安全防御 一、防火墙1、防火墙是如何诞生的2、防火墙如何分类2.1 软件型防火墙2.1.1 个人防火墙2.1.2 网关型防火墙 3、硬件型防火墙4、防火墙的技术类型5、代理服务器什么是代理 6、防火墙的接口模式7、防火墙抵御的攻击7.1 DDoS攻击7.2 单包攻击7.3 用户行为不受控7.4 威胁安…...

如何给PPT幻灯片解除密码保护以防止编辑

如果您忘记了ppt幻灯片的密码或无法编辑它&#xff0c;不要担心&#xff0c;提供了一种安全、简单易操作的方法来找回密码或解除限制。具体步骤如下&#xff1a;第一步百度搜索密码帝官网&#xff0c;第二步在用户中心上传需要解密的文件即可。 无论您是使用手机还是电脑&#…...

在linux安装单机版hadoop-3.3.6

一、下载hadoop https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/core/hadoop-3.3.6/ 二、配置环境变量 1、配置java环境变量 2、配置hadoop环境变量 export HADOOP_HOME/usr/local/bigdata/hadoop-3.3.6 export HBASE_HOME/usr/local/bigdata/hbase-2.5.6 export JA…...

Hadoop相关

hdfs getconf -confKey dfs.namenode.http-address 查看Hadoop工作端口的信息 hdfs getconf -confKey dfs.datanode.http.address 查看HDFS的NameNode组件的HTTP端口。...

ArcGIS 气象风场等示例 数据制作、服务发布及前端加载

1. 原始数据为多维数据 以nc数据为例。 首先在pro中需要以多维数据的方式去添加多维数据&#xff0c;这里的数据包含uv方向&#xff1a; 加载进pro的效果&#xff1a; 这里注意 数据属性需要为矢量uv&#xff1a; 如果要发布为服务&#xff0c;需要导出存储为tif格式&…...

【Axure高保真原型】树切换动态面板案例

今天和大家分享树切换动态面板的原型模板&#xff0c;点击树的箭头可以打开或者收起子节点&#xff0c;点击最后一级人物节点&#xff0c;可以切换右侧面板的状态到对应的页面&#xff0c;左侧的树是通过中继器制作的&#xff0c;使用简单&#xff0c;只需要按要求填写中继器表…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...