关于注解的作用、以及自定义注解的使用和场景
一、注解的作用
- 注解是一种元数据形式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
- 注解用来修饰,类、方法、变量、参数、包。
- 注解不会对所修饰的代码产生直接的影响。
二、创建自定义注解
2.1 基本定义
- 首先使用 @interface声明注解名称
- 然后,使用@Retention,@Target等元注解标注注解的生命周期和作用元素
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface FirstAnnotation{
}
2.2 示例 – 类级别的注解
定义注解
@Target(ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Custom1 {
String[] values();
}
测试注解
@Custom1(values = {
"V1","V2"})
public class MainTest {
@Test
public void t1(){
MainTest t = new MainTest();
boolean present = t.getClass().isAnnotationPresent(Custom1.class);
if (present) {
Custom1 ca = t.getClass().getAnnotation(Custom1.class);
if (ca != null) {
for (String value : ca.values()){
System.out.println(value);
}
}
}
}
}
控制台打印
V1
V2
2.3 示例 – 方法级别的注解
定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CanRun {
}
使用注解,方法之上
public class AnnotationRunner {
public void method1() {
System.out.println("method1");
}
@CanRun
public void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
@CanRun
public void method5() {
System.out.println("method4");
}
}
测试注解
public class MainTest {
@Test
public void t1(){
AnnotationRunner runner = new AnnotationRunner();
Method[] methods = runner.getClass().getMethods();
for (Method method : methods) {
CanRun annos = method.getAnnotation(CanRun.class);
if (annos != null) {
try {
method.invoke(runner);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
控制台打印
method2
method4
2.4 示例 – 字段属性级别的注解
定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CanRun {
}
使用注解,方法之上
public class AnnotationRunner {
public void method1() {
System.out.println("method1");
}
@CanRun
public void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
@CanRun
public void method5() {
System.out.println("method4");
}
}
测试注解
public class MainTest {
@Test
public void t1(){
AnnotationRunner runner = new AnnotationRunner();
Method[] methods = runner.getClass().getMethods();
for (Method method : methods) {
CanRun annos = method.getAnnotation(CanRun.class);
if (annos != null) {
try {
method.invoke(runner);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
控制台打印
method2
method4
三、注解的使用场景
3.1 JDK中的常用注解
还有,几个元注解,用于定义注解的
@Retention: 表示对它所标记的元素的生命周期(参考的范围看RetentionPolicy枚举类)
@Target: 表示标记定义的注解可以和什么目标元素绑定
@Inherited: 表示该注解可以被继承
@Document: 表示该注解可以被生成API文档
3.2 Spring中的常用注解
@RestController
@RequestMapping
@PostMapping
@RequestBody
…
这里是Spring中所有的注解链接
3.3 自定义注解的使用场景
3.3.1 AOP + @CusLog,通过自定义注解实现 >>操作日志 --> DB数据库
- 首先是有日志的Model定义,Dao层的save实现,这里不介绍太多了…
@Data
@Entity
@Table(name = "operate_log")
public class OperateLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String recordId; // 操作数据id
private String module;// 模块名称
private String business;// 业务方法描述
private String opType;// 操作类型
private Long userId;// 操作人
private String userName;// 操作人姓名
private String params;// 操作数据
/** * 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
(2) - 定义注解
@Target({
ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SystemLog {
/** * 操作描述 业务名称business * * @return */
String description() default "";
/** * 操作模块 * * @return */
OperateModule module();
/** * 操作类型 create modify delete * * @return */
OperateType opType();
/** * 主键入参参数名称,入参中的哪个参数为主键 * * @return */
String primaryKeyName() default "";
/** * 主键在参数中的顺序,从0开始,默认0 */
int primaryKeySort() default 0;
/** * 主键所属类 * * @return */
Class<?> primaryKeyBelongClass();
}
(3) - 使用注解(处理注解),AOP / Aspect + 反射(通过注解获取对象的字段,方法等信息)
@Aspect
@Component
@Slf4j
public class WebLogAspect {
@Autowired
private OperateLogRepository logRepository;
@Pointcut("execution(public * demo1.controller..*.*(..)) "
+ " && @annotation(demo1.log.SystemLog)")
public void webLog() {
}
@Around("webLog()")
public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
// log.info("环绕通知开始........");
// String username = SecurityUtils.getCurrentUserName();
String username = "ss";
// 入参 value
Object[] args = joinPoint.getArgs();
// 入参名称
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map<String, Object> params = new HashMap<>();
// 获取所有参数对象
for (int i = 0; i < args.length; i++) {
if (null != args[i]) {
if (args[i] instanceof BindingResult) {
params.put(paramNames[i], "bindingResult");
} else {
params.put(paramNames[i], args[i]);
}
} else {
params.put(paramNames[i], "无");
}
}
Map<String, Object> values = getControllerAnnotationValue(joinPoint);
String opType = values.get("opType").toString();
String module = values.get("module").toString();
String business = values.get("business").toString();
String primaryKeyName = values.get("primaryKeyName").toString();
int primaryKeySort = Integer.parseInt(values.get("primaryKeySort").toString());
Class<?> primaryKeyBelongClass = (Class<?>) values.get("primaryKeyBelongClass");
Object primaryKeyValue = null;
if (StringUtils.isNotBlank(primaryKeyName) && OperateType.valueOf(opType) == OperateType.delete) {
primaryKeyValue = args[primaryKeySort];
}
// 切面返回值
Object returnValue = joinPoint.proceed();
if (OperateType.valueOf(opType) != OperateType.delete) {
// 主要目的是为了获取方法保存成功的返回数据格式,以获取保存成功的数据id
// 此处要限制新增返回成功的格式,return ok("具体操作信息", new MapBean("此处为实体主键属性名称",
// primaryKeyValue));
// 不然自己也可定义格式,进行拆分获取主键
primaryKeyName = getPrimaryKeyName(primaryKeyBelongClass).toString();
primaryKeyValue = ReflectUtils.dynamicGet(returnValue, primaryKeyName);
if (primaryKeyValue == null || primaryKeyValue.toString().equals("")) {
// 处理service层返回ResultBean
Object result = ReflectUtils.dynamicGet(returnValue, "result");
if (result != null) {
primaryKeyValue = ReflectUtils.dynamicGet(result, primaryKeyName);
} else {
primaryKeyValue = args[primaryKeySort];
}
}
}
OperateLog operateLog = new OperateLog();
//
if (JSONUtil.toJsonStr(params).length() <= 2000) {
operateLog.setData(JSONUtil.toJsonStr(params));
}
operateLog.setUserId(1L);
operateLog.setUserName(username == null ? "系统" : username);
operateLog.setModule(module);
operateLog.setOpType(opType);
operateLog.setBusiness(business);
String recordId = null;
if (primaryKeyValue instanceof Object[]) {
recordId = Arrays.toString((Object[]) primaryKeyValue);
} else {
recordId = primaryKeyValue.toString();
}
operateLog.setRecordId(recordId);
operateLog.setCreateTime(new Date());
logRepository.save(operateLog);
log.info(">>>操作日志:{}", operateLog);
return returnValue;
}
/** * 获取class的主键字段名 主键值 */
@SuppressWarnings({
"rawtypes", "unchecked"})
private static Object getPrimaryKeyName(Class<?> clazz) throws Exception {
Object param = null;
// 递归获取父子类所有的field
Class tempClass = clazz;
// 当父类为null的时候说明到达了最上层的父类(Object类
while (tempClass != null && !StringUtils.equals(tempClass.getName().toLowerCase(), "java.lang.object")) {
Field[] fields = tempClass.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
// boolean类型不必判断,因实体里包含boolean类型的属性,getter方法是以is开头,不是get开头
if (field.getType().equals(Boolean.class) || field.getType().getName().equals("boolean")) {
continue;
}
if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
continue;
}
String getterMethod = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method method = tempClass.getDeclaredMethod(getterMethod);
// 字段上是否存在@Id注解
Object primaryAnnotation = field.getAnnotation(Id.class);// for hibernate
if (primaryAnnotation == null)
primaryAnnotation = field.getAnnotation(org.springframework.data.annotation.Id.class);// for spring
// data
// getter方法上是否存在@Id注解
if (primaryAnnotation == null)
primaryAnnotation = method.getAnnotation(Id.class);
if (primaryAnnotation == null)
primaryAnnotation = method.getAnnotation(org.springframework.data.annotation.Id.class);
// 存在@Id注解,则说明该字段为主键
if (primaryAnnotation != null) {
/* String primaryKeyName = field.getName(); */
param = field.getName();
break;
}
}
if (param != null && StringUtils.isNotBlank(param.toString())) {
break;
}
// 得到父类赋值给tempClass
tempClass = tempClass.getSuperclass();
}
if (param == null) {
throw new Exception(clazz.getName() + "实体,未设置主键");
}
return param;
}
/** * 获取@SystemLog 注解上信息 * * @param joinPoint * @return map * @throws Exception */
@SuppressWarnings("rawtypes")
public static Map<String, Object> getControllerAnnotationValue(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
Map<String, Object> map = new HashMap<>();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] classes = method.getParameterTypes();
if (classes.length == arguments.length) {
// 取入参数据
String description = method.getAnnotation(SystemLog.class).description();
String module = method.getAnnotation(SystemLog.class).module().name();
String opType = method.getAnnotation(SystemLog.class).opType().name();
String primaryKeyName = method.getAnnotation(SystemLog.class).primaryKeyName();
int primaryKeySort = method.getAnnotation(SystemLog.class).primaryKeySort();
Class<?> clazz = method.getAnnotation(SystemLog.class).primaryKeyBelongClass();
map.put("module", module);
map.put("opType", opType);
map.put("business", description);
map.put("primaryKeyName", primaryKeyName);
map.put("primaryKeySort", primaryKeySort);
map.put("primaryKeyBelongClass", clazz);
break;
}
}
}
return map;
}
@AfterReturning("webLog()")
public void doAfterReturning(JoinPoint joinPoint) {
// 处理完请求,返回内容
// log.info("WebLogAspect.doAfterReturning()");
}
}