JAVA中的注解和泛型 - 指南
目录
JAVA注解介绍
概念
注解是 JDK 5.0 引入的一种元数据机制,用来对代码进行标注。它不会影响程序的实际逻辑,但可被编译器、开发工具、框架或运行时反射机制读取,用于生成代码、配置信息、提供指令等。
简单理解:注解就是“给代码贴标签”,再由工具读取标签做相应处理。
注解的本质
Java 中的注解是一个接口,所有注解都是 java.lang.annotation.Annotation 接口的子类型。注解可以添加在类、方法、变量、参数等元素上。
我们可以通过反射获取注解的信息:
Class clazz = MyClass.class;if (clazz.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class); System.out.println(annotation.value());}
4种标准元注解
元注解的作用是负责注解其他注解。 Java5.0定义了 4个标准的 meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
@Target —— 说明注解可以应用于哪些 Java 成员上
@Target({ElementType.TYPE, ElementType.METHOD})
常见取值(ElementType):
TYPE: 类、接口、枚举FIELD: 成员变量METHOD: 方法PARAMETER: 参数CONSTRUCTOR: 构造方法LOCAL_VARIABLE: 局部变量ANNOTATION_TYPE: 注解PACKAGE: 包
@Retention —— 指定注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
常见取值(RetentionPolicy):
SOURCE:仅在源码中保留,编译后丢弃(如@Override)CLASS:编译时保留,运行时不可通过反射获取(默认值)RUNTIME:运行时仍可读取(用于框架反射,如 Spring、MyBatis)
@Documented —— 表示注解是否包含在 Javadoc 中
@Documented
@Inherited —— 注解是否可以被子类继承
@Inherited #仅对类有效:如果某个类使用了被 @Inherited 修饰的注解,那么其子类也会继承这个注解。
自定义注解
/* * 需要登录才可以访问对应的资源 */@Target(ElementType.METHOD) // 注解作用于方法上@Retention(RetentionPolicy.RUNTIME) // 注解在运行时仍然可用@Documentedpublic @interface RequireLogin { String role() default "user";}
public class AnnotationTest { @RequireLogin(role = "admin") public void deleteUser() { System.out.println("删除用户"); } @RequireLogin public void queryUser() { System.out.println("查询用户"); } public void publicMethod() { System.out.println("公共方法,不需要登录"); } public static void main(String[] args) { //获取注解的反射信息 AnnotationTest test = new AnnotationTest(); // 获取类的注解 Class clazz = AnnotationTest.class; Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(RequireLogin.class)) { RequireLogin annotation = method.getAnnotation(RequireLogin.class); System.out.println("方法: " + method.getName() + ", 角色: " + annotation.role()); } } }}
注解一般需要配置拦截器使用实现对应的业务场景,例如日志、权限等功能实现,也可以配合AOP使用来实现一些特定的场景需求
以下例子是我自己写的一个实现登录日志记录的案例:
注解
/** * 自定义操作日志注解 * @author hyh */@Target({ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行@Documentedpublic @interface LoginLog { String businessName() default ""; // 操作名称}
AOP切面
这里对应数据库操作的类换成自己的
/** * 登录日志记录切面 * * @author hyh * @date 2024-06-012 */@Aspect@Componentpublic class LoginLogAspect { @Autowired private HttpServletRequest request; @Autowired private ISysLoginInforService sysLoginInforService; // 切点定义 只要包含 @LoginLog 注解的方法都会被拦截 @Pointcut("@annotation(com.hyh.ad.common.annotation.LoginLog)") public void operationLogPointCut() { } // 方法正常返回之后执行 afterReturningLogWrite 方法。 @AfterReturning(pointcut = "operationLogPointCut()", returning = "returnValue") public void afterReturningLogWrite(JoinPoint joinPoint, Object returnValue) { handleLog(joinPoint, null, returnValue); } //表示:方法抛出异常后执行 afterThrowingLogWrite 方法。 @AfterThrowing(pointcut = "operationLogPointCut()", throwing = "e") public void afterThrowingLogWrite(JoinPoint joinPoint, Throwable e) { handleLog(joinPoint, e, null); } //这个方法负责构建一个 SysLogininfor 登录日志对象,然后保存到数据库。 private void handleLog(JoinPoint joinPoint, Throwable e, Object returnValue) { // 创建日志对象 SysLogininfor sysLogininfor = new SysLogininfor(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); LoginLog log = method.getAnnotation(LoginLog.class); String username = ""; Object[] args = joinPoint.getArgs(); ObjectMapper objectMapper = new ObjectMapper(); String params = ""; try { params = objectMapper.writeValueAsString(args); } catch (JsonProcessingException jsonProcessingException) { jsonProcessingException.printStackTrace(); } // 获取User-Agent String userAgentString = request.getHeader("User-Agent"); UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString); // 获取操作系统和浏览器信息 OperatingSystem os = userAgent.getOperatingSystem(); Browser browser = userAgent.getBrowser(); // 获取参数里面的用户名 if (args.length > 0 && args[0] instanceof LoginBody) { LoginBody loginBody = (LoginBody) args[0]; username = loginBody.getUsername(); } else { username = "Unknown"; } String ipAddress = IpUtil.getIpAddr(request); sysLogininfor.setUserName(username); sysLogininfor.setIpaddr(ipAddress); sysLogininfor.setLoginLocation("内网IP"); // 或者通过IP地址来判断内外网 sysLogininfor.setOs(os.getName()); sysLogininfor.setBrowser(browser.getName()); //时间加上8小时 sysLogininfor.setLoginTime(LocalDateTime.now().plusHours(8)); // 设置状态 if (e != null) { sysLogininfor.setStatus("1"); sysLogininfor.setMsg(e.getMessage()); // 设置错误信息 } else { sysLogininfor.setStatus("0"); if (returnValue != null) { String returnValueString = returnValue.toString(); // 假设 returnValue 是包含 {msg=验证码已过期, code=201} 的对象 String msg = ""; // 使用正则表达式提取 msg 字段 Pattern pattern = Pattern.compile("msg=([^,}]*)"); Matcher matcher = pattern.matcher(returnValueString); if (matcher.find()) { msg = matcher.group(1); } // 设置 sysLogininfor 的 msg 字段 sysLogininfor.setMsg(msg); } else { sysLogininfor.setMsg("Operation completed successfully."); } } try { sysLoginInforService.insert(sysLogininfor); } catch (Exception exception) { exception.printStackTrace(); }}
应用的方法
/** * 用户名密码登录 * @param loginBody * 对象参数包括以下字段: * 用户名 名字或者手机号 * 密码 * 验证码 * uuid */ @ApiOperation(value = "通过用户名密码登陆 ") @PostMapping("/loginByPwd") @LoginLog(businessName = "用户登录") //注解 public AjaxResult loginByPassword(@RequestBody LoginBody loginBody) { return sysUserService.loginByPassword(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); }
泛型介绍
泛型的定义
泛型是 Java 中的一种语法机制,用于在类、接口和方法中参数化类型,让你在写代码时不用指定具体的数据类型,而在使用时再指定,从而提高代码的复用性、类型安全性 和 可读性。
JAVA泛型
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型。
泛型方法(<E>)
我们可以编写一个泛型方法,该方法在调用的时候可以接受不同的参数,根据传递的参数不同,编译器适当的处理每一个不同的调度。
/* * 编写泛型方法 */public class FanxingMethodTest { //通用泛型方法 public static void printArray(E[] inputArray) { for (E e : inputArray) { System.out.println("元素的类型是: " + e.getClass().getName() + ", 值: " + e); } } public static void main(String[] args) { printArray(new Integer[]{1, 2, 3, 4, 5}); printArray(new String[]{"Hello", "World", "Generics"}); printArray(new Double[]{1.1, 2.2, 3.3, 4.4, 5.5}); printArray(new Character[]{'A', 'B', 'C', 'D', 'E'}); }}
泛型类(<T>)
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
泛型类
/* * 泛型类 */@Datapublic class Box { private T data;}
泛型类测试
public class BoxTest { public static void main(String[] args) { //字符串类型 Box stringBox = new Box(); stringBox.setData("Hello, Generics!"); System.out.println("String Box contains: " + stringBox.getData()); //整数类型 Box integerBox = new Box(); integerBox.setData(123); System.out.println("Integer Box contains: " + integerBox.getData()); }}
类型通配符
类型通配符一般是使用 ?代替具体的类型参数。例如 List 在逻辑上是List,List 等所有 List<具体类型实参>的父类。
类型擦除
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的 List和 List等类型,在编译之后都会变成 List,JVM看到的只是 List,而由泛型附加的类型信息对 JVM来说是不可见的。类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。
运行时无法获取泛型类型
-
List list1 = new ArrayList<>();List list2 = new ArrayList<>();System.out.println(list1.getClass() == list2.getClass()); // true 无法使用
T创建数组-
public class Box { T[] array = new T[10]; // ❌ 编译报错} 不能用
instanceof判断泛型类型-
if (obj instanceof List) { // ❌ 编译错误}
浙公网安备 33010602011771号