Spring系列 AOP实现过程
文章目录
- 实现原理
- EnableAspectJAutoProxy
- AnnotationAwareAspectJAutoProxyCreator 代理创建过程
- wrapIfNecessary
- getAdvicesAndAdvisorsForBean
- findCandidateAdvisors
- findAdvisorsThatCanApply
- createProxy
- @AspectJ注解处理
- 代理调用过程
实现原理
本文源码基于spring-aop-5.3.31版本,测试代码如下:
@Aspect
@Component
public class UserAspect {@Pointcut("execution(* org.example..*..UserService.*(..))")private void pointcut() {}@Around("pointcut()")public Object aroundUserServiceOperation(ProceedingJoinPoint pjp) throws Throwable { //2long start = System.currentTimeMillis();Object retVal = pjp.proceed();long duration = System.currentTimeMillis() - start;System.out.printf("time for pointcut method is %d seconds%n", duration);return retVal;}
}@Service
public class UserService {public String login(String username, String password) {return String.format(" %s login with password: %s\n", username, password);}
}@RestController
@RequestMapping(value = "/api/user")
public class UserController {@ResourceUserService userService;@PostMapping("/login")public String login(@RequestParam("username") String username, @RequestParam("password") String password) {return userService.login(username, password);}
}
启动类
@SpringBootApplication
@EnableAspectJAutoProxy
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class, args);}
}
EnableAspectJAutoProxy
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {/*** 指定代理方式是CGLIB还是基于接口的动态代理*/boolean proxyTargetClass() default false;/*** 是否暴露代理对象,暴露可以通过AopContext拿到*/boolean exposeProxy() default false;
}
导入了这个AspectJAutoProxyRegistrar,实际上是一个ImportBeanDefinitionRegistrar实现。我们知道,@Import + ImportBeanDefinitionRegistrar结合使用的效果就是向容器中注入一些Bean。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {/*** 注册, 配置AspectJ代理*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 这一步就是所有的注册过程AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);AnnotationAttributes enableAspectJAutoProxy =AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);if (enableAspectJAutoProxy != null) {if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);}}}
}
通过AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
可以看到
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");// AUTO_PROXY_CREATOR_BEAN_NAME// org.springframework.aop.config.internalAutoProxyCreatorif (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) {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;
}
从上面的内容来看,AspectJAutoProxyRegistrar的工作就是注册一个AspectJAutoProxyCreator的东西,默认是org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
,如果容器种已经有了一个名称为org.springframework.aop.config.internalAutoProxyCreator
的Bean,那么会优先使用它,否则使用AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator 代理创建过程
AspectJAwareAdvisorAutoProxyCreator的子类,负责处理所有AspectJ注解,以及所有Spring的Advisor。所有AspectJ注解的类都会被自动识别
主要关注点,它是一个BeanPostProcessor
最终org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
找到了Bean的后置处理逻辑
@Override
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;}if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return null;}}// Create proxy here if we have a custom TargetSource.// Suppresses unnecessary default instantiation of the target bean:// The TargetSource will handle target instances in a custom fashion.TargetSource targetSource = getCustomTargetSource(beanClass, beanName);if (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}return null;
}/*** Create a proxy with the configured interceptors if the bean is* identified as one to proxy by the subclass.* @see #getAdvicesAndAdvisorsForBean*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {// bean 就是被代理对象if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
代理是需要创建代理对象和被代理对象的
wrapIfNecessary
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;}// 下面四个类不会被代理:Advice, Pointcut, Advisor, AopInfrastructureBeanif (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.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;
}
getAdvicesAndAdvisorsForBean
对于指定的Bean,获取可用的切面,如果没有切面,则无需生成代理
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator实现了这个方法
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 获取所有的AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 找到与beanClass相关联的那些AdvisorList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}
findCandidateAdvisors
/*** Find all candidate Advisors to use in auto-proxying.* @return the List of candidate Advisors*/
protected List<Advisor> findCandidateAdvisors() {Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");return this.advisorRetrievalHelper.findAdvisorBeans();
}
AnnotationAwareAspectJAutoProxyCreator中继承了此方法
@Override
protected List<Advisor> findCandidateAdvisors() {// Add all the Spring advisors found according to superclass rules.List<Advisor> advisors = super.findCandidateAdvisors();// Build Advisors for all AspectJ aspects in the bean factory.if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}
切面的查找实际上委托给了BeanFactoryAdvisorRetrievalHelper和BeanFactoryAspectJAdvisorsBuilder这两个类来完成的
BeanFactoryAdvisorRetrievalHelper用于从BeanFactory中获取org.springframework.aop.Advisor
接口的实现类,核心逻辑如下(省略了部分不重要的代码)
public List<Advisor> findAdvisorBeans() {// Determine list of advisor bean names, if not cached already.String[] advisorNames = this.cachedAdvisorBeanNames;if (advisorNames == null) {// 获取所有Advisor类型的Bean名称,同时缓存到 this.cachedAdvisorBeanNamesadvisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);this.cachedAdvisorBeanNames = advisorNames;}if (advisorNames.length == 0) {return new ArrayList<>();}List<Advisor> advisors = new ArrayList<>();for (String name : advisorNames) {if (isEligibleBean(name)) {// 忽略正在创建的Beanif (this.beanFactory.isCurrentlyInCreation(name)) {// 记录日志} else {// ....advisors.add(this.beanFactory.getBean(name, Advisor.class));// ....}}}return advisors;
}
BeanFactoryAspectJAdvisorsBuilder
在项目启动的过程中,项目中的所有切面会被 AnnotationAwareAspectJAutoProxyCreator 解析,它会找到切面中的每一个通知以及通知对应的切点,拿这二者构建一个新的对象,这个对象就是 Advisor。最后将所有解析得到的增强器注入到容器中。
BeanFactoryAspectJAdvisorsBuilder这个类的操作是获取所有Advisor
创建代理
findAdvisorsThatCanApply
此方法的作用是找到目标类可用的Advisor列表
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);} finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}
}
判断Advisor和目标类是否匹配
- IntroductionAdvisor:使用ClassFilter类进行匹配
- PointcutAdvisor:使用Pointcut来进行匹配
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor) {return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);}else if (advisor instanceof PointcutAdvisor) {PointcutAdvisor pca = (PointcutAdvisor) advisor;return canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {// It doesn't have a pointcut so we assume it applies.return true;}
}
PointCut类型的
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {Assert.notNull(pc, "Pointcut must not be null");if (!pc.getClassFilter().matches(targetClass)) {return false;}MethodMatcher methodMatcher = pc.getMethodMatcher();if (methodMatcher == MethodMatcher.TRUE) {// No need to iterate the methods if we're matching any method anyway...return true;}// AspectJExpressionPointcutIntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;}Set<Class<?>> classes = new LinkedHashSet<>();if (!Proxy.isProxyClass(targetClass)) {classes.add(ClassUtils.getUserClass(targetClass));}classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));for (Class<?> clazz : classes) {Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {if (introductionAwareMethodMatcher != null ?introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}
AspectJExpressionPointcut是一个MethodMatcher的实现,代表了使用AspectJ注解@Pointcut定义的一个切点,比如:
@Pointcut("execution(* org.example..*..UserService.*(..))")
private void pointcut() {
}
createProxy
找到Advisor之后下一步就是创建代理对象
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (proxyFactory.isProxyTargetClass()) {// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc);}}}else {// No proxyTargetClass flag enforced, let's apply our default checks...if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}return proxyFactory.getProxy(classLoader);
}
@AspectJ注解处理
@Aspect注解表示一个AspectJ配置类,里面可以配置切点和切面,以及二者之间的关系
注意:@Aspect在Spring中没有专门定义一个组件去扫描它,因此需要其他额外的配置将@AspectJ配置的那个类放入Spring容器
-
加@Component注解
-
使用@ComponentScan注解
-
如果使用XML配置,可以使用context:component-scan,还需要配置
<context:include-filter/>
,其中context:component-scan这个标签是扫描@Component、@Controller、@Service等这些注解的类,则把这些类注册为bean。它不扫描@Aspect注解的。所以需要在子标签添加 <context:include-filter type="annotation"expression=“org.aspectj.lang.annotation.Aspect”/>
代理调用过程
如果想跟踪Advice是如何执行的,以及Advice执行到被代理类方法执行的过程,该怎么办呢?
仅需要进行简单的设置,即可输出cglib以及jdk动态代理产生的class文件,然后工具查看生成的动态代理类
// 输出cglib动态代理产生的类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");
// 输出jdk动态代理产生的类
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
基于CGlib的代理类命名方式为:被代理类类名 + $$EnhancerBySpringCGLIB$$ + 8个随机字符,例如:UserService$$EnhancerBySpringCGLIB$$efcfcced
使用jd-gui工具查看反编译的字节码,如下图所示
可以在里面找到login方法
下面将login方法调用的相关内容拿出来看看
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.TargetClassAware;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Dispatcher;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;public class UserService$$EnhancerBySpringCGLIB$$efcfcced extends UserService implements SpringProxy, Advised, Factory {private MethodInterceptor CGLIB$CALLBACK_0;private static final MethodProxy CGLIB$login$0$Proxy;private static final Method CGLIB$login$0$Method;public final String login(String paramString1, String paramString2) {if (this.CGLIB$CALLBACK_0 == null)CGLIB$BIND_CALLBACKS(this); return (this.CGLIB$CALLBACK_0 != null) ? (String)this.CGLIB$CALLBACK_0.intercept(this, CGLIB$login$0$Method, new Object[] { paramString1, paramString2 }, CGLIB$login$0$Proxy): super.login(paramString1, paramString2);}
}
MethodInterceptor是spring-core提供的一个接口
public interface MethodInterceptor extends Callback {Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
intercept(Object obj, Method method, Object[] args, MethodProxy proxy)是 MethodInterceptor 的核心方法。在方法被调用时,Spring 会调用此方法,传递如下参数:
- obj:被代理的目标对象,即UserService
- method:被调用的方法,即login方法
- args:方法的参数
- proxy:用于调用目标方法的 MethodProxy 对象
现在只知道CGLIB$CALLBACK_0
是一个MethodInterceptor,但到底是哪个实现呢,我们可以通过Debug查看,如下图所示,可以看到它的实际类型是org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Object target = null;// this.advised -> ProxyFactoryTargetSource 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);// 获取拦截器链,即@Around, @Before等修饰的方法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() && CglibMethodInvocation.isMethodProxyCompatible(method)) {// 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 = invokeMethod(target, method, argsToUse, methodProxy);}else {// We need to create a method invocation...retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();}retVal = processReturnType(proxy, target, method, retVal);return retVal;}finally {if (target != null && !targetSource.isStatic()) {targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}
}
相关文章:
Spring系列 AOP实现过程
文章目录 实现原理EnableAspectJAutoProxyAnnotationAwareAspectJAutoProxyCreator 代理创建过程wrapIfNecessarygetAdvicesAndAdvisorsForBeanfindCandidateAdvisorsfindAdvisorsThatCanApply createProxy AspectJ注解处理代理调用过程 实现原理 本文源码基于spring-aop-5.3.…...
C语言 getchar 函数完全解析:掌握字符输入的关键
前言 在C语言中,getchar 是一个非常实用的函数,用于从标准输入流(通常是键盘)读取单个字符。这对于处理文本输入非常有用,尤其是在需要逐个字符处理的情况下。本文将深入探讨 getchar 函数的用法和特点,并…...
Docker安装mysql8并配置主从复制
1. 安装mysql8 1.1 新增挂载文件 # 新增mysql挂载文件夹 mkdir -p /root/docker/mysql/m01/log mkdir -p /root/docker/mysql/m01/data mkdir -p /root/docker/mysql/m01/conf1.2 新增mysql配置文件 # 新增mysql配置文件 cd /root/docker/mysql/m01/conf vim my.cnf # 下面是…...
快手:数据库升级实践,实现PB级数据的高效管理|OceanBase案例
本文作者:胡玉龙,快手技术专家 快手在较初期采用了OceanBase 3.1版本成功替换了多个核心业务、数百套的MySQL集群。至2023年,快手的数据量已突破800TB大关,其中最大集群的数据量更是达到了数百TB级别。为此,快手将数据…...
基于Node.js+Express+MySQL+VUE实现的计算机毕业设计共享单车管理网站
单车信息选择骑行 骑行状态留言公告/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序 目录 功能图 界面展示 开发目标 开发背景意义 开发意义 开发目的 项目概述 技术选型与理由 系统设计与功能实现 项目可执行性分析 系统架构需求 性能需…...
人工智能辅助的神经康复
人工智能辅助的神经康复是通过应用人工智能(AI)技术来改善神经系统损伤患者的康复过程。此领域结合了深度学习、数据分析和机器人技术,旨在提升康复效果、个性化治疗方案和监测进展。以下是该领域的关键组成部分和应用: 1. 康复评…...
KKT实际运用 -MATLAB
FMINCON函数可以很方便的求出:fun:目标函数,即需要最小化的函数,输入参数为向量x,输出为标量f(x)。x0:初始点,即求解过程的起始点,可以是标量、向量或矩阵。A和b:线性不等…...
php在线相册
1、将静态页面效果完成 解压到www里 整个数据 暂时是错误的 建立连接密码为root 运行sql文件 右键根目录刷新 刷新后成功 开始 测试 如果需要上传照片,点击创建相册,选择上传文件,选择文件后退出 导入alumbenew2 2.提交表单方式 3.利用ph…...
Xcode手动安装SDK模拟器
1.下载SDK模拟器&Xcode SDK和Xcode官方下载地址 2.下载好后使用命令将SDK导入到Xcode中如下命令 注:我是在/Applications 目录下执行的命令,模拟其地址直接拖拽过来 sudo xcode-select -s Xcode.app xcodebuild -runFirstLaunch xcodebuild -imp…...
Docker安装consul + go使用consul + consul知识
1. 什么是服务注册和发现 假如这个产品已经在线上运行,有一天运营想搞一场促销活动,那么我们相对应的【用户服务】可能就要新开启三个微服务实例来支撑这场促销活动。而与此同时,作为苦逼程序员的你就只有手动去 API gateway 中添加新增的这…...
JWT 漏洞 - 学习手册
0x01:JWT 前导知识 0x0101:JWT 详解 0x02:JWT 漏洞介绍 0x0201:JWT 漏洞介绍 0x03:JWT 挖掘思路 JWT 漏洞挖掘思路 - JWT Payload 敏感信息泄露 备注:通过泄露的 JWT Payload 获取用户的敏感信息&#…...
HTML【知识改变命运】03font 字体标签
题目:在页面上显示"北京"两个字,字体为微软雅黑,颜色为红色,大小为40xp; font标签可以修饰字体的大小,颜色,和字体 属性:color颜色,face字体,size大…...
集师专属知识付费小程序搭建 心理咨询小程序搭建
一、产品简介 集师SaaS知识付费软件,为知识创业者或商家提供一站式内容交付解决方案,助力商家搭建集品牌传播、商业变现和用户运营于一体的线上知识服务系统,覆盖全渠道经营场景,占据每个流量入口,使流量变现快速高效…...
https://www.aitoolpath.com/ 一个工具数据库,目前储存了有2000+各种工具。每日更新
AI 工具爆炸?别怕,这个网站帮你整理好了! 哇塞,兄弟们!AI 时代真的来了!现在各种 AI 工具跟雨后春笋似的,噌噌噌地往外冒。AI 写作、AI 绘画、AI 代码生成……简直是要逆天啊! 可是…...
科技的成就(六十三)
583、八小时工作制 最先提出这种理念的人竟然也是一名企业家,而且还是一名空想社会主义者。这名叫做罗伯特欧文的英国人,也凭借先进的人本管理理念成为了现代人事管理之父。 584、SDN(软件定义网络) "SDN(软件定…...
浅谈抗量子密码学:保护未来的数字安全
一、引言 随着量子计算机技术的发展,传统的加密算法面临前所未有的挑战。量子计算机利用量子位(qubits)的特性,能够在理论上比经典计算机更快地破解现有的加密系统。为了应对这一威胁,研究者们正在开发所谓的“抗量子…...
10款物联网开源嵌入式操作系统对比分析
摘要 本文对目前市场上广受欢迎的10款物联网开源嵌入式操作系统进行了深度对比分析,包括Huawei LiteOS、RT-Thread、AliOS Things等。通过探讨这些操作系统的实时性、可扩展性、特点、运行要求、开发社区活跃度和应用领域等方面,帮助开发者更好地理解它…...
Elasticsearch深度攻略:核心概念与实践应用
目录 一、Elasticsearch简介1、Elasticsearch是什么2、Elasticsearch的应用场景3、Elasticsearch的核心概念 二、Elasticsearch安装与配置1、安装Elasticsearch(1)系统要求(2)下载Elasticsearch(3)解压安装…...
TLS详解
什么是TLS TLS(Transport Layer Security)传输层安全性协议 ,它的前身是SSL(Secure Sockets Layer)安全套接层,是一个被应用程序用来在网络中安全的通讯协议, 防止电子邮件、网页、消息以及其他协议被篡改或是窃听。是用来替代SSL的…...
正则表达式中的特殊字符
正则表达式中的特殊字符 字符类预定义字符类量词锚点分组和捕获选择、分支和条件反义和否定转义字符示例 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 在正则表达式中,有许…...
EP42 公告详情页
文件路径: E:/homework/uniappv3tswallpaper/api/apis.js 先添加相应的api。 import {request } from "/utils/requset.js"export function apiGetBanner() {return request({url: "/homeBanner"}) } export function apiGetDayRandom() {ret…...
游戏找不到xinput1_3.dll的原因及解决方法
1. xinput1_3.dll 基本信息 1.1 文件名 xinput1_3.dll 是一个动态链接库(DLL)文件,它属于 Microsoft DirectX for Windows 的一部分。这个文件主要负责处理与 Xbox 360 控制器和其他兼容 XInput 标准的游戏手柄相关的输入信号,确…...
防反接电路设计
方案1 串联二极管, 优点:成本低、设计简单 缺点:损耗大,P ui 方案2 串联自恢复保险丝 当电源反接的时候,D4导通,F2超过跳闸带你留,就会断开,从而保护了后级电路 方案3 H桥电路…...
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
SpringMVC九大内置组件之HandlerMapping处理器映射器-AbstractHandlerMethodMapping类以及子类RequestMappingHandlerMapping如何将Controller修饰的注解类以及类下被注解RequestMapping修饰的方法存储到处理器映射器中。 从RequestMappingHandlerMapping寻找: AbstractHandle…...
毕业设计选题:基于ssm+vue+uniapp的购物系统小程序
开发语言:Java框架:ssmuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:M…...
【动态规划-最长公共子序列(LCS)】力扣583. 两个字符串的删除操作
给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 示例 1: 输入: word1 “sea”, word2 “eat” 输出: 2 解释: 第一步将 “sea” 变为 “ea” ,第二步将 "e…...
【分布式微服务云原生】8分钟探索RPC:远程过程调用的奥秘与技术实现
摘要 在分布式系统中,RPC(Remote Procedure Call,远程过程调用)技术是连接各个组件的桥梁。本文将深入探讨RPC的概念、技术实现原理、以及请求处理的详细过程。通过清晰的结构、流程图、代码片段和图表,我们将一起揭开…...
Linux操作系统中Redis
1、什么是Redis Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 可以理解成一个大容量的map。…...
每日论文5—06TCAS2锁相环电流匹配的gain-boosting电荷泵
《Gain-Boosting Charge Pump for Current Matching in Phase-Locked Loop》 06TCAS2 本质上和cascode来增加输出电阻,从而减小电流变化的思路是一样的。这里用了放大器来增加输出电阻。具体做法如下图: 如图1(a),A3把Vb和Vx拉平࿰…...
接口隔离原则(学习笔记)
客户端不应该被迫依赖于它不使用的方法:一个类对另一个类的依赖应该建立在最小的接口上。 上面的设计我们发现他存在的问题,黑马品牌的安全门具有防盗,防水,防火的功能。现在如果我们还需要再创建一盒传智品牌的安全门,…...
学做面包网站/最新新闻热点话题
大家好,我叫亓官劼(q guān ji ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客,亓官劼的博客2。 第十一届 蓝桥杯 省 模拟赛 完整题目题…...
wordpress视频主题/seo专员是做什么的
学完Java初级也一年了,很多东西都忘的差不多了,今天趁着周日再翻开之前的笔记本好好复习下。 1.java语言是一种编译解释型语言,java编译器将java代码编译成字节码指令存放到.class文件中,运行时使用特定的 java解释器把字节码指令…...
泉州网站关键词推广/学seo如何入门
Java之封装与访问权限控制(一)对于封装的概念,我总觉得自己还是挺了解的,但是真要我说,还真说不出个啥来。我只能默默地通过身边的例子加上书本理论完善我对封装的认识。就比如,我们在玩游戏的时候,我们只能通过完成指…...
58同城保定网站建设/企业站seo价格
问题 Image和Label数据成对写入TFRecord文件,按理训练过程中读取的Image和Label也应该是一一对应的,但有的时候发现Image和Label并不能匹配。如: 将以下数据写入TFrecord中: Image 1 —— Label 1 Image 2 —— Label 2 Image …...
wordpress登录密码错误也不报错/百度推广竞价是什么意思
2019年高考结束最后一门英语考试的时候,王恒杰这个懂得感恩的大男孩被我们记住了。在高考英语结束后,所有人都放下了心中的担心,轻轻松松的走出了考场。此刻王恒杰也是万千学子中的一名,但是,他之后的举动却让他在众多…...
bootstrap设计的精美网站/软文营销经典案例
敏捷开发 敏捷个人敏捷一词已席卷软件界。 敏捷已经远远超过了它的炒作周期。 人们对敏捷的认识日益提高,但是仍然存在一些疑问,尤其是开发人员和测试人员。 那么,敏捷有何不同?如果您是开发人员或测试人员,这有什么关…...