学习记录:DAY22
我的重生开发之旅:优化DI容器,git提交规范,AOP处理器,锁与并发安全
前言
我重生了,重生到了五一开始的一天。上一世,我天天摆烂,最后惨遭实习生优化。这一世,我要好好内卷…
今天的目标是继续优化DI容器的功能,尝试兼容AOP,并简单做一个后端联调测试。
----------5.2-----------
今天继续完善DI容器,如果有时间就研究一下多线程。
日程
8点,起的最早的一集。
被类加载器问题卡了一下午,晚上先摸摸🐟,看看git提交的规范
晚上9点,不摸了不摸了
凌晨1点了,被AOP逼疯了。
----------5.2-----------
弄了一早上,可算把AOP弄出来了,下午写写代码分析
晚上快8点了,来看看多线程问题
学习内容
省流:
- 优化DI容器
- 类加载器隔离问题和类加载器桥接
- 关于git提交的一些规范
- 手搓AOP处理器
- 锁与并发安全
1. 优化DI容器
先来看看现在存在的问题:
-
循环依赖处理不足:
- 当前实现只能检测循环依赖,但没有解决它(比如通过三级缓存)。
- 对于构造器注入的循环依赖无法处理。
-
作用域支持有限:
- 只有单例(Singleton)和原型(未标注时)两种作用域。
- 缺少request/session等其他常用作用域。
-
接口映射问题:
- 一个接口只能有一个实现类(第一个遇到的会被保留)。
- 没有处理多个实现的情况(比如通过
@Qualifier
)。
-
初始化顺序问题:
- 没有考虑依赖的初始化顺序。
- 缺少
@PostConstruct
等生命周期回调支持。
-
配置灵活性不足:
- 缺少XML/注解/JavaConfig等多种配置方式。
- 没有环境配置(dev/test/prod)支持。
-
性能问题:
- 每次
getBean
都会反射创建新实例(原型作用域时)。 - 没有缓存反射元数据。
- 每次
-
类型安全:
- 大量使用强制类型转换(cast)。
- 泛型支持不完善。
-
线程安全性:
- 没有考虑并发场景下的线程安全。
- singletonInstances等集合不是并发安全的。
-
异常处理:
- 异常信息不够详细。
- 缺少特定的异常类型。
-
扩展性:
- 没有提供扩展点(如
BeanPostProcessor
)。 - 不支持AOP等高级功能。
- 没有提供扩展点(如
-
资源管理:
- 没有提供销毁钩子或资源清理机制。
- 对于需要close的资源没有特殊处理。
-
测试支持:
- 缺少mock注入等测试支持功能。
-
其他功能缺失:
- 不支持条件化Bean(
@Conditional
)。 - 不支持profile。
- 不支持懒加载(
@Lazy
)。 - 不支持
@Value
等属性注入。
- 不支持条件化Bean(
解决方案:
1)支持多包扫描,并写入配置文件
这个比较简单,就不记录了。
2)预处理依赖
// 构建依赖图 查询解析依赖的Bean名称,查询其被@KatAutowired标记的子段,
// 然后通过拓扑排序重排依赖处理顺序
private void buildDependencyGraph() {// 初始化图和入度registry.getClassRegistry().keySet().forEach(beanName -> {dependencyGraph.put(beanName, new ArrayList<>());inDegree.put(beanName, 0);});// 构建依赖关系registry.getClassRegistry().forEach((beanName, clazz) -> {// 处理字段依赖for (Field field : clazz.getDeclaredFields()) {if (field.isAnnotationPresent(KatAutowired.class)) {String dependencyName = registry.resolveBeanName(field.getType());dependencyGraph.get(beanName).add(dependencyName);inDegree.put(dependencyName, inDegree.get(dependencyName) + 1);}}// 处理构造器依赖Constructor<?> autowiredCtor = findAutowiredConstructor(clazz);if (autowiredCtor != null) {for (Class<?> paramType : autowiredCtor.getParameterTypes()) {String dependencyName = registry.resolveBeanName(paramType);dependencyGraph.get(beanName).add(dependencyName);inDegree.put(dependencyName, inDegree.get(dependencyName) + 1);}}});
}
3)AOP支持
关于这一大块内容比较复杂,移步到一个大分支进行分析。
4)并发安全
需要注意的是,Tomcat服务器是实现了多线程的
通过synchronized
互斥锁和ConcurrentHashMap
(内部实现了synchronized
)
在map插入时使用putIfAbsent
而不是put
,因为putIfAbsent
是原子操作,更适合并发环境。
2. 类加载器隔离问题
Java类加载器是Java运行时环境(JRE)的重要组成部分,负责在运行时动态加载Java类到JVM内存中。可以通过继承java.lang.ClassLoader
类并重写findClass()
方法来实现自定义类加载器(这里不展开讲)。
类加载器的隔离原理:
每个类加载器实例都有独立的命名空间,同一个类被不同类加载器加载会被JVM视为不同的类。
Tomcat等Web容器为每个Web应用创建独立的WebAppClassLoader
:
Common ClassLoader├── WebApp1 ClassLoader└── WebApp2 ClassLoader
这也是Tomcat热部署的工作原理:创建新的类加载器加载修改后的类。
在我构建的DI容器中,类路径扫描器使用的是系统类加载器(AppClassLoader
),而Tomcat实现了自己的类加载器,导致了类加载隔离。
问题描述:
因为Tomcat没有被Bean容器管理,并且@WebServlet("/*")
使得Tomcat对类有自己的实现,与@KatComponent
产生了冲突。因此需要在controller层重写init
方法,手动注入依赖:
public void init() throws ServletException {super.init();// 从 ServletContext 获取 ContainerFactoryContainerFactory factory = (ContainerFactory) getServletContext().getAttribute("ContainerFactory");if (factory == null) {throw new ServletException("ContainerFactory 未初始化!");}try {factory.injectDependencies(this); // 手动注入依赖} catch (Exception e) {throw new RuntimeException(e);}
}
如果不更改类加载器,factory.injectDependencies(this)
这一步将会注入失败,因为controller层的movieService
是由Tomcat类加载器实例化的,而movieServiceImpl
则是Class.forName(className, false, Thread.currentThread().getContextClassLoader())
时由当前线程的上下文加载器决定的。
解决方法:
在启动服务器后
tomcat.start();
// 获取Tomcat类加载器
ClassLoader tomcatLoader = ctx.getLoader().getClassLoader();
// 设置类加载器
Thread.currentThread().setContextClassLoader(tomcatLoader);
ClassPathScanner.setClassLoader(tomcatLoader);
这里的关键是Thread.currentThread().setContextClassLoader(tomcatLoader)
。为什么?
在Java的类加载机制中,默认情况下,类的加载遵循双亲委派模型:
类加载器会先委托父类加载器加载,如果父类加载器找不到,才自己加载。而Tomcat的WebAppClassLoader
是AppClassLoader
的子加载器。
Bootstrap ClassLoader (JVM内置)↓
Extension ClassLoader (JVM内置)↓
System/App ClassLoader (JVM内置,加载CLASSPATH)↓
Common ClassLoader (Tomcat)├── Catalina ClassLoader (Tomcat容器专用)├── Shared ClassLoader (Web应用共享)└── WebApp ClassLoader (每个Web应用独立)
通过设置上下文类加载器,DriverManager
使用当前线程的ContextClassLoader
(即WebAppClassLoader
)来打破双亲委派机制的限制,让父加载器(AppClassLoader
)能访问子加载器(WebAppClassLoader
)加载的类。
3. 关于git提交的一些规范
基本格式
<type>(<scope>): <subject>
Footer
常用type:
feat
:新功能(feature)的添加。fix
:修复bug或问题。docs
:仅文档内容的更改(如 README、注释)。style
:代码格式的更改(不影响代码运行的变动,例如空格、制表符、等号等)。refactor
:代码重构(既不添加新功能也不修复bug的代码变动)。test
:添加或修改测试(包括单元测试、集成测试等)。chore
:构建过程或辅助工具的变动(例如更新依赖、修改构建脚本等)。gitignore
用这个。build
:影响项目构建或外部依赖项的更改(例如,更新了package.json
、pom.xml
或build.gradle
文件)。ci
:持续集成配置文件和脚本的更改(例如,更新.travis.yml
、.gitlab-ci.yml
等)。配置文件更改用这个。perf
:性能优化。revert
:撤销之前的提交。merge
:合并分支(通常由Git自动生成)。init
:项目的初始化提交。
scope:
可以是项目中任何可识别的部分,例如文件名、模块名、功能区域等。
影响范围比较大时用模块名,比较小时用类/影响
以下是一些常用的影响scope
值示例:
ui
:用户界面相关的更改。api
:API接口或后端服务的更改。db
:数据库相关的更改,如模式、迁移等。auth
:认证和授权相关的更改。config
:配置文件或设置的更改。deps
或dependencies
:依赖项的更新或更改。tests
:测试代码的更改。cli
:命令行界面相关的更改。docs
:文档内容的更改。build
:构建系统或脚本的更改。ci
:持续集成配置的更改。perf
:性能优化相关的更改。security
:安全性相关的更改。logging
:日志系统的更改。internationalization
或i18n
:国际化和本地化的更改。migration
:数据迁移或系统迁移相关的更改。cleanup
:代码清理,如删除无用代码或重构。refactor
:代码重构,不改变外部行为的内部结构调整。feature
:特定功能模块的更改。bugfix
:特定bug修复的更改。
Footer:
- 关联 Issue:如
Closes #123
或Fixes #456
。 - 破坏性变更:以
BREAKING CHANGE:
开头,描述不兼容的改动。
通常在长期维护的项目中使用。
4. 手搓AOP处理器
先来看看AOP的工作流程:
- 扫描注解
- 注册切面类/方法
- 解析,匹配切面表达式
- 动态代理实现类
1)扫描注解
这里可以集成到我的DI容器注册中。
定义切面类映射:
private final Set<Class<?>> aspectClasses = new HashSet<>();
通过路径扫描切面(与Component
的扫描类似):
private void scanAspects() {List<Class<?>> aspectList = basePackages.stream().flatMap(pkg -> ClassPathScanner.scanClassesWithAnnotation(pkg, KatAspect.class).stream()).toList();aspectList.forEach(aspect -> {aspectClasses.add(aspect);registerClass(aspect); // 切面也需要作为普通Bean注册});
}
2)注册切面类/方法
在BeanBuilder
中,执行依赖图处理后对AOP进行注册:
// 处理aop注册
private void processAspects() {registry.getAspectClasses().forEach(aspectClass -> {try {Object aspect = createAspectInstance(aspectClass);aspectInstances.put(aspectClass, aspect);aspectProcessor.registerAspect(aspect);} catch (Exception e) {throw new RuntimeException("Aspect initialization failed: " + aspectClass.getName(), e);}});
}
切面也需要依赖注入:
private Object createAspectInstance(Class<?> aspectClass) throws Exception {Object instance = createRawInstance(aspectClass);injectFields(instance);return instance;
}
在调用aspectProcessor.registerAspect(aspect)
方法时,将切面实例返回给AspectProcessor
类,注册到切面映射中:
// 缓存切点表达式与对应通知方法的映射
private final Map<String, List<AdviceWrapper>> aspectCache = new ConcurrentHashMap<>();
// 注册切面类
public void registerAspect(Object aspect) {Class<?> aspectClass = aspect.getClass();Arrays.stream(aspectClass.getMethods()).filter(m -> m.isAnnotationPresent(KatAround.class)).forEach(method -> registerAdvice(aspect, method));
}
// 注册单个通知方法
private void registerAdvice(Object aspect, Method adviceMethod) {KatAround around = adviceMethod.getAnnotation(KatAround.class);String pointcut = around.value();int priority = adviceMethod.isAnnotationPresent(KatOrder.class)? adviceMethod.getAnnotation(KatOrder.class).value() : 0;// 编译切点表达式compiledPatterns.computeIfAbsent(pointcut, this::compilePointcut);// 注册通知方法并按优先级排序aspectCache.computeIfAbsent(pointcut, k -> new CopyOnWriteArrayList<>()).add(new AdviceWrapper(aspect, adviceMethod, priority));aspectCache.get(pointcut).sort(Comparator.comparingInt(AdviceWrapper::priority));
}
AdviceWrapper
是一个通知包装类,包含了切面实例,通知方法,优先级等信息:
// 通知包装类
private record AdviceWrapper(Object aspectInstance, Method adviceMethod, int priority) {private AdviceWrapper(Object aspectInstance, Method adviceMethod, int priority) {this.aspectInstance = aspectInstance;this.adviceMethod = adviceMethod;this.priority = priority;this.adviceMethod.setAccessible(true);}public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {return adviceMethod.invoke(aspectInstance, joinPoint);}
}
关于切面表达式部分,则是实现了较为复杂的逐字符匹配,来避免直接替换导致的问题:
// 编译切点表达式为正则
private Pattern compilePointcut(String expression) {String regex = convertToRegex(expression);return Pattern.compile(regex);
}// 转换切点表达式为正则
private static String convertToRegex(String pointcut) {// 先处理特殊字符转义(但不处理通配符)StringBuilder regex = new StringBuilder();char[] chars = pointcut.toCharArray();for (int i = 0; i < chars.length; i++) {char c = chars[i];// 处理..通配符(需要看后续字符)if (c == '.' && i + 1 < chars.length && chars[i + 1] == '.') {regex.append("\\..+?"); // 非贪婪匹配i++; // 跳过下一个点continue;}// 处理普通字符转义switch (c) {case '.':regex.append("\\.");break;case '$':regex.append("\\$");break;case '*':// 临时标记,后面统一处理regex.append("\u0001");break;case '(':// 参数部分临时标记regex.append(handleParameters(chars, i));i = skipParameters(chars, i);break;default:regex.append(c);}}// 最后处理*通配符(避免被前面替换影响)String result = regex.toString().replace("\u0001", "[^.]+");return "^" + result + "$";
}// 处理参数部分
private static String handleParameters(char[] chars, int start) {int end = findParameterEnd(chars, start);String params = new String(chars, start, end - start + 1);return switch (params) {case "(..)" -> "\\(.*\\)";case "()" -> "\\(\\)";case "(*)" -> "\\([^,]+\\)";default -> Pattern.quote(params); // 其他参数形式原样匹配};
}
skipParameters
和findParameterEnd
用于处理嵌套括号。
转换规则:
切点语法 | 正则表达式 | 说明 |
---|---|---|
. | \\. | 转义包分隔符 |
.. | \\..+? | 匹配任意子包(非贪婪) |
* | [^.]+ | 匹配非点字符(不跨越包层级) |
(..) | \\(.*\\) | 匹配任意参数 |
() | \\(\\) | 匹配无参数方法 |
(*) | \\([^,]+\\) | 匹配单个任意类型参数 |
其他参数形式 | 原样保留 | 如 (String,int) |
示例转换过程:
输入切点:
com.example..service.*.*(..)
转换步骤:
..
→\\..+?
.
→\\.
*
→\u0001
(临时)(..)
→\\(.*\\)
- 替换
\u0001
→[^.]+
- 添加边界符
最终正则:
^com\.example\..+?service\.[^.]+\.[^.]+\(.*\)$
3)解析,匹配切面表达式
在BeanBuilder
方法中,在创建实例这一步加入对AOP代理的判断:
// 创建实例
private Object createInstance(Class<?> clazz) throws Exception {// 1. 创建原始实例(不区分单例/原型)Object rawInstance;Constructor<?> autowiredCtor = findAutowiredConstructor(clazz);if (autowiredCtor != null) {rawInstance = createInstanceWithConstructor(autowiredCtor);} else {rawInstance = clazz.getDeclaredConstructor().newInstance();}// 2. 注入依赖injectFields(rawInstance);// 3. 统一应用AOP代理(无论是否单例)return wrapWithAopIfNeeded(rawInstance, clazz);
}
// 判断是否要生成代理
private Object wrapWithAopIfNeeded(Object rawInstance, Class<?> targetClass) {try {if (shouldProxy(targetClass)) {return createProxy(rawInstance, targetClass);}return rawInstance;} catch (Exception e) {throw new RuntimeException("AOP proxy creation failed for " + targetClass.getName(), e);}
}
private boolean shouldProxy(Class<?> targetClass) {// 不是切面类 && 有匹配的切面逻辑return !targetClass.isAnnotationPresent(KatAspect.class) &&aspectProcessor.hasMatchingAdvice(targetClass);
}
转交给Aspectprocessor
类检查切面匹配:
// 检查类是否有匹配的切面
public boolean hasMatchingAdvice(Class<?> targetClass) {return aspectCache.keySet().stream().anyMatch(pointcut -> {// 构建类名模式:com.example.Service -> com.example.Service.*(..)String classPattern = targetClass.getName() + ".*(..)";return matchesPointcut(pointcut, classPattern);});
}// 检查方法是否有匹配的切面
public boolean hasMatchingAdvice(Method method) {String methodSignature = buildMethodSignature(method);return aspectCache.keySet().stream().anyMatch(pointcut -> matchesPointcut(pointcut, methodSignature));
}// 检查切点是否匹配签名
private boolean matchesPointcut(String pointcut, String signature) {Pattern pattern = compiledPatterns.get(pointcut);if (pattern == null) {pattern = compilePointcut(pointcut);compiledPatterns.put(pointcut, pattern);}return pattern.matcher(signature).matches();
}
切面处理成正则的处理逻辑已经在上面给出。
4)动态代理实现类
如果找到匹配的切面则返回BeanBuilder
类中进行代理对象的创建:
private Object createProxy(Object target, Class<?> targetClass) throws Exception {if (targetClass.isInterface()) { //JDK的代理只能处理接口对象return Proxy.newProxyInstance(targetClass.getClassLoader(),new Class<?>[]{targetClass},(proxy, method, args) -> aspectProcessor.applyAspects(target, method, args) //调用委托给AspectProcessor);} else { return new ByteBuddy().subclass(targetClass) // 创建子类.method(not(isDeclaredBy(Object.class))) // 排除Object原生方法.intercept(MethodDelegation.to(new AspectInterceptor(target, aspectProcessor))) // 委托给拦截器.make().load(targetClass.getClassLoader(), ClassLoadingStrategy.Default.INJECTION) // 使用INJECTION策略(新类注入到目标类的ClassLoader中).getLoaded().getDeclaredConstructor().newInstance();}
}
ByteBuddy是一个字节码增强管理库:
<!-- bytebuddy -->
<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.15.11</version>
</dependency>
<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>1.15.11</version>
</dependency>
AspectInterceptor方法拦截器
通过方法拦截器,将调用委托给AspectProcessor
:
public class AspectInterceptor {private final Object target;private final AspectProcessor aspectProcessor;public AspectInterceptor(Object target, AspectProcessor aspectProcessor) {this.target = target;this.aspectProcessor = aspectProcessor;}//在运行时,拦截目标方法,并执行切面逻辑@RuntimeTypepublic Object intercept(@Origin Method method,@AllArguments Object[] args,@SuperCall Callable<?> callable) throws Exception {try {return aspectProcessor.applyAspects(target, method, args);} catch (Throwable throwable) {if (throwable instanceof Exception) {throw (Exception) throwable;}throw new Exception(throwable);}}
}
回到AspectInterceptor
类,执行委托的切面逻辑:
// 执行切面逻辑
public Object applyAspects(Object target, Method method, Object[] args) throws Throwable {// 构建更精确的方法签名String methodSignature = target.getClass().getName() + "." + method.getName() +"(" + Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(",")) + ")";// 查找匹配的切面List<AdviceWrapper> matchedAdvices = aspectCache.entrySet().stream().filter(entry -> matchesPointcut(entry.getKey(), methodSignature)).flatMap(entry -> entry.getValue().stream()).sorted(Comparator.comparingInt(AdviceWrapper::priority)).toList();if (matchedAdvices.isEmpty()) {return method.invoke(target, args);}// 创建连接点ProceedingJoinPoint joinPoint = new ProceedingJoinPoint(target, method, args);// 执行通知链Object result = null;for (AdviceWrapper advice : matchedAdvices) {result = advice.invoke(joinPoint);}return result;
}
通知链执行流程:
典型场景示例
场景:执行 userService.save(user)
方法
-
签名生成
→"com.service.UserService.save(com.entity.User)"
-
切面匹配
- 匹配
@Around("com.service..*.save*(..)")
- 匹配
@Before("execution(* save(*))")
- 匹配
-
执行顺序
1. @Before 切面 2. @Around 切面(内部调用 proceed()) 3. 实际执行 save() 方法 4. @Around 剩余逻辑 5. @AfterReturning 切面
连接点joinPoint
,是连接通知和代理实例的桥梁:
public record ProceedingJoinPoint(Object target, Method method, Object[] args) {public Object proceed() throws Exception {return method.invoke(target, args);}
}
注意:每个切面都收到同一个joinPoint
对象,用于记录当前的进程状态。通过以下的示例,可以更好地理解通知链的执行流程:
5. 锁与并发安全
基本的线程锁:
-
互斥锁 (synchronized)
最基础的并发控制机制,确保同一时间只有一个线程能访问共享资源。private final Object lock = new Object(); public void safeMethod() {synchronized(lock) {// 临界区代码} }
-
读写锁 (ReadWriteLock)
允许多个线程同时读,只允许一个线程写,且写时不能读。ReadWriteLock rwLock = new ReentrantReadWriteLock(); public void readData() {rwLock.readLock().lock();try {// 读取操作} finally {rwLock.readLock().unlock();} } public void writeData() {rwLock.writeLock().lock();try {// 写入操作} finally {rwLock.writeLock().unlock();} }
基本的并发容器:
ConcurrentHashMap
CopyOnWriteArrayList
BlockingQueue
锁粒度:
指锁的作用范围大小,合理选择锁粒度能平衡线程安全与系统性能。
-
粗粒度锁
// 锁整个对象 public synchronized void process() {// 所有操作都在锁内 }// 锁整个集合 synchronized(map) {// 操作map的所有方法 }
-
细粒度锁
// 为每个数据项单独加锁 public class FineGrainedCache {private final Map<Key, Lock> keyLocks = new HashMap<>();private final Map<Key, Value> cache = new HashMap<>();public void update(Key key, Value value) {Lock keyLock;synchronized(this) {keyLock = keyLocks.computeIfAbsent(key, k -> new ReentrantLock());}keyLock.lock();try {// 只锁定当前key的操作cache.put(key, value);} finally {keyLock.unlock();}} }
应当使用细粒度锁的场景:
- 临界区包含I/O或耗时计算
- 高并发访问
- 资源可自然分片(如用户ID、订单ID等)
- 需要最大化吞吐量
常见优化模式:
-
锁分解:将一个大锁拆分为多个小锁
// 优化前 public class Account {private final Object lock = new Object();private long balance;private int transactionCount;public void transfer(Account to, long amount) {synchronized(lock) {this.balance -= amount;to.balance += amount;this.transactionCount++;}} }// 优化后 public class Account {private final Object balanceLock = new Object();private final Object countLock = new Object();private long balance;private int transactionCount;public void transfer(Account to, long amount) {synchronized(balanceLock) {this.balance -= amount;to.balance += amount;}synchronized(countLock) {this.transactionCount++;}} }
-
锁分段:
单个粗粒度锁拆分为多个细粒度锁,通过哈希算法将数据映射到对应的锁。只有操作同一分段的线程才会竞争同一把锁。public class StripedDictionary<K, V> {private final int NUM_LOCKS = 16; // 分段数private final Map<K, V> map = new HashMap<>();private final Object[] locks = new Object[NUM_LOCKS]; // 锁数组public StripedDictionary() {for (int i = 0; i < NUM_LOCKS; i++) {locks[i] = new Object(); // 初始化所有锁}}// 通过key的哈希决定使用哪个锁分段private int getLockIndex(K key) {return Math.abs(key.hashCode() % NUM_LOCKS);}public void put(K key, V value) {int lockIndex = getLockIndex(key);synchronized (locks[lockIndex]) { // 只锁定当前分段map.put(key, value);}}public V get(K key) {int lockIndex = getLockIndex(key);synchronized (locks[lockIndex]) { // 只锁定当前分段return map.get(key);}} }
-
读写锁分离,例如
ReadWriteLock
的设计模式。
注意:粒度越细的锁,越容易面临复杂编程下的死锁风险。
简单判断是否需要加锁:
避免死锁风险的技巧:
-
锁顺序固定化
-
尝试锁机制:尝试获取锁,若失败立即返回,而不会像
synchronized
等传统锁一样阻塞直到成功。 -
超时释放
-
死锁检测
注意:实际开发中,要养成对共享资源的访问必须加锁的意识。
结语
大概先做这么多内容,如果还有更多内容,我会放在第二天的blog里。
相关文章:
学习记录:DAY22
我的重生开发之旅:优化DI容器,git提交规范,AOP处理器,锁与并发安全 前言 我重生了,重生到了五一开始的一天。上一世,我天天摆烂,最后惨遭实习生优化。这一世,我要好好内卷… 今天的…...
HarmonyOS NEXT第一课——HarmonyOS介绍
一、什么是HarmonyOS 万物互联时代应用开发的机遇、挑战和趋势 随着万物互联时代的开启,应用的设备底座将从几十亿手机扩展到数百亿IoT设备。全新的全场景设备体验,正深入改变消费者的使用习惯。 同时应用开发者也面临设备底座从手机单设备到全场景多设…...
数据库系统概论|第五章:数据库完整性—课程笔记1
前言 在前文介绍完数据库标准语言SQL之后,大家已经基本上掌握了关于数据库编程的基本操作,那我们今天将顺承介绍关于数据库完整性的介绍,数据库的完整性是指数据的正确性和相容性。数据的完整性是为了防止数据库中存在不符合语义的数据&…...
开源无人机地面站QGroundControl安卓界面美化与逻辑优化实战
QGroundControl作为开源无人机地面站软件,其安卓客户端界面美化与逻辑优化是提升用户体验的重要工程。 通过Qt框架的界面重构和代码逻辑优化,可以实现视觉升级与性能提升的双重目标。本文将系统讲解QGC安卓客户端的二次开发全流程,包括开发环境搭建、界面视觉升级、多分辨率…...
工作记录 2017-12-12 + 在IIS下发布wordpress
工作记录 2017-12-12 序号 工作 相关人员 1 修改邮件上的问题。 更新RD服务器。 在IIS下发布wordpress。 郝 服务器更新 RD服务器更新了,更新的文件放在190的D:\Temp\CHTeam\fnehr_update_20171212\下了。 数据库更新: 数据库没有更新 更新的文件…...
BBR 之 ProbeRTT 新改
早在 1981 年,Jaffe 在 Flow Control Power is Nondecentralizable 中就给出过论证,测量 maxbw 必然引入队列,而获得 minrtt 时带宽必然欠载,这确定了后面 30 年的拥塞控制算法基调,但 BBR 在 35 年后非常聪明地在两者…...
[创业之路-354]:农业文明到智能纪元:四次工业革命下的人类迁徙与价值重构
农业文明到智能纪元:四次工业革命下的人类迁徙与价值重构 从游牧到定居,从蒸汽轰鸣到算法洪流,人类文明的每一次跨越都伴随着生产关系的剧烈震荡。四次工业革命的浪潮不仅重塑了物质世界的生产方式,更将人类推向了身份认同与存在…...
敏感词 v0.25.0 新特性之 wordCheck 策略支持用户自定义
开源项目 敏感词核心 https://github.com/houbb/sensitive-word 敏感词控台 https://github.com/houbb/sensitive-word-admin 版本特性 大家好,我是老马。 敏感词一开始了内置了多种检验策略,但是很多用户在使用的过程中希望可以自定义策略。 所以 v0…...
从0到上线,CodeBuddy 如何帮我快速构建旅游 App?
引言 腾讯云AI代码助手之前就改成了CodeBuddy我相信这也是在为后期做准备。那么这篇文章会对CodeBuddy进行比较详细的介绍,并一起来上手实战,感受一下实际开发中这款插件能带给我们多少的便利。本篇文章是一边写一边进行测试,并不是测试完之…...
微信小程序 自定义组件 标签管理
环境 小程序环境: 微信开发者工具:RC 1.06.2503281 win32-x64 基础运行库:3.8.1 概述 基础功能 标签增删改查:支持添加/删除单个标签、批量删除、重置默认标签 数据展示:通过对话框展示结构化数据并支持复制 动…...
从 Eclipse Papyrus / XText 转向.NET —— SCADE MBD技术的演化
从KPN[1]的萌芽开始,到SCADE的推出[2],再到Scade 6的技术更迭[3],SCADE 基于模型的开发技术已经历许多。现在,Scade One 已开启全新的探索 —— 从 Eclipse Papyrus / XText 转向.NET 8跨平台应用。 [1]: KPN, Kahn进程网络 (197…...
【学习笔记】机器学习(Machine Learning) | 第五章(2)| 分类与逻辑回归
机器学习(Machine Learning) 简要声明 基于吴恩达教授(Andrew Ng)课程视频 BiliBili课程资源 文章目录 机器学习(Machine Learning)简要声明 二、决策边界决策边界的数学表达线性决策边界示例非线性决策边界非线性决策边界的示例…...
python 常用web开发框架及使用示例
Python常用Web开发框架及使用示例 Python拥有丰富的Web开发框架生态系统,以下是主流框架及其使用示例: 一、Flask - 轻量级框架 安装 pip install flask 基础示例 from flask import Flask, request, jsonifyapp Flask(__name__)app.route(/) def…...
[ Qt ] | 第一个Qt程序
1. 创建Qt项目 我们打开Qt Create工具,左上角“文件”,新建文件。 --- --- --- --- 这个是我们的APP“走出国门”的时候,要关注的,这里就不说了。 后面这两个直接默认,下一步就行~~。 2. 项目默认内容 下面就是Qt C…...
react + antd 实现后台管理系统
文章目录 完整路由搭建Layout 和 Aside组件引入 AntdAside组件实现 项目效果图 项目完整代码地址 https://gitee.com/lyh1999/react-back-management 项目完整代码地址 react依赖安装 最好采用yarn 安装 react-router 安装依赖 配置路由 history模式 / // src/router/…...
vue3+ts项目 配置vue-router
安装vue-router pnpm install vue-router配置 1.src/router/index.ts文件下的内容 import type { App } from vue import type { RouteRecordRaw } from vue-router import { createRouter, createWebHistory } from vue-router import remainingRouter from ./modules/remai…...
MySQL基本查询(二)
文章目录 UpdateDelete插入查询结果(select insert)聚合函数分组聚合统计 Update 1. 语法: set后面加列属性或者表达式 UPDATE table_name SET column expr [, column expr …][WHERE …] [ORDER BY …] [LIMIT …] 案例 将孙悟空同学的…...
MySQL:联合查询
目录 一、笛卡尔积 二、内连接 三、外连接 (1)左外连接 (2)右外连接 (3)全外连接 四、自连接 五、子查询 (1)单行子查询 (2)多行子查询 &…...
[算法学习]——通过RMQ与dfs序实现O(1)求LCA(含封装板子)
每周五篇博客:(3/5) 碎碎念 其实不是我想多水一篇博客,本来这篇是欧拉序的博客,结果dfs序也是可以O1求lca的,而且常数更优,结果就变成这样了。。。 前置知识 [算法学习]——dfs序 思想 分…...
复刻低成本机械臂 SO-ARM100 舵机配置篇(WSL)
视频讲解: 复刻低成本机械臂 SO-ARM100 舵机配置篇(WSL) 飞特舵机 组装之前需要配置舵机的ID,如下的网址为舵机的资料,实际上用不到,但可以mark在这里 Software-深圳飞特模型有限公司 User Guide里面可以…...
聊一聊接口测试更侧重于哪方面的验证
目录 一、功能性验证 输入与输出正确性 参数校验 业务逻辑覆盖 二、数据一致性验证 数据格式规范 数据完整性 数据类型与范围 三、异常场景验证 容错能力测试 边界条件覆盖 错误码与信息清晰度 四、安全与权限验证 身份认证 数据安全 防攻击能力 五、性能与可…...
【网络安全实验】SSL协议的应用
目录 一、SSL协议介绍 2.功能与特点 1)数据加密 2)身份验证 3)数据完整性校验 3.SSL的工作流程(握手过程) 1)客户端问候(ClientHello) 2)服务器响应(…...
测试——用例篇
目录 1. 测试用例 1.1 概念 2. 设计测试用例的万能公式 2.1 常规思考逆向思维发散性思维 2.2 万能公式 3. 设计测试用例例的方法 3.1 基于需求的设计方法 编辑 3.2 具体的设计方法 3.2.1 等价类 3.2.2 边界值 3.2.3 正交法 3.2.4 判定表法 3.2.5 场景法 3.2.6…...
计算机视觉技术的发展历程
计算机视觉技术的发展历程可以分为以下几个阶段: 早期探索阶段(1960s-1980s) 1960年代:计算机视觉的概念开始形成,研究者尝试让计算机识别和理解图像,主要集中在基础的图像处理,如边缘检测和特…...
docker 官方:在 alpine 上安装 python 的方法
在 alpine 上安装 python 的方法在 alpine 上安装 python 的方法: # alpine 官方 apk add python3 # docker 官方 docker pull python:3.11-alpine # 第三方 docker run --rm frolvlad/alpine-python3 python3 -c print("Hello World") # 编译安装 略 要点…...
mescroll.js 是在 H5端 运行的下拉刷新和上拉加载插件
1. mescroll的uni版本, 是专门用在uni-app的下拉刷新和上拉加载的组件, 支持一套代码编译到iOS、Android、H5、小程序等多个平台 2. mescroll的uni版本, 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 .. 3. mescroll的uni版本, 丰富的…...
openEuler 22.03 安装 Mysql 5.7,RPM 在线安装
目录 一、检查系统是否安装其他版本Mariadb数据库二、安装 MySQL三、配置 MySQL四、修改默认存储路径五、开放防火墙端口六、数据备份七、生产环境优化八、常用命令 一、检查系统是否安装其他版本Mariadb数据库 # 查看已安装的 Mariadb 数据库版本 [rootopeneuler ~]# rpm -qa…...
云原生后端架构的挑战与应对策略
📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 随着云计算、容器化以及微服务等技术的快速发展,云原生架构已经成为现代软件开发和运维的主流趋势。企业通过构建云原生后端系统,能够实现灵活的资源管理、快速的应用迭代和高效的系统扩展。然而,尽管云原…...
第十六届蓝桥杯 2025 C/C++组 客流量上限
目录 题目: 题目描述: 题目链接: 思路: 打表找规律: 核心思路: 思路详解: 得到答案的方式: 按计算器: 暴力求解代码: 快速幂代码: 位运…...
LeetCode算法题 (移除链表元素)Day15!!!C/C++
https://leetcode.cn/problems/remove-linked-list-elements/description/ 一、题目分析 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 今天的题目非常好理解,也就是要删除…...
stm32 HAI库 SPI(一)原理
基本特点 通信方式:同步、串行(串行、并行、并发,别再傻傻分不清了!_串行和并行的区别-CSDN博客)、全双工 (也可以选择半双工)速率:50MHZ以下数据格式:8位/16位传输顺序…...
仿腾讯会议——主界面设计创建房间加入房间客户端实现
1、实现腾讯会议主界面 2、添加Qt类WeChatDialog 3、定义创建会议和加入会议的函数 4、实现显示名字、头像的函数 调用函数 5、在中间者类中绑定函数 6、实现创建房间的槽函数 7、实现加入房间的槽函数 8、设置界面标题 9、服务器定义创建和进入房间函数 10、服务器实现创建房间…...
在pycharm profession 2020.3上安装使用xlwings
之前写了一篇文章在win7和python3.8上安装xlwings-CSDN博客 今天安装了pycharm profession 2020.3,自带Terminal,所以试一下安装xlwings。 一、新建一个python项目 二、安装xlwings 三、输入安装命令 pip3.exe install -i https://pypi.tuna.tsinghu…...
Mybatis学习笔记
介绍 MyBatis 是一款优秀的持久层开发框架,它在 Java 开发中被广泛应用,以下是对它的详细介绍: 概述 MyBatis 最初是 Apache 的一个开源项目 iBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code&#…...
「Mac畅玩AIGC与多模态13」开发篇09 - 基于多插件协同开发智能体应用(天气+名言查询助手)
一、概述 本篇介绍如何在 macOS 环境下,同时接入多个自定义 OpenAPI 插件,实现智能体根据用户请求自动分析,调用天气查询或名言查询服务,完成多功能协同应用开发。 二、环境准备 1. 确认本地开发环境 macOS 系统Dify 平台已部署并可访问可正常访问外部 API 服务2. 准备天…...
C++--入门基础
C入门基础 1. C的第一个程序 C继承C语言许多大多数的语法,所以以C语言实现的hello world也可以运行,C中需要把文件定义为.cpp,vs编译器看是.cpp就会调用C编译器编译,linux下要用g编译,不再是gcc。 // test.cpp #inc…...
Ubuntu环境下如何管理系统中的用户:创建用户、删除用户、修改密码、切换用户、用户组管理
管理用户的操作需要root权限,在执行命令时需要加sudo,关于sudo命令可以看这篇:Linux_sudo命令的使用与机制 1、添加用户 使用命令: adduser 用户名,主要是按提示输入密码和用户信息(可直接回车使用默认配置…...
广告事件聚合系统设计
需求背景 广告事件需要进行统计,计费,分析等。所以我们需要由数据接入,数据处理,数据存储,数据查询等多个服务模块去支持我们的广告系统 规模上 10000 0000个点击(10000 00000 / 100k 1wQPS) …...
PDF智能解析与知识挖掘:基于pdfminer.six的全栈实现
前言 在数字化信息爆炸的时代,PDF(便携式文档格式)作为一种通用的电子文档标准,承载着海量的结构化与非结构化知识。然而,PDF格式的设计初衷是用于展示而非数据提取,这使得从PDF中挖掘有价值的信息成为数据…...
VGG网络模型
VGG网络模型 诞生背景 VGGNet是牛津大学计算机视觉组核谷歌DeepMind一起研究出来的深度卷积神经网络。VGG是一种被广泛使用的卷积神经网络结构,其在2014年的ImageNet大规模视觉识别挑战中获得亚军。 通常所说的VGG是指VGG-16(13层卷积层3层全连接层)。具有规律的…...
开闭原则与依赖倒置原则区别:原类不变,新增类(功能)vs 接口类不变,原实现类可变
好,我来用最通俗的方式,用角色扮演 场景对话,不讲术语,让你彻底明白「依赖倒置原则」和「开闭原则」的区别。 🎭 场景:你是老板(高层),你要雇人做事 一、【依赖倒置原则…...
【AI面试准备】Azure DevOps沙箱实验全流程详解
介绍动手实验:通过 Azure DevOps 沙箱环境实操,体验从代码提交到测试筛选的全流程。如何快速掌握,以及在实际工作中如何运用。 通过 Azure DevOps 沙箱环境进行动手实验,是快速掌握 DevOps 全流程(从代码提交到测试筛选…...
大数据面试问答-数据湖
1. 概念 数据湖(Data Lake): 以原始格式(如Parquet、JSON等)存储海量原始数据的存储库,支持结构化、半结构化和非结构化数据(如文本、图像)。采用Schema-on-Read模式,数…...
驱动开发系列56 - Linux Graphics QXL显卡驱动代码分析(三)显示模式设置
一:概述 如之前介绍,在qxl_pci_probe 中会调用 qxl_modeset_init 来初始化屏幕分辨率和刷新率,本文详细看下 qxl_modeset_init 的实现过程。即QXL设备的显示模式设置,是如何配置CRTC,Encoder,Connector 的以及创建和更新帧缓冲区的。 二:qxl_modeset_init 分析 in…...
沥青路面裂缝的目标检测与图像分类任务
文章题目是《A grid‐based classification and box‐based detection fusion model for asphalt pavement crack》 于2023年发表在《Computer‐Aided Civil and Infrastructure Engineering》 论文采用了一种基于网格分类和基于框的检测(GCBD)ÿ…...
单片机-STM32部分:0、学习资料汇总
飞书文档https://x509p6c8to.feishu.cn/wiki/Kv7VwjDD8idFWKkMj4acZA3lneZ 一、软件部分 STM32F1系列资料官网下载地址 https://www.stmcu.com.cn/Designresource/list/STM32F1/document/document STM32官方数据手册 有哪些版本,哪些资源,对应哪些IO…...
杭电oj(1180、1181)题解
目录 1180 题目 思路 问题概述 代码思路分析 1. 数据结构与全局变量 2. BFS 函数 bfs 3. 主函数 main 总结 代码 1181 题目 思路 1. 全局变量的定义 2. 深度优先搜索函数 dfs 3. 主函数 main 总结 代码 1180 题目 思路 注:当走的方向和楼梯方向一…...
内部类(3):匿名内部类
1 匿名类 请看下面这个例子: public class Parcel7 {public Contents contents() {return new Contents() {private int i 11;public int value() {return i;}};}public static void main(String[] args) {Parcel7 p new Parcel7();Contents c p.contents();} }…...
组件通信-$attrs
概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(爷→孙)。 具体说明:$attrs是一个对象,包含所有父组件传入的标签属性。 注意:$attrs会自动排除props中声明的属性(可以认为声明过…...
Laravel Octane 项目加速与静态资源优化指南
Laravel Octane 项目加速与静态资源优化指南 一、Octane 核心加速配置 扩展安装与环境配置 composer require laravel/octane # 安装核心扩展php artisan octane:install # 生成配置文件(选择 Swoole/RoadRunner 等服务器)服务器参数调优 …...