首页
壁纸
统计
友链
Search
1
Vue2详细笔记
68 阅读
2
ExpressAPI
34 阅读
3
微信小程序
31 阅读
4
Spring MVC 请求生命周期
30 阅读
5
Nestjs概述-中文
29 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
40
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
18
篇与
的结果
2026-06-03
反射 + AOP + 注解
反射 + AOP + 注解 讲解目录注解(Annotation)什么是注解元注解与自定义注解如何在反射中读取注解反射(Reflection)反射的核心类:Class、Method、Field动态创建对象、调用方法、访问字段反射与注解的结合AOP(面向切面编程)AOP 的核心概念Spring AOP 的实现原理(代理)通知类型与切入点表达式如何在切面中使用反射和注解三剑合璧实战案例案例1:自定义 @Log 日志记录案例2:自定义 @RequirePermission 权限控制总结与关系图谱一、注解(Annotation)1.1 什么是注解?注解是 Java 5 引入的一种元数据,它本身不影响代码逻辑,但可以被编译、运行时工具或框架读取,从而赋予代码额外的语义或行为。常见注解如 @Override、@Deprecated、@Autowired 等。1.2 元注解定义注解时需要用到的注解,共有四个:元注解作用@Target指定注解能用在哪里(类、方法、字段等)@Retention指定注解的存活周期(源码、字节码、运行时)@Documented是否包含在 JavaDoc 中@Inherited是否允许子类继承父类的注解@Retention 的取值:RetentionPolicy.SOURCE:只在源码中存在,编译时丢弃(如 @Override)RetentionPolicy.CLASS:在字节码中存在,但运行时不可见(默认)RetentionPolicy.RUNTIME:运行时可通过反射读取(自定义注解必须选这个)1.3 自定义一个注解import java.lang.annotation.*; @Target(ElementType.METHOD) // 只能用在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时保留 public @interface Loggable { String value() default ""; // 注解属性 }使用:@Loggable("删除用户") public void deleteUser(Long id) { ... }1.4 在反射中读取注解无论是类、方法还是字段,反射 API 都提供了读取注解的方法:getAnnotation(Class<T> annotationClass) —— 获取指定类型的注解getAnnotations() —— 获取所有注解(含继承)getDeclaredAnnotations() —— 获取直接声明的注解(不含继承)isAnnotationPresent(Class<?> annotationClass) —— 判断是否存在某注解示例:读取方法上的 @LoggableMethod method = ...; // 通过反射拿到方法对象 Loggable log = method.getAnnotation(Loggable.class); if (log != null) { System.out.println("描述:" + log.value()); }二、反射(Reflection)2.1 反射的核心类类作用获取方式Class类的元信息对象.getClass()、类名.class、Class.forName("全类名")Method方法信息Class 对象的 getMethod(...)、getDeclaredMethods() 等Field字段信息Class 对象的 getField(...)、getDeclaredFields() 等Constructor构造器信息Class 对象的 getConstructor(...)、getDeclaredConstructors() 等2.2 获取 Class 对象的三种方式// 1. 通过类名.class Class<String> clz1 = String.class; // 2. 通过实例的 getClass() String s = "hello"; Class<?> clz2 = s.getClass(); // 3. 通过完全限定名字符串 Class<?> clz3 = Class.forName("java.lang.String");2.3 获取 Method 对象方法说明getMethod(name, paramTypes...)获取指定名称和参数的 public 方法(含继承)getMethods()获取所有 public 方法(含继承)getDeclaredMethod(name, paramTypes...)获取本类中声明的方法(不含继承,可访问私有)getDeclaredMethods()获取本类中声明的所有方法(不含继承)示例:获取 deleteUser(Long) 方法Method method = UserService.class.getMethod("deleteUser", Long.class);2.4 动态调用方法UserService target = new UserService(); Method method = UserService.class.getMethod("deleteUser", Long.class); Object result = method.invoke(target, 123L); // 相当于 target.deleteUser(123L)如果方法是私有的,需要先 setAccessible(true)。2.5 反射与注解的结合反射的 Method、Field、Class 等都实现了 AnnotatedElement 接口,因此可以直接读取注解。Method method = ...; if (method.isAnnotationPresent(Loggable.class)) { Loggable log = method.getAnnotation(Loggable.class); System.out.println("需要记录日志:" + log.value()); }应用场景:框架(Spring、Hibernate)通过反射扫描注解,完成依赖注入、ORM 映射等。自定义工具类,根据注解动态生成 SQL、校验参数等。三、AOP(面向切面编程)3.1 AOP 核心概念切面(Aspect):封装横切逻辑的模块(比如日志、权限)。连接点(JoinPoint):程序执行过程中的一个点(方法调用、字段访问等),Spring AOP 只支持方法连接点。切入点(Pointcut):匹配连接点的表达式,决定哪些方法会被增强。通知(Advice):在切入点处执行的动作(具体逻辑)。目标对象(Target):被代理的原始对象。代理(Proxy):包裹目标对象,加入切面逻辑后生成的对象。3.2 Spring AOP 的实现Spring AOP 基于动态代理:如果目标类实现了接口,默认使用 JDK 动态代理(java.lang.reflect.Proxy)。如果没有接口,则使用 CGLIB 生成子类作为代理。从 Spring Boot 2.0 开始,默认使用 CGLIB 代理(即使有接口也会用 CGLIB)。3.3 通知类型与切入点表达式通知类型注解说明@Before目标方法执行前@AfterReturning目标方法正常返回后@AfterThrowing目标方法抛出异常后@After目标方法结束后(类似 finally)@Around包围目标方法,可控制是否执行常用切入点表达式表达式含义execution(* com.example.service.*.*(..))匹配 service 包下的所有方法@annotation(com.example.Loggable)匹配带有 @Loggable 注解的方法execution(public * *(..))匹配所有 public 方法within(com.example.service.*)匹配 service 包下的所有类的所有方法3.4 切面中如何使用反射和注解在 @Around 通知中,通过 ProceedingJoinPoint 可以获取方法签名(MethodSignature),然后通过反射拿到 Method 对象,进而读取注解。@Around("@annotation(loggable)") public Object log(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable { // 获得方法签名 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); // 反射 Method 对象 // 可以读取更多注解 AnotherAnnotation anno = method.getAnnotation(AnotherAnnotation.class); // 执行原方法 return pjp.proceed(); }为什么要 MethodSignature? 因为 pjp.getSignature() 返回的是通用的 Signature 接口,只有强转成 MethodSignature 才能获取反射 Method 以及参数名、返回类型等详细信息。3.5 JoinPoint 信息树(速查)JoinPoint (ProceedingJoinPoint 继承它) ├─ getTarget() → 目标对象(被代理的原始 Bean) ├─ getThis() → 当前代理对象 ├─ getArgs() → 方法参数值数组 ├─ getSignature() → Signature (实际是 MethodSignature) │ ├─ getName() → 方法名 │ ├─ getDeclaringTypeName() → 声明该方法的类全名 │ └─ (MethodSignature 特有) │ ├─ getMethod() → 反射 Method ★ │ │ └─ getAnnotation(..) → 获取注解 │ ├─ getParameterNames() → 参数名数组(需编译配置) │ ├─ getReturnType() → 返回类型 │ └─ getParameterTypes() → 参数类型数组 └─ getStaticPart() → 同 getSignature()四、三剑合璧实战案例4.1 案例 1:@Log 注解实现方法日志记录需求:在方法执行时自动打印入参、出参和耗时。步骤:自定义 @Log 注解编写切面 LogAspect,使用 @Around + @annotation在切面中通过 MethodSignature 获取方法名,通过 getArgs() 获取参数值// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { } // 切面 @Aspect @Component public class LogAspect { @Around("@annotation(com.example.Log)") public Object around(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); String method = sig.getDeclaringTypeName() + "." + sig.getName(); Object[] args = pjp.getArgs(); System.out.println(">>> 调用 " + method + ",参数:" + Arrays.toString(args)); long start = System.currentTimeMillis(); Object result = pjp.proceed(); long time = System.currentTimeMillis() - start; System.out.println("<<< 返回:" + result + " 耗时:" + time + "ms"); return result; } } // 业务使用 @Service public class UserService { @Log public String getUser(Long id) { return "User#" + id; } }4.2 案例 2:@RequirePermission 权限控制需求:标记方法需要某权限,没有则拒绝访问。实现思路:定义注解 @RequirePermission("user:delete")在拦截器中把用户权限存入 ThreadLocal切面中读取注解所需的权限值,与用户权限对比,不满足抛异常// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequirePermission { String value(); } // 权限上下文 public class UserContext { private static final ThreadLocal<List<String>> PERMISSIONS = new ThreadLocal<>(); public static void set(List<String> p) { PERMISSIONS.set(p); } public static List<String> get() { return PERMISSIONS.get() != null ? PERMISSIONS.get() : Collections.emptyList(); } public static void clear() { PERMISSIONS.remove(); } } // 切面 @Aspect @Component public class PermissionAspect { @Around("@annotation(RequirePermission)") public Object check(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); Method method = sig.getMethod(); RequirePermission permission = method.getAnnotation(RequirePermission.class); String required = permission.value(); if (!UserContext.get().contains(required)) { throw new RuntimeException("无权限:" + required); } return pjp.proceed(); } } // 拦截器(简化) public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, ...) { String user = request.getHeader("X-User"); List<String> permissions = loadPermissions(user); // 从数据库查 UserContext.set(permissions); return true; } @Override public void afterCompletion(...) { UserContext.clear(); } }五、总结与关系图谱三者关系:注解 是标记,贴在代码上描述元数据。反射 是读取这些标记和操作代码结构(类、方法)的能力。AOP 是利用反射机制,在运行时动态地将横切逻辑织入到带有特定注解(或符合表达式)的方法周围。常见协作模式:自定义一个注解(如 @Log)在 AOP 切面中,通过切入点表达式 @annotation(Log) 拦截所有标注了该注解的方法在切面通知方法里,利用 JoinPoint/ProceedingJoinPoint 获取方法签名(MethodSignature)通过 MethodSignature.getMethod() 拿到反射 Method 对象,再用 getAnnotation() 读取注解的具体值,实现差异化逻辑学习路径建议:先熟练 反射(Class、Method、注解读取)理解 代理模式(JDK 动态代理、CGLIB)掌握 Spring AOP 的基本用法(切入点表达式、通知类型)最后将三者结合,实现自定义注解驱动切面当你遇到如下代码时,大脑能快速解析:MethodSignature sig = (MethodSignature) joinPoint.getSignature(); Method method = sig.getMethod(); MyAnnotation ann = method.getAnnotation(MyAnnotation.class);它正在用 AOP 的连接点 获取 反射的 Method,然后 读取自定义注解,从而执行相应逻辑——这。
2026年06月03日
15 阅读
0 评论
1 点赞
2026-06-03
Spring JDBC + JdbcTemplate
Spring JDBC + JdbcTemplate 学习笔记(方法驱动格式)本笔记聚焦于 JdbcTemplate 中常用的数据库操作方法。每个方法均按以下结构介绍:方法:方法签名/名称作用:该方法解决什么问题参数:主要参数的含义和类型方法案例:代码示例(可直接运行)实例说明:对案例的详细解释,包括注意事项前置环境:Spring 6 + HikariCP + HSQLDB,配置了 DataSource 和 JdbcTemplate Bean。第一章:准备工作与环境配置1.1 Maven 依赖<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.7.1</version> </dependency>1.2 Spring 配置类(AppConfig)@Configuration @ComponentScan @PropertySource("jdbc.properties") public class AppConfig { @Value("${jdbc.url}") String jdbcUrl; @Value("${jdbc.username}") String jdbcUsername; @Value("${jdbc.password}") String jdbcPassword; @Bean DataSource dataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); return new HikariDataSource(config); } @Bean JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }1.3 数据库初始化(自动建表)@Component public class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; @PostConstruct void init() { jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" + "id BIGINT IDENTITY PRIMARY KEY, " + "email VARCHAR(100) NOT NULL UNIQUE, " + "password VARCHAR(100), " + "name VARCHAR(100))"); } }第二章:核心方法详解(按格式组织)方法一:queryForObject() – 查询单行记录并映射为对象作用:执行查询 SQL,期望返回一条记录,并将结果集的第一行通过 RowMapper 转换成 Java 对象。若查询结果为空或大于一行则抛出异常。参数:String sql – SQL 语句(可使用 ? 占位符)RowMapper<T> rowMapper – 将 ResultSet 当前行映射为 T 对象的函数式接口Object... args – 可变参数,按顺序替换 SQL 中的 ?方法案例:根据 email 查询用户public User getUserByEmail(String email) { String sql = "SELECT id, email, password, name FROM users WHERE email = ?"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), email ); }实例说明:RowMapper 的 lambda 表达式接收 ResultSet 和当前行号(通常忽略行号),手动构建 User 对象。如果数据库中不存在该 email,会抛出 EmptyResultDataAccessException,需要捕获处理或改为返回 null。也适用于聚合查询,例如统计总数:public long countUsers() { String sql = "SELECT COUNT(*) FROM users"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getLong(1)); }方法二:query() – 查询多行记录,返回 List作用:执行查询 SQL,将结果集中的每一行都通过 RowMapper 转换为对象,最后返回 List<T>。结果集为空时返回空列表(不抛异常)。参数:String sql – SQL 语句RowMapper<T> rowMapper – 行映射器Object... args – 参数列表方法案例:分页查询用户public List<User> getUsersByPage(int pageIndex, int pageSize) { String sql = "SELECT * FROM users LIMIT ? OFFSET ?"; int offset = (pageIndex - 1) * pageSize; return jdbcTemplate.query(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), pageSize, offset ); }实例说明:使用 BeanPropertyRowMapper 可自动映射(要求列名与属性名一致或下划线转驼峰):public List<User> getAllUsers() { return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class)); }BeanPropertyRowMapper 适合字段较多的简单映射;复杂映射仍需手动 RowMapper。方法三:update() – 执行插入、更新、删除作用:执行 DML 语句(INSERT/UPDATE/DELETE),返回受影响的行数。参数:String sql – DML 语句Object... args – 参数列表返回值:int – 受影响的行数方法案例 1:更新用户名称public int updateUserName(Long id, String newName) { String sql = "UPDATE users SET name = ? WHERE id = ?"; return jdbcTemplate.update(sql, newName, id); }实例说明:调用方可根据返回值判断是否更新成功(例如返回值 == 0 表示用户不存在)。方法案例 2:删除用户public boolean deleteUser(Long id) { String sql = "DELETE FROM users WHERE id = ?"; int rows = jdbcTemplate.update(sql, id); return rows > 0; }方法案例 3:插入用户(不返回主键)public void addUserWithoutKey(String name, String email) { String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; jdbcTemplate.update(sql, name, email); }方法四:update() + KeyHolder – 插入并返回自增主键作用:执行插入语句后,获取数据库自动生成的主键(如 IDENTITY 或 AUTO_INCREMENT)。参数:PreparedStatementCreator psc – 用于创建 PreparedStatement,必须指定 Statement.RETURN_GENERATED_KEYSKeyHolder keyHolder – 用于接收生成的主键方法案例:public User addUserAndGetId(String name, String email, String password) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, password); return ps; }, keyHolder); Long newId = keyHolder.getKey().longValue(); return new User(newId, email, password, name); }实例说明:PreparedStatementCreator 是一个函数式接口,需手动设置参数并返回带 RETURN_GENERATED_KEYS 的 PreparedStatement。执行后通过 keyHolder.getKey() 获取主键,通常为 Number 类型,需转换为 Long 或 Integer。适用于大多数关系型数据库(MySQL、HSQLDB、PostgreSQL、SQL Server 等)。方法五:batchUpdate() – 批量操作作用:一次性执行多条相同结构的 SQL(通常是批量插入或批量更新),性能远高于循环单条执行。参数:String sql – 带占位符的 SQL 模板List<Object[]> batchArgs – 每个元素是一个 Object[],代表一次执行对应的参数返回值:int[] – 数组长度等于批处理次数,每个元素为对应操作影响的行数方法案例:批量插入多个用户public int[] batchAddUsers(List<User> users) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; List<Object[]> batch = new ArrayList<>(); for (User u : users) { batch.add(new Object[]{u.getName(), u.getEmail(), u.getPassword()}); } return jdbcTemplate.batchUpdate(sql, batch); }实例说明:批量操作会减少 JDBC 与数据库之间的网络往返次数,显著提升吞吐量。注意:某些数据库(如 HSQLDB)对单次批处理的大小有限制,可分批提交。方法六:execute() – 执行任意 SQL(DDL 或存储过程)作用:执行 DDL(如 CREATE TABLE)或任何不希望返回结果集的 SQL,也可以用于执行存储过程。参数:String sql – 要执行的 SQL 字符串返回值:无(或者可重载为 execute(Callback) 形式返回自定义结果)方法案例:创建日志表public void createLogTable() { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS sys_log (" + "id INT PRIMARY KEY, " + "message VARCHAR(255), " + "create_time TIMESTAMP)"); }实例说明:execute(String sql) 不关心返回结果,适合做数据库初始化、清空表、重建索引等管理操作。方法七:execute(ConnectionCallback) – 最底层扩展作用:获取原生 JDBC Connection 对象,执行任意复杂数据库操作(如设置超时、调用存储过程、使用原生 API 等)。参数:ConnectionCallback<T> action – 回调接口,接收 Connection 并返回 T返回值:由回调决定的 T方法案例:使用原生 PreparedStatement 和手动控制事务public User getUserWithNativeConnection(Long id) { return jdbcTemplate.execute((Connection conn) -> { try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) { ps.setLong(1, id); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ); } return null; } } }); }实例说明:JdbcTemplate 会自动释放 Connection,但回调内创建的 PreparedStatement 和 ResultSet 必须由开发者手动释放(建议使用 try-with-resources)。一般情况下优先使用更高级的 query()、update() 等方法,只有非常规需求时才使用 ConnectionCallback。方法八:execute(PreparedStatementCallback) – 灵活处理 PreparedStatement作用:由 JdbcTemplate 创建 PreparedStatement,回调中可自行执行查询、更新并处理结果集。参数:String sql – SQL 模板PreparedStatementCallback<T> action – 回调接收 PreparedStatement,返回 T方法案例:判断用户是否存在public boolean existsUserByName(String name) { return jdbcTemplate.execute("SELECT 1 FROM users WHERE name = ?", (PreparedStatement ps) -> { ps.setString(1, name); try (ResultSet rs = ps.executeQuery()) { return rs.next(); // 有记录返回 true } }); }实例说明:此方法比 ConnectionCallback 更高层,无需创建 PreparedStatement,但依然需要手动执行查询和处理 ResultSet。第三章:RowMapper 详解(两个常用实现)3.1 手动 RowMapper(Lambda)作用:将 ResultSet 当前行的数据提取并组装成业务对象。格式:(ResultSet rs, int rowNum) -> { ... }案例:已在 queryForObject 中展示。3.2 BeanPropertyRowMapper作用:自动将结果集的列名(支持下划线转驼峰)映射到 JavaBean 的同名属性(需有 setter)。参数:BeanPropertyRowMapper.newInstance(Class<T>) 或构造函数。案例:List<User> users = jdbcTemplate.query("SELECT id, email, name FROM users", BeanPropertyRowMapper.newInstance(User.class));实例说明:要求数据库列名 email 对应 Java 属性 email;若列名为 user_name,则对应 userName。非常便捷,适合大多数查询场景。第四章:声明式事务管理核心注解:@Transactional作用:将方法(或类中的所有方法)标记为事务性方法。Spring 会在方法执行前开启事务,执行成功则提交,抛出未捕获异常则回滚。参数(常用):propagation – 传播行为(默认 REQUIRED)isolation – 隔离级别(默认数据库默认级别)rollbackFor – 指定哪些异常触发回滚(默认 RuntimeException 和 Error)方法案例:银行转账(两个更新操作必须在同一事务)@Service public class AccountService { @Autowired JdbcTemplate jdbcTemplate; @Transactional public void transfer(Long fromId, Long toId, BigDecimal amount) { jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId); // 模拟异常:如果转账金额过大,回滚两个更新 if (amount.compareTo(BigDecimal.valueOf(10000)) > 0) { throw new RuntimeException("转账金额超过限额"); } jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId); } }实例说明:需要先在配置类上添加 @EnableTransactionManagement 并注册 DataSourceTransactionManager。默认情况下,只有 RuntimeException 会触发回滚;若希望检查型异常也回滚,需设置 @Transactional(rollbackFor = Exception.class)。事务边界默认基于 AOP 代理,内部方法调用(this.method())不会触发事务。第五章:实战示例 – RESTful API 完整流程5.1 User 实体public class User { private Long id; private String name; private String email; private String password; // getter/setter/构造方法 省略 }5.2 UserService(使用上述方法)@Service public class UserService { @Autowired JdbcTemplate jdbcTemplate; public User addUser(String name, String email, String pwd) { KeyHolder kh = new GeneratedKeyHolder(); jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement( "INSERT INTO users (name, email, password) VALUES (?,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, pwd); return ps; }, kh); return new User(kh.getKey().longValue(), email, pwd, name); } public User getUser(Long id) { try { return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", BeanPropertyRowMapper.newInstance(User.class), id); } catch (EmptyResultDataAccessException e) { return null; } } public List<User> listUsers() { return jdbcTemplate.query("SELECT * FROM users", BeanPropertyRowMapper.newInstance(User.class)); } public int updateUser(Long id, String newName) { return jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id); } public int deleteUser(Long id) { return jdbcTemplate.update("DELETE FROM users WHERE id = ?", id); } }5.3 UserController@RestController @RequestMapping("/api/users") public class UserController { @Autowired UserService userService; @PostMapping public User add(@RequestParam String name, @RequestParam String email, @RequestParam String pwd) { return userService.addUser(name, email, pwd); } @GetMapping("/{id}") public User get(@PathVariable Long id) { return userService.getUser(id); } @GetMapping public List<User> list() { return userService.listUsers(); } @PutMapping("/{id}") public String update(@PathVariable Long id, @RequestParam String name) { int rows = userService.updateUser(id, name); return rows > 0 ? "success" : "user not found"; } @DeleteMapping("/{id}") public String delete(@PathVariable Long id) { int rows = userService.deleteUser(id); return rows > 0 ? "deleted" : "not exist"; } }5.4 测试命令# 新增 curl -X POST "http://localhost:8080/api/users?name=张三&email=zs@test.com&pwd=123" # 查询单个 curl "http://localhost:8080/api/users/1" # 查询全部 curl "http://localhost:8080/api/users" # 更新 curl -X PUT "http://localhost:8080/api/users/1?name=张三丰" # 删除 curl -X DELETE "http://localhost:8080/api/users/1"第六章:常见问题与注意事项queryForObject 空结果抛异常:需捕获 EmptyResultDataAccessException 或改用 query 方法加判断。BeanPropertyRowMapper 要求属性有 setter:否则映射会忽略该列。批量插入时数据库限制:例如 HSQLDB 默认批量最大 100 条,可分批调用 batchUpdate。事务失效场景:同一个类内非 @Transactional 方法调用 @Transactional 方法(无代理)方法被 private 修饰异常被 try-catch 吞掉且没有重新抛出使用 KeyHolder 时:必须通过 Statement.RETURN_GENERATED_KEYS 创建 PreparedStatement。附录:JdbcTemplate 方法速查表方法名作用适用场景queryForObject单行,返回对象根据主键查询、聚合函数query多行,返回 List列表查询、分页update单条 DML插入/更新/删除update + KeyHolder插入并返回主键获取自增 IDbatchUpdate批量 DML批量导入数据execute(String)DDL/存储过程建表、清空表execute(ConnectionCallback)原生 JDBC 操作特殊需求、存储过程---。
2026年06月03日
12 阅读
0 评论
1 点赞
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日
5 阅读
0 评论
1 点赞
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日
11 阅读
0 评论
1 点赞
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日
10 阅读
0 评论
1 点赞
1
2
3
4