1、AOP面向切面编程
1. @Target(ElementType.METHOD)
-
作用:指定自定义注解可以应用的目标范围。
-
参数:
ElementType
是一个枚举类,定义了注解可以应用的目标类型。-
ElementType.METHOD
表示该注解只能用于方法上。 -
其他常见的
ElementType
值:-
TYPE
:类、接口、枚举等。 -
FIELD
:字段(包括枚举常量)。 -
PARAMETER
:方法参数。 -
CONSTRUCTOR
:构造函数。 -
ANNOTATION_TYPE
:注解类型。 -
PACKAGE
:包。 -
等等。
-
-
示例:
@Target(ElementType.METHOD) public @interface MyAnnotation { String value() default ""; }
-
上面的
@MyAnnotation
注解只能用于方法上,如果尝试将其用于类或字段上,编译器会报错。
2. @Retention(RetentionPolicy.RUNTIME)
-
作用:指定自定义注解的生命周期,即注解在什么阶段有效。
-
参数:
RetentionPolicy
是一个枚举类,定义了注解的保留策略。-
RetentionPolicy.RUNTIME
:注解在运行时保留,可以通过反射机制读取。 -
RetentionPolicy.CLASS
:注解在编译时保留,但运行时不可见(默认行为)。 -
RetentionPolicy.SOURCE
:注解仅在源码阶段保留,编译后会被丢弃。
-
示例:
@Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value() default ""; }
-
上面的
@MyAnnotation
注解在运行时仍然有效,可以通过反射获取注解信息。
结合使用
通常,@Target
和 @Retention
会一起使用,以明确注解的作用范围和生命周期。
示例:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) // 该注解只能用于方法上 @Retention(RetentionPolicy.RUNTIME) // 该注解在运行时有效 public @interface MyAnnotation { String value() default ""; }
使用场景:
public class MyClass { @MyAnnotation(value = "This is a method") public void myMethod() { System.out.println("Hello, World!"); } }
通过反射获取注解信息:
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Method method = MyClass.class.getMethod("myMethod"); MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); if (annotation != null) { System.out.println("Annotation value: " + annotation.value()); } } }
输出:
Annotation value: This is a method
总结
-
@Target(ElementType.METHOD)
:指定注解只能用于方法上。 -
@Retention(RetentionPolicy.RUNTIME)
:指定注解在运行时仍然有效,可以通过反射读取。 -
这两个注解通常一起使用,用于定义自定义注解的行为和适用范围。
3、反射(Reflection)
是 Java 提供的一种强大的机制,允许程序在运行时动态地获取类的信息(如类名、方法、字段、构造函数等),并操作这些信息(如调用方法、访问字段、创建对象等)。反射的核心思想是“在运行时检查和修改程序的行为”。
反射的主要用途包括:
-
动态加载类。
-
动态创建对象。
-
动态调用方法。
-
动态访问和修改字段。
-
获取注解信息。
反射的核心类
Java 反射的核心类和接口位于 java.lang.reflect
包中,主要包括:
-
Class<T>
:表示一个类或接口,是反射的入口。 -
Method
:表示类的方法。 -
Field
:表示类的字段(成员变量)。 -
Constructor<T>
:表示类的构造函数。 -
Annotation
:表示注解。
反射的基本用法
1. 获取 Class
对象
Class
对象是反射的起点,可以通过以下方式获取:
-
Class.forName("全限定类名")
:通过类的全限定名获取。 -
对象.getClass()
:通过对象的实例获取。 -
类名.class
:通过类字面量获取。
示例:
Class<?> clazz = Class.forName("com.example.MyClass");
2. 创建对象
通过 Class
对象可以动态创建类的实例。
示例:
Class<?> clazz = Class.forName("com.example.MyClass"); Object obj = clazz.getDeclaredConstructor().newInstance();
3. 调用方法
通过 Method
对象可以动态调用类的方法。
示例:
Class<?> clazz = Class.forName("com.example.MyClass"); Object obj = clazz.getDeclaredConstructor().newInstance(); // 获取方法 Method method = clazz.getMethod("myMethod", String.class); // 调用方法 method.invoke(obj, "Hello, Reflection!");
4. 访问字段
通过 Field
对象可以动态访问和修改类的字段。
示例:
Class<?> clazz = Class.forName("com.example.MyClass"); Object obj = clazz.getDeclaredConstructor().newInstance(); // 获取字段 Field field = clazz.getDeclaredField("myField"); // 设置字段可访问(如果是私有字段) field.setAccessible(true); // 修改字段值 field.set(obj, "New Value"); // 获取字段值 Object value = field.get(obj); System.out.println(value);
5. 获取注解信息
通过反射可以获取类、方法、字段等元素上的注解信息。
示例:
Class<?> clazz = Class.forName("com.example.MyClass"); // 获取类上的注解 MyAnnotation classAnnotation = clazz.getAnnotation(MyAnnotation.class); if (classAnnotation != null) { System.out.println("Class Annotation Value: " + classAnnotation.value()); } // 获取方法上的注解 Method method = clazz.getMethod("myMethod"); MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class); if (methodAnnotation != null) { System.out.println("Method Annotation Value: " + methodAnnotation.value()); }
反射的优缺点
优点
-
灵活性:可以在运行时动态加载类、调用方法、访问字段,适合编写通用框架或工具。
-
扩展性:通过反射可以实现插件化架构,动态加载第三方类。
-
注解处理:反射是处理注解的基础,许多框架(如 Spring、MyBatis)都依赖反射来解析注解。
缺点
-
性能开销:反射操作比直接调用方法或访问字段要慢,因为涉及动态解析。
-
安全性问题:反射可以绕过访问控制(如访问私有字段或方法),可能导致安全问题。
-
代码可读性差:反射代码通常比较复杂,难以理解和维护。
反射的实际应用场景
-
框架开发:许多框架(如 Spring、Hibernate、MyBatis)都使用反射来实现依赖注入、动态代理、注解解析等功能。
-
单元测试:测试框架(如 JUnit)使用反射来调用测试方法。
-
动态加载类:在插件化架构中,通过反射动态加载第三方类。
-
序列化和反序列化:通过反射访问对象的字段,实现对象的序列化和反序列化。
示例代码
以下是一个完整的反射示例,展示了如何通过反射创建对象、调用方法和访问字段:
import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) throws Exception { // 获取 Class 对象 Class<?> clazz = Class.forName("com.example.MyClass"); // 创建对象 Object obj = clazz.getDeclaredConstructor().newInstance(); // 调用方法 Method method = clazz.getMethod("myMethod", String.class); method.invoke(obj, "Hello, Reflection!"); // 访问字段 Field field = clazz.getDeclaredField("myField"); field.setAccessible(true); // 设置私有字段可访问 field.set(obj, "New Value"); System.out.println("Field Value: " + field.get(obj)); } } class MyClass { private String myField = "Default Value"; public void myMethod(String message) { System.out.println("Method Invoked: " + message); } }
输出:
Method Invoked: Hello, Reflection! Field Value: New Value
总结
反射是 Java 中一种强大的机制,允许程序在运行时动态操作类和对象。虽然反射提供了极大的灵活性,但也带来了性能开销和安全性问题,因此应谨慎使用。在实际开发中,反射常用于框架开发、注解处理、动态加载类等场景。
4、idea快捷键ctrl + alt + b
快速转到接口实现方法。
5、实战
1 步骤一
自定义注解 AutoFill
进入到sky-server模块,创建com.sky.annotation包。
package com.sky.annotation;
import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
其中OperationType已在sky-common模块中定义
package com.sky.enumeration;
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
2 步骤二
自定义切面 AutoFillAspect
在sky-server模块,创建com.sky.aspect包。
package com.sky.aspect;
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
/重要
//可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解
log.info("开始进行公共字段自动填充...");
}
}
完善自定义切面 AutoFillAspect 的 autoFill 方法
package com.sky.aspect;
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
//获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3 步骤三
在Mapper接口的方法上加入 AutoFill 注解
以CategoryMapper为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作
package com.sky.mapper;
@Mapper
public interface CategoryMapper {
/**
* 插入数据
* @param category
*/
@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
" VALUES" +
" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
@AutoFill(value = OperationType.INSERT)
void insert(Category category);
/**
* 根据id修改分类
* @param category
*/
@AutoFill(value = OperationType.UPDATE)
void update(Category category);
}
同时,将业务层为公共字段赋值的代码注释掉。
1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。
2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。