首页
壁纸
统计
友链
Search
1
Vue2详细笔记
37 阅读
2
Nestjs概述-中文
19 阅读
3
ExpressAPI
17 阅读
4
答辩
14 阅读
5
JavaScript企业数据处理实用指南
13 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
36
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
36
篇与
的结果
2026-06-02
未命名文档
注意:Java 8 环境下,javax.annotation(@PostConstruct、@PreDestroy、@Resource)是 JDK 自带的,无需额外添加依赖。Spring Boot 2.7 使用 Spring Framework 5.x,完美支持 JSR-250 规范。一、Bean 的作用域(Scope)Spring 中的 Bean 默认是 单例(Singleton),即整个容器内只有一个实例。也可以通过 @Scope 指定为 原型(Prototype),每次获取都创建新实例。1.1 Singleton(单例)—— 默认创建时机:容器启动时(除非设置为 @Lazy)。销毁时机:容器正常关闭时,会调用 @PreDestroy 方法。获取结果:每次注入或 getBean() 返回同一个对象。@Component // 默认 singleton public class MailService { // ... }1.2 Prototype(原型)行为:每次获取(@Autowired 或 getBean)都会创建一个全新的实例。生命周期差异:容器负责创建 → 依赖注入 → 调用 @PostConstruct。容器不负责销毁:即使你在原型 Bean 中写了 @PreDestroy,Spring 不会自动调用它。需要你自己写代码清理(例如手动调用销毁方法)。声明方式:@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 或 @Scope("prototype") public class MailSession { @PostConstruct public void init() { System.out.println("原型 Bean 初始化,每次都会调用"); } @PreDestroy public void destroy() { System.out.println("原型 Bean 销毁 — 不会被 Spring 自动调用"); } }最佳实践:如果没有特殊需求,优先使用 singleton。prototype 适合那些有状态且不宜共享的对象,但要记得手动管理资源释放。二、注入 List —— 自动收集同一类型的所有 Bean当你需要在一个地方使用某个接口的 所有实现类 时,可以直接注入 List<T>,Spring 会自动把该接口的所有 Bean 放进列表。示例:多个 Validator 实现public interface Validator { void validate(String email, String password, String name); } @Component // 自动被收集 public class EmailValidator implements Validator { @Override public void validate(String email, String password, String name) { // email 校验逻辑 } } @Component public class PasswordValidator implements Validator { @Override public void validate(String email, String password, String name) { // password 校验逻辑 } } @Component public class NameValidator implements Validator { @Override public void validate(String email, String password, String name) { // name 校验逻辑 } }注入 List@Component public class Validators { @Autowired private List<Validator> validators; // Spring 自动注入所有 Validator 实现 public void validate(String email, String password, String name) { for (Validator v : validators) { v.validate(email, password, name); } } }控制顺序 —— @Order默认顺序不确定,可以用 @Order 注解指定(数字越小优先级越高)。@Component @Order(1) public class EmailValidator implements Validator { ... } @Component @Order(2) public class PasswordValidator implements Validator { ... } @Component @Order(3) public class NameValidator implements Validator { ... }注入的 List<Validator> 会按照 @Order 的值升序排列。三、可选注入 —— @Autowired(required = false)默认情况下,如果某个依赖的 Bean 不存在,Spring 会抛出 NoSuchBeanDefinitionException。如果你允许该依赖为 null,可以设置 required = false。@Component public class MailService { @Autowired(required = false) private ZoneId zoneId = ZoneId.systemDefault(); // 找不到 ZoneId 时使用默认值 }典型场景:某些配置是可选的(如时区、外部服务),若有定义则使用,否则退回到默认值。四、创建第三方 Bean —— @Bean 方法对于不在我们包内的类(例如 JDK 的 ZoneId、第三方库的 DataSource),无法用 @Component 扫描。此时需要在 @Configuration 类中编写一个 工厂方法,并标记 @Bean。@Configuration @ComponentScan public class AppConfig { @Bean public ZoneId zoneId() { return ZoneId.of("Z"); } @Bean("customRestTemplate") // 指定 Bean 名称 public RestTemplate myRestTemplate() { return new RestTemplate(); } }Spring 保证该方法 只被调用一次(默认单例)。方法名默认作为 Bean 的名称,也可用 @Bean("name") 显式命名。可以依赖其他 Bean:方法参数会自动注入。@Bean public DataSource dataSource() { return HikariDataSource(...); } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { // 自动注入上面创建的 DataSource return new JdbcTemplate(dataSource); }本质上,@Bean 方法就是一种 工厂方法,比实现 FactoryBean 接口更简洁。五、Bean 的初始化和销毁 —— @PostConstruct 和 @PreDestroy5.1 为什么需要初始化:在依赖注入完成后,执行一些启动逻辑(打开连接、启动后台线程、校验配置)。销毁:在容器关闭前,释放资源(关闭连接池、停止线程、清理临时文件)。5.2 使用方式(JSR-250,Java 8 原生支持)@Component public class MailService { @Autowired(required = false) private ZoneId zoneId = ZoneId.systemDefault(); @PostConstruct public void init() { System.out.println("初始化:依赖已注入,zoneId = " + zoneId); } @PreDestroy public void shutdown() { System.out.println("销毁前:释放资源(如关闭连接池)"); } }5.3 执行顺序初始化流程:调用构造方法创建实例依赖注入(@Autowired、@Resource 等)调用 @PostConstruct 方法(如果实现了 InitializingBean)调用 afterPropertiesSet()(如果配置了 init-method)调用自定义方法销毁流程(容器正常关闭时):调用 @PreDestroy 方法(如果实现了 DisposableBean)调用 destroy()(如果配置了 destroy-method)调用自定义方法5.4 重要注意事项方法必须 无参数,返回值任意(通常 void)。方法名任意,不要求 init/destroy。prototype 作用域的 Bean,Spring 只调用 @PostConstruct,不会自动调用 @PreDestroy。如果原型 Bean 需要清理,你必须自己设计清理逻辑(例如提供 cleanup() 方法并手动调用)。5.5 “全自动”的条件在 Spring Boot 2.7(Java 8)中,只要满足以下条件,@PostConstruct 和 @PreDestroy 就是 全自动 的:Bean 是 singleton 作用域(默认)。Spring Boot 应用正常关闭(按 Ctrl+C、收到 SIGTERM、程序正常结束)。Spring Boot 会自动注册 JVM 关闭钩子,触发容器关闭,从而调用 @PreDestroy。不要使用 kill -9 强行杀死进程(那样 JVM 直接退出,不会执行关闭钩子)。@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); // 按 Ctrl+C 时,会看到 @PreDestroy 的输出 } }六、处理多个同类型 Bean —— 别名、限定符、主次6.1 问题描述在 @Configuration 中定义多个同类型的 @Bean:@Configuration public class AppConfig { @Bean ZoneId createZoneOfZ() { return ZoneId.of("Z"); } @Bean ZoneId createZoneOfUTC8() { return ZoneId.of("UTC+08:00"); } }直接注入 ZoneId 会报错:NoUniqueBeanDefinitionException。6.2 解决方案(1) 为每个 Bean 起一个名字(别名)@Bean("z") ZoneId createZoneOfZ() { ... } @Bean @Qualifier("utc8") ZoneId createZoneOfUTC8() { ... }(2) 注入时用 @Qualifier 指定名称@Component public class MailService { @Autowired @Qualifier("z") // 精确指定名称为 "z" 的那个 ZoneId private ZoneId zoneId; }或者使用 JSR-250 的 @Resource(name = "z"):@Component public class MailService { @Resource(name = "z") private ZoneId zoneId; }(3) 使用 @Primary 指定主要 Bean当没有使用 @Qualifier 时,Spring 会注入标记 @Primary 的那个 Bean。@Bean @Primary // 主要候选 @Qualifier("z") ZoneId createZoneOfZ() { ... } @Bean @Qualifier("utc8") ZoneId createZoneOfUTC8() { ... }注入时:@Component public class MailService { @Autowired // 无 @Qualifier,默认注入 @Primary 的那个(即 "z") private ZoneId zoneId; }6.3 经典应用:多数据源@Configuration public class DataSourceConfig { @Bean @Primary DataSource masterDataSource() { ... } @Bean @Qualifier("slave") DataSource slaveDataSource() { ... } }默认注入主数据源(如 DAO 层无需特殊指定)。需要从库时:@Qualifier("slave") 精确注入。七、FactoryBean —— 工厂模式创建 Bean(了解即可)7.1 是什么FactoryBean 是 Spring 提供的工厂接口,允许你自定义 Bean 的创建逻辑。Spring 会先创建 FactoryBean 实例,再调用其 getObject() 方法返回真正的 Bean。@Component public class ZoneIdFactoryBean implements FactoryBean<ZoneId> { private String zone = "Z"; @Override public ZoneId getObject() throws Exception { return ZoneId.of(zone); } @Override public Class<?> getObjectType() { return ZoneId.class; } }注入/获取时拿到的是 getObject() 返回的 ZoneId。如果需要获取 FactoryBean 本身,可以在名称前加 &:context.getBean("&zoneIdFactoryBean")。7.2 现状在 Spring Boot 2.7 中,@Bean 方法已经足够简洁强大,完全可以替代 FactoryBean 的大部分场景。除非你要编写非常复杂的创建逻辑,或需要与旧框架集成(如 MyBatis 的 MapperFactoryBean),否则推荐直接使用 @Bean 方法。八、综合对比与最佳实践功能推荐方案关键点普通 Bean@Component默认 singleton原型 Bean@Scope("prototype")每次新建,容器不负责 @PreDestroy收集所有实现@Autowired List<Interface>配合 @Order 控制顺序可选依赖@Autowired(required=false)避免异常,可设默认值第三方 Bean@Bean 方法在 @Configuration 中定义初始化逻辑@PostConstruct依赖注入后自动执行销毁逻辑@PreDestroy容器关闭时自动执行(singleton 有效)同类型多个 Bean@Qualifier + 命名精确注入;@Primary 设置默认复杂工厂@Bean 方法一般不用 FactoryBean九、Java 8 + Spring Boot 2.7 特别提醒注解包:@PostConstruct、@PreDestroy、@Resource 使用 javax.annotation,无需添加任何额外依赖。Spring Boot 自动关闭:Spring Boot 应用默认注册 JVM 关闭钩子,只要你正常停止应用(Ctrl+C、kill 不带 -9、System.exit()),@PreDestroy 就会被调用。prototype 的陷阱:如果你的 Bean 是 prototype 且需要释放资源,请考虑改用 singleton,或自行设计清理方法(如实现 DisposableBean 并手动调用)。@Bean 方法的默认名称:方法名即 Bean 名称,注意避免重名(同一类型不同方法名)。
2026年06月02日
3 阅读
0 评论
0 点赞
2026-06-02
Java随心记
一、常用工具与集合操作1.1 Arrays.asList()作用:将数组转换为 List。注意:返回的 List 是固定大小的,不支持 add/remove 操作。List<String> list = Arrays.asList("a", "b", "c");1.2 CollUtil.toMap()(Hutool 工具)把一个 List 转换成 Map。参数1:要转换的列表。参数2:key 的生成方式(如 Stock::getSkuId)。参数3:value 的生成方式,Function.identity() 表示用对象本身作为 value。Map<Long, Stock> map = CollUtil.toMap(stocks, Stock::getSkuId, Function.identity());1.3 IdentityHashMap比较冷门,判断 key 是否相同依据 对象的引用地址(==),而不是 equals() / hashCode()。二、数据查询策略2.1 联表查询 vs 分开查 + Map 组装方式适用场景联表查询数据量不大、关联简单、不需要对查出的用户数据做额外处理分开查 + Map 组装数据分布在不同的服务器、微服务架构,或者需要灵活组装三、Java 集合框架3.1 集合体系概览Collection 是除 Map 外所有集合类的根接口。java.util 包主要提供三种类型的集合:List、Set、Map。统一使用 Iterator 遍历集合。3.2 ListArrayList:基于动态数组,内存连续。LinkedList:基于双向链表,节点有前后指针。List 允许添加 null。使用 List.of() 创建不可变 List(不支持 null 元素)。List<Integer> list = List.of(1, 2, 5);Iterator 遍历:boolean hasNext() 判断是否有下一个元素。E next() 返回下一个元素。boolean contains(Object o) 判断是否包含某元素。int indexOf(Object o) 返回元素索引,不存在返回 -1。重要:使用 contains()、indexOf() 等方法时,元素类必须正确覆写 equals()。String、Integer 等标准库类已经正确实现了 equals(),所以可以直接使用。3.3 编写 equals() 方法理论原则:自反性、对称性、传递性、一致性。实践写法(直接套用):先确定实例相等的逻辑(哪些字段相等就算相等)。用 instanceof 判断类型,类型匹配后再比较字段。使用 Objects.equals() 比较引用类型字段,避免空指针。public boolean equals(Object o) { if (o instanceof Person p) { return Objects.equals(this.name, p.name) && this.age == p.age; } return false; }3.4 MapMap 遍历是无序的。底层是哈希表,通过哈希函数将 key 转换成数组下标,直接定位,查找速度极快 O(1)。作为 key 的对象必须正确覆写 equals() 和 hashCode(),相等的 key 调用 equals 必须返回 true。3.5 Queue 队列实现先进先出(FIFO)的数据结构。常用方法:add() / offer():添加元素到队尾。remove() / poll():从队首获取并删除元素。element() / peek():从队首获取但不删除。避免将 null 添加到队列。四、编码与安全4.1 URL 编码使用 java.net.URLEncoder 对任意字符串进行 URL 编码。4.2 哈希算法目的:验证原始数据是否被篡改。MessageDigest 常见用途:计算文件校验和实现文件秒传存储用户密码防止数据重复生成 API 签名4.3 AES 加密最可靠的 AES 加密模式:AES-GCM。五、多线程与并发5.1 线程基础守护线程(Daemon)JVM 退出时不等待守护线程结束,直接强制终止。非守护线程(用户线程)结束时 JVM 才会退出。设置方法:在 start() 前调用 setDaemon(true)。核心业务线程不能设置为守护线程。典型用途:垃圾回收、日志监控、心跳检测。t.join()让当前线程等待 t 线程执行结束。生产环境禁用,容易造成阻塞,推荐用 Future 等异步方式。Thread.sleep(long millis)静态方法,哪个线程调用,哪个线程睡觉。线程中断线程结束即 run() 方法执行完毕。目标线程通过检查 isInterrupted() 标志判断自身是否已被中断。volatile 与内存可见性变量的值保存在主内存,线程访问时会拷贝到工作内存。修改后的值何时回写主内存不确定,因此会出现可见性问题。synchronized 不能修饰变量,要保证变量跨线程可见性,使用 volatile。注意:volatile 只能保证可见性和禁止指令重排,不保证原子性。5.2 同步机制synchronizedJava 语言层面语法,不需要手动释放锁(退出同步块自动释放)。修饰方法时,锁对象是 this(实例方法)或类对象(静态方法)。配合 wait() / notifyAll() 实现线程等待/唤醒(了解即可,实际多用 Lock 体系)。ReentrantLockJava 代码实现的锁,必须在 finally 中手动释放。标准用法:private final Lock lock = new ReentrantLock(); lock.lock(); try { // 同步代码 } finally { lock.unlock(); }配合 Condition 的 await() / signalAll() 实现等待/通知。ReadWriteLock提升读多写少场景的并发性能。规则:读-读 可共存写-写 互斥读-写 互斥(要么全是读,要么只有一个写)实现:private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); private final Lock rlock = rwlock.readLock(); private final Lock wlock = rwlock.writeLock(); // 写操作 wlock.lock(); try { ... } finally { wlock.unlock(); } // 读操作 rlock.lock(); try { ... } finally { rlock.unlock(); }注意:读锁是悲观锁,频繁读可能导致写线程饥饿。StampedLock三种模式:写锁、悲观读锁、乐观读。乐观读 tryOptimisticRead() 完全不阻塞写线程,先获取版本号再读数据,读完后验证版本号有无变化,变化则重试或升级为悲观读。不可重入(一个线程持有写锁再尝试获取读锁会死锁)。Semaphore信号量,限制同时访问某个资源的线程数量。用法:final Semaphore semaphore = new Semaphore(3); // 最多3个许可 public String access() throws Exception { semaphore.acquire(); // 获取许可,可能阻塞 try { // 业务逻辑 } finally { semaphore.release(); // 释放许可 } }tryAcquire(long timeout, TimeUnit unit) 可指定等待时间。5.3 线程安全集合接口非线程安全线程安全替代ListArrayListCopyOnWriteArrayListMapHashMapConcurrentHashMapSetHashSet / TreeSetCopyOnWriteArraySetQueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueueDequeArrayDeque / LinkedListLinkedBlockingDeque5.4 线程池ExecutorService经典用法:ExecutorService executor = Executors.newFixedThreadPool(3); executor.submit(task1); ... executor.shutdown(); // 等待任务完成后关闭常用线程池及底层原理:FixedThreadPool:核心线程数 = 最大线程数 + 无界队列CachedThreadPool:核心线程数 0,最大线程数无限 + 同步移交队列SingleThreadExecutor:核心和最大线程数均为 1 + 无界队列ScheduledThreadPool:ScheduledThreadPoolExecutor,支持定时调度生产推荐:直接使用 ThreadPoolExecutor 定制参数,队列必须有界。public ThreadPoolExecutor( int corePoolSize, // CPU密集:n+1 IO密集:2n int maximumPoolSize, // ≥ corePoolSize long keepAliveTime, // 非核心线程空闲存活时间,常用30-60s TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 有界队列,如 ArrayBlockingQueue ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )拒绝策略策略行为AbortPolicy(默认)丢弃任务,抛 RejectedExecutionExceptionCallerRunsPolicy由提交任务的线程自行执行,可减缓提交速度(推荐)DiscardPolicy静默丢弃任务,不抛异常DiscardOldestPolicy丢弃队列最老的任务,重新提交当前任务推荐配置:CallerRunsPolicy + 监控告警。注意事项创建线程只有一种方式:new Thread().start()。生产环境只用线程池提交任务,禁止手动 new Thread()。for 循环中调用 t.start() 会立即返回,不会等待线程 run() 执行完。5.5 Future 与异步编程Callable 与 Runnable 的区别:Callable 有返回值,可抛出异常。提交 Callable 返回 Future<V>。get():获取结果(可能阻塞)get(long timeout, TimeUnit unit):限时获取cancel(boolean mayInterruptIfRunning):取消任务isDone():判断任务是否完成CompletableFuture实现 Future 和 CompletionStage,支持函数式异步编程。方法引用示例:Main::fetchPrice 等价于 () -> fetchPrice(),常用在 CompletableFuture.supplyAsync(Main::fetchPrice) 中。六、JDBC 简介各数据库厂商使用统一接口,Java 代码无需针对不同数据库分别开发。Maven 依赖:<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> <scope>runtime</scope> <!-- 运行时才需要此包 --> </dependency>建立连接:DriverManager.getConnection()(自动扫描 classpath 中的驱动)。重要提示:数据库自身就是并发专家,不要用 Java 层锁去干涉数据库操作,除非操作对象是内存数据。
2026年06月02日
4 阅读
0 评论
0 点赞
2026-06-02
Java 反射与动态代理 详细笔记
Java 反射与动态代理 详细笔记一、反射的核心思想反射:在程序运行时,动态获取某个类的完整信息(类名、方法、字段、父类、接口等),并能动态创建对象、调用方法、访问字段。不需要在编译时知道具体类名,可以在运行时根据字符串类名加载类。常用于框架(Spring、MyBatis)、通用工具(IDE 自动补全)、动态代理等。二、Class 类:一切反射的入口2.1 每个类都有唯一的 Class 对象JVM 在第一次加载一个类时,会在内存中为该类创建一个 Class 实例(俗称“说明书”)。这个 Class 实例保存了该类的全部信息(类名、包、方法、字段、父类、接口等)。无论通过哪种方式获取,同一个类的 Class 实例是同一个对象(可用 == 比较)。2.2 获取 Class 实例的三种方法// 方法1:通过类名.class(最直接,编译时已知类) Class<String> cls1 = String.class; // 方法2:通过实例的 getClass() 方法 String s = "hello"; Class<?> cls2 = s.getClass(); // 实际返回 String.class // 方法3:通过 Class.forName(完整类名)(运行时根据字符串加载) Class<?> cls3 = Class.forName("java.lang.String");三者获得的是同一个 Class 对象:cls1 == cls2 == cls3三、通过反射访问字段(Field)3.1 获取字段的方法(返回值是 java.lang.reflect.Field)方法说明能否得到父类字段getField(String name)获取 public 字段(包括父类)✅ 能getDeclaredField(String name)获取当前类声明的任意字段(不限访问修饰符)❌ 不能,只看当前类getFields()获取所有 public 字段(包括父类)✅ 能getDeclaredFields()获取当前类声明的所有字段(不包括父类)❌ 不能3.2 访问私有字段通过 getDeclaredField 拿到私有 Field 对象后,调用 setAccessible(true) 打破封装。然后可以 get(obj) / set(obj, value) 读写该私有字段。class Person { private String name; } Class<Person> cls = Person.class; Field field = cls.getDeclaredField("name"); field.setAccessible(true); // 允许访问私有字段 Person p = new Person(); field.set(p, "Alice"); String name = (String) field.get(p); // "Alice"四、通过反射调用方法(Method)4.1 获取方法的方法(返回值是 java.lang.reflect.Method)方法说明包括父类getMethod(String name, Class<?>... parameterTypes)获取某个 public 方法✅ 包括getDeclaredMethod(...)获取当前类声明的某个方法(不限修饰符)❌ 不包括getMethods()获取所有 public 方法✅ 包括getDeclaredMethods()获取当前类声明的所有方法❌ 不包括4.2 调用方法拿到 Method 对象后,调用 invoke(Object obj, Object... args)。如果方法是 static,第一个参数传 null。// 示例:调用 String 的 length() 方法 String str = "hello"; Method m = String.class.getMethod("length"); int len = (int) m.invoke(str); // len = 5五、通过反射调用构造器(Constructor)5.1 获取构造器方法说明getConstructor(Class<?>... parameterTypes)获取某个 public 构造器getDeclaredConstructor(...)获取任意修饰符的构造器(包括 private)getConstructors() / getDeclaredConstructors()获取所有构造器5.2 创建实例通过 Constructor.newInstance(Object... initargs) 创建对象。比 Class.newInstance() 更灵活(可以传参数,可以调用非 public 构造器)。// 调用带参数的构造器 Constructor<String> cons = String.class.getConstructor(String.class); String s = cons.newInstance("abc"); // 相当于 new String("abc") // 调用私有构造器(单例模式等) Constructor<MyClass> cons2 = MyClass.class.getDeclaredConstructor(); cons2.setAccessible(true); MyClass obj = cons2.newInstance();注意:Class.newInstance() 已过时(只能调用无参 public 构造器),推荐使用 Constructor.newInstance()。六、获取继承关系方法作用示例Class.getSuperclass()返回父类的 Class 对象Integer.class.getSuperclass() → Number.classClass.getInterfaces()返回当前类直接实现的接口数组Integer.class.getInterfaces() → Comparable, Constable, ConstantDescClass.isAssignableFrom(Class<?> cls)判断当前类是否可接收 cls 类型的对象(即 cls 能否向上转型为当前类)Number.class.isAssignableFrom(Integer.class) → true6.1 关于接口的特殊说明对接口调用 getSuperclass() → 返回 null。获取接口的父接口必须用 getInterfaces()。接口的 isAssignableFrom 用法与类一致:Serializable.class.isAssignableFrom(Integer.class) → true。6.2 instanceof 与 isAssignableFrom 的区别instanceof 需要对象实例:obj instanceof NumberisAssignableFrom 需要Class 对象:Number.class.isAssignableFrom(Integer.class)七、动态代理(Dynamic Proxy)7.1 解决的问题不手动编写接口的实现类,就能在运行时动态创建实现了该接口的对象。典型应用:AOP(面向切面编程)、RPC 框架、日志/事务拦截等。7.2 限制条件只能代理接口(不能代理普通类)。如果需要代理普通类,可使用 CGLIB 或 ByteBuddy。7.3 核心 APIjava.lang.reflect.Proxyjava.lang.reflect.InvocationHandler(函数式接口)7.4 实现步骤定义接口(必须,告知代理对象有哪些方法)。实现 InvocationHandler:重写 invoke 方法,定义当接口方法被调用时实际执行的代码。调用 Proxy.newProxyInstance 创建代理实例。将代理实例转型为接口类型,然后正常调用方法。7.5 示例代码(完整)// 1. 接口 public interface Hello { void morning(String name); } // 2. 使用动态代理(不写 HelloWorld 类) import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxyDemo { public static void main(String[] args) { // 2.1 定义调用处理器 InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("morning")) { String name = (String) args[0]; System.out.println("Good morning, " + name); } return null; // 方法返回 void,所以返回 null } }; // 2.2 创建代理对象 Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), // 类加载器 new Class[]{Hello.class}, // 要实现的接口列表 handler // 调用处理器 ); // 2.3 调用方法(实际执行 handler.invoke) hello.morning("Bob"); // 输出:Good morning, Bob } }7.6 动态代理内部原理Proxy.newProxyInstance 会在运行时动态生成一个新的类(例如 $Proxy0)。这个类实现了指定的接口(Hello),并且每个方法的实现都是:public void morning(String name) { super.h.invoke(this, method_morning, new Object[]{name}); }super.h 就是你传入的 InvocationHandler。然后通过 ClassLoader 加载这个类,并实例化返回。换句话说:动态代理 = JVM 自动帮你写了一个实现类,并用反射调用你的 handler。7.7 动态代理与反射的关系动态代理生成的新类($Proxy0)本身也是一个类,JVM 会为它创建 Class 对象。你可以对这个 Class 对象进行任何反射操作。InvocationHandler.invoke 方法中收到的 Method 参数就是通过反射得到的。所以动态代理是反射机制的一个典型应用,而不是替代品。八、总结:什么时候用什么技术?场景推荐技术已知类名,需要调用它的方法/访问字段直接调用,无需反射类名字符串在运行时才确定,需要创建实例Class.forName() + Constructor.newInstance()需要遍历一个对象的所有字段/方法(例如写序列化工具)getDeclaredFields() / getDeclaredMethods()不写实现类,仅通过接口定义行为动态代理需要在方法调用前后统一添加逻辑(日志、事务)动态代理 + InvocationHandler最后记住一句话:反射让你在运行时“看见”并“操作”一个类;动态代理让你在运行时“凭空造出”一个实现了接口的类,并把所有方法调用委托给你写的处理器。
2026年06月02日
7 阅读
0 评论
0 点赞
2026-05-27
test
AI Native 实战开发流程:以“管理员后台”为例(通用模板)本流程基于《AI Native 开发治理笔记》的治理理念,以 v3 管理后台 的一个典型功能“退款审核”为案例,展示如何用 AI 工程化方法交付一个安全、合规、可积累的生产级功能。该流程可复用到任何管理员后台的 CRUD、审批、报表、导出等场景。一、总体开发阶段概览阶段核心动作参与角色产出物(存入对应目录)1. 知识准备补充原始资料与业务规则人docs/、business/2. 任务规划编写 task 与选择 workflow人ai/tasks/current/、ai/workflows/3. AI 分析对齐让 AI 只分析,不写代码,对齐方案AI + 人分析报告(对话产物)4. AI 开发实现AI 按约束编码AI代码变更(apps/)5. 质量审查AI 自检 + 人复核AI + 人ai/reviews/6. 知识沉淀提炼通用经验与开发日志AI + 人ai/engineering-knowledge/、ai/development-logs/7. 交付上线最终验证与合并人可运行代码二、前置条件:工程骨架已搭建假设团队已经按治理笔记建立了以下目录和基础文件:v3-system/ ├── apps/ # 现有管理后台代码 ├── docs/ # 已有数据库 DDL、API 文档 ├── business/ # 已有 order/wallet/refund 等业务规则 ├── ai/ │ ├── tasks/current/ │ ├── workflows/ │ ├── reviews/ │ ├── engineering-knowledge/ │ └── development-logs/ ├── AGENTS.md # 已编写全局 AI 规则 └── CLAUDE.md如果还没有,必须先按笔记完成骨架搭建,否则后续流程会因为缺少约束而失控。三、实战步骤详解(以退款审核为例)阶段 1:知识准备——让 AI 有据可依目标:确保 AI 看到的“真相”是完整且准确的。人需要做的事情:检查 docs/是否存在 refund_order 表的 DDL?字段、索引、注释是否完整?是否有关联的 wallet、wallet_transaction 表结构?支付回调相关的第三方文档是否可访问?如果不全,立即用 mysqldump --no-data 导出最新结构放入 docs/db/。编写/更新 business/refund/rules.md 按照笔记模板,补充退款专属规则,例如:# 退款规则 1. 仅支付成功订单可退款 2. 退款必须审核(状态机:PENDING_AUDIT → APPROVED / REJECTED) 3. 退款执行时原子增加用户余额(UPDATE wallet SET balance = balance + ? WHERE user_id = ?) 4. 幂等:退款单 APPROVED 后重复请求直接返回成功 5. 记录 wallet_transaction 类型为 REFUND检查 business/wallet/invariants.md 确认余额不变量(balance >= 0)等规则已存在。产出物:更新后的 docs/ 和 business/,AI 的“知识底座”建立完毕。阶段 2:任务规划——画好 AI 的“活动圈”目标:精确控制 AI 本次能做什么、不能做什么。人需要做的事情:创建任务文件 ai/tasks/current/TASK-REFUND-APPROVE.md 按模板填写(关键字段见笔记第七章),示例要点:Goal:开发退款审核通过接口Allowed Modules:RefundController、RefundService、RefundDao,表 refund_order, wallet, wallet_transactionForbidden Modules:auth, distributor, goods,以及任何前端代码Technical Requirements:JdbcTemplate、@Transactional、Redis 锁、幂等Deliverables:修改的文件、SQL 变更、风险点选择或编写 workflow 如果 ai/workflows/add-admin-api.md 已存在,则沿用;否则参考笔记第八章编写一个。workflow 定义了标准开发步骤:分析 → 方案 → 编码 → 自检。产出物:TASK-REFUND-APPROVE.md 和确认使用的 workflow。阶段 3:AI 分析对齐——让 AI 先“说出来”再动手目标:AI 在不写代码的前提下,给出完整技术方案,由人确认,避免方向错误。操作步骤:新建 AI 会话,使用分析 Prompt(复制以下内容)请按顺序读取以下文件: 1. AGENTS.md 2. business/refund/rules.md 3. business/wallet/invariants.md 4. ai/workflows/add-admin-api.md 5. ai/tasks/current/TASK-REFUND-APPROVE.md 目标:理解系统约束和本次任务边界。 现在,仅进行分析设计,**禁止编写任何实现代码**。 请输出: 1. API 设计草案(POST /api/admin/refund/approve,请求体 refundOrderId,返回体通用Result) 2. SQL 设计草案(查询退款单、更新状态、原子更新钱包余额、插入流水) 3. 事务边界:退款单状态更新 + 余额更新 + 流水插入必须在同一事务 4. 并发控制方案:Redis分布式锁,key = lock:refund:approve:{refundOrderId},超时30s 5. 风险点分级(如:余额并发更新 -> 已用原子SQL规避;锁未释放导致死锁 -> finally释放) 6. 需要澄清的业务点(如:退款金额是否包含优惠券部分?我们假设仅退现金)审核 AI 输出的分析报告 重点检查:SQL 是否使用原子更新而非 select-then-update?事务是否覆盖了所有需要一致性的操作?幂等逻辑是否只是状态检查(可靠吗?)?结合 Redis 锁后的行为?权限校验是否遗漏(Controller 上要加 RBAC 注解)?修改方案直到满意,若有业务不确定点,拉产品经理确认。产出物:一份人工确认过的技术方案(可保存在 ai/reviews/ 下作为设计记录)。阶段 4:AI 开发实现——戴着手铐跳舞目标:AI 严格遵守约束,产出符合规范的代码。操作步骤:在同一会话(或保留上下文的新会话)发送开发 Prompt基于我们已确认的方案,现在开始编码。 请严格遵守以下约束: - 仅修改 task 中 Allowed Modules 内的文件 - 遵守 AGENTS.md 所有规则(SQL规则、事务规则、架构分层等) - 使用 JdbcTemplate,保持与现有代码风格一致 - 实现幂等、分布式锁、事务、原子更新 - 任何新增文件需先说明用途 请按顺序产出: 1. DAO 层方法(JdbcTemplate实现) 2. Service 层逻辑(含事务和锁) 3. Controller 层(参数校验、调用Service) 4. 针对每个 SQL 给出 EXPLAIN 输出 5. 自认为的实现风险点监控 AI 产出 人随时检查 AI 是否有越界行为(比如偷偷修改了 WalletController),一旦发现立即叫停并纠正。 通常 AI 会直接给出代码块,将其复制到对应文件或通过工具应用。产出物:修改的 Java 文件(及可能的 SQL 迁移脚本),AI 提供的 EXPLAIN 结果。阶段 5:质量审查——AI 自首 + 人工终审目标:系统性捕捉缺陷,防止带病上线。操作步骤:发送 Review Prompt请对我们刚刚实现的退款审核接口进行完整 Review,严格遵守 AGENTS.md 的 Review 规则。 逐一检查: - SQL 注入 - EXPLAIN 分析(再次确认索引命中) - 事务是否覆盖正确 - 空指针可能点 - 业务不变量(balance >= 0, 状态机) - 幂等性 - Redis 锁的使用(正确加锁、解锁、超时) - RBAC 权限注解是否添加 - 缓存一致性(如果涉及缓存) 请输出结构化 Review 报告,标注每项的通过/不通过/风险及建议。人复核 Review 报告对阻断性问题,让 AI 立即修复,然后重新走一遍轻量 review。存档报告到 ai/reviews/REVIEW-REFUND-APPROVE-20260527.md。产出物:Review 报告,以及修复后的代码。阶段 6:知识沉淀——把教训变成 AI 的长期记忆目标:让这次开发的经验在未来项目中自动生效。操作步骤:提炼 engineering-knowledge 从 Review 报告和开发过程中的坑点提炼通用知识,例如:refund-atomic-balance.md(退款余额原子更新)redis-lock-finally-unlock.md(锁必须 finally 释放)人可手动编写或让 AI 生成:从本次 Review 和开发日志中提取可复用的工程知识,按 ai/engineering-knowledge/ 模板创建文件。生成 development-log请生成本次开发日志,包含已完成功能、遗留风险、关键决策、下一步计划,存入 ai/development-logs/LOG-20260527-REFUND-APPROVE.md。产出物:新的 knowledge 文件和开发日志。阶段 7:交付上线——完成闭环目标:功能正式合入主分支。运行单元测试、集成测试。进行 Code Review(可能还有人工同事)。部署到测试环境验证。上线生产,监控日志和报警。此时,TASK-REFUND-APPROVE.md 移入 ai/tasks/archive/。四、对管理员后台的通用性说明上述流程不限于退款审核,任何后台功能开发皆可套用:新增列表查询:task 约束仅修改列表相关 DAO/Service,workflow 引用 add-admin-query.md,business 关注深分页、数据脱敏规则。导出报表:约束文件生成、异步任务、限流规则。权限/角色管理:重点关注 RBAC 规则、审计日志要求。配置管理:关注缓存刷新、并发修改。只需按照功能点创建对应的 business 规则和 task,选择或定制 workflow,其余审查、知识沉淀、开发日志步骤完全一致。五、核心成功要素回顾约束先行:business、AGENTS、task 必须写清楚再让 AI 动手。分析分离:先出方案、对齐,再编码,避免返工。审查强制:SQL、事务、幂等、并发必须逐项过关。知识积累:每次开发结束必须产出 knowledge,形成复利。记忆外挂:development-log 让 AI 随时恢复上下文,支撑连续迭代。坚持这套流程 2~3 个迭代后,团队会发现:新增后台功能的 AI 出错率大幅下降,开发速度稳步提升,而维护成本反而降低——因为所有规则都已编码在知识体系中,AI 越来越像一个懂业务的资深工程师。
2026年05月27日
3 阅读
0 评论
0 点赞
2026-05-27
AI 开发治理入门笔记(初学者实战版)
目标:你不是学习:“怎么让 AI 帮你写几段代码”而是学习:“怎么真正用 AI 开发一个长期项目”下面这份。我会完全按:真实开发流程给你讲。一、先理解:AI 开发真正的问题是什么?很多新人以为:AI开发是:“会 Prompt”其实不是。真正问题是:AI 有三个巨大问题问题结果AI 会乱改代码改到别的模块AI 会忘记业务状态机错误AI 会忘记上下文第二天继续不了所以:真正的 AI 开发:本质是:管理 AI不是:单纯使用 AI二、真正 AI 工程的核心思想你以后:不要这样:错误方式“帮我开发后台”因为:AI 会:无限扩散改很多文件忘记事务忘记权限忘记 review正确方式你以后:每次只做:一个明确任务例如:开发用户分页接口不是:开发整个后台三、真正完整目录(你以后就这样)v3-system/ ├── apps/ ├── docs/ ├── business/ ├── ai/ │ ├── tasks/ │ ├── workflows/ │ ├── reviews/ │ ├── engineering-knowledge/ │ └── development-logs/ ├── AGENTS.md └── CLAUDE.md下面。我一个一个给你解释。四、docs(原始资料)作用存:项目原始资料例如:docs/ ├── auth.sql ├── business.sql ├── 管理员API.md └── 产品需求.md谁创建?你AI 能修改吗?不能为什么?因为:docs 是:原始真相AI:不能:修改真相五、business(最重要)这个:是:整个系统最重要的东西business 是什么?business:保存:业务规则例如:订单状态钱包规则分销规则提现规则为什么一定需要 business?因为:AI:根本不懂你的业务它只能猜。举例如果:你没有 business。AI 可能:允许:已完成订单退款但实际上:你业务:禁止完成后退款所以:业务规则:必须:你定义六、business 实际怎么写?文件business/order.md内容(真实版)# Order State Machine CREATED → PAID → DELIVERING → FINISHED 退款: PAID → REFUND_PENDING → REFUNDED 禁止: - FINISHED退款 - CREATED退款七、AGENTS.md(AI宪法)这个:超级重要。它是什么?它是:AI全局规则类似:公司制度文件AGENTS.md内容(初学者推荐版)# Global AI Rules ## SQL Rules - Never use SELECT * - Never update without WHERE - Always check indexes --- ## Scope Rules Never modify unrelated modules. --- ## Architecture Rules - Controller handles params only - Service handles business only - DAO handles SQL only --- ## Transaction Rules Critical operations must use transactions. --- ## Review Rules Always review: - transaction - sql - security - invariants八、为什么 AGENTS 很重要?因为:AI:非常容易乱来例如:你开发:用户分页AI:突然:改 auth改 wallet改 distributor很常见。所以:必须:提前立规矩九、tasks(最关键)本质本次任务 + AI边界为什么 task 最重要?因为:它决定:AI 允许做什么task 文件ai/tasks/admin-user-page.mdtask 内容(真实版)# Goal 开发: GET /admin/user/page --- # Features 支持: - phone - status - create_time 分页: - page - pageSize --- # Allowed Modules - user --- # Forbidden Modules - auth - wallet - distributor --- # Requirements 1. JdbcTemplate 2. Explain SQL 3. Prevent SQL Injection 4. Soft Delete Only --- # Deliverables Output: - modified files - sql - risks十、workflow(SOP)很多新人:最容易误解这个。workflow 不是任务workflow:不是:“开发用户分页”而是:“新增后台接口应该怎么做”workflow 本质AI工作流程模板文件ai/workflows/add-admin-api.md内容# Add Admin API Workflow ## Analysis 1. Analyze database 2. Check indexes 3. Check transactions 4. Check permissions --- ## Design Output: - api path - params - response - sql - risks --- ## Development 1. DAO 2. Service 3. Controller --- ## Review Must review: - explain - sql injection - RBAC - invariants十一、workflow 什么时候用?开始任务前例如:开发:退款接口AI:先读取:workflow知道:正确开发步骤十二、真正完整开发流程(重点)现在。真正开发开始。Step 1:你先看 docs理解:SQLAPI产品Step 2:你写 business定义:业务真相例如:退款规则。Step 3:你写 task限制:AI工作边界Step 4:AI读取 workflow学习:标准开发流程Step 5:第一次 AI 对话(分析)Prompt(你直接背)请读取: - AGENTS.md - business/order.md - ai/workflows/add-admin-api.md - ai/tasks/refund-api.md 现在: 只进行分析。 禁止开发。 输出: 1. API设计 2. SQL设计 3. 索引检查 4. 风险点 5. 事务分析 6. review建议为什么先分析?因为:AI:直接开发很危险Step 6:你审核分析结果检查:SQL风险事务是否符合业务Step 7:第二次 AI 对话(开发)Prompt(必背)请读取: - AGENTS.md - business/order.md - ai/workflows/add-admin-api.md - ai/tasks/refund-api.md 根据已确认方案。 现在开始开发。 要求: 1. 先DAO 2. 再Service 3. 最后Controller 输出: 1. 修改文件 2. explain sql 3. 风险 4. review建议Step 8:第三次 AI 对话(Review)Prompt(必背)review 当前模块。 检查: - explain - sql injection - indexes - null pointer - transaction - RBAC - idempotent - redis consistency - deep paginationreviews 保存什么?文件ai/reviews/refund-review.md内容# Refund Review 1. Missing Redis Lock 2. Callback not idempotent 3. Wallet rollback risk十三、engineering-knowledge(长期经验)review 后。你发现:支付回调容易重复这是:长期经验于是:保存:文件ai/engineering-knowledge/payment.md内容# Payment Callback 支付回调必须幂等。 原因: 支付平台会重复回调。十四、development-logs(开发记忆)文件ai/development-logs/refund-api.md内容# Refund API Development Log Completed: - refund api - sql review Risks: - no retry queue Next: - add retry mechanism十五、为什么必须有 DevelopmentLog?因为:AI:会失忆第二天:你继续开发。AI 读取:development-log快速恢复上下文。十六、真正成熟后的核心思想(最重要)你以后:不要认为:AI工程 = Prompt真正成熟后:AI工程:本质是:规则 + 边界 + SOP + Review + Knowledge + 记忆十七、最后一个真正重要的建议(一定记住)你现在:不要追求:“最完整治理”真正长期能跑的:一定是:最小必要治理先:能稳定开发再:慢慢增加规则这是:真正企业级 AI Native 开发
2026年05月27日
4 阅读
0 评论
0 点赞
1
2
3
...
8