What - 什么是注解?

       Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
  Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

How - 注解的作用

  1. 生成文档,通过代码里标识的元数据生成javadoc文档。
  2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

注解的常见分类

  • Java自带的标准注解,包括@Override@Deprecated@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
  • 元注解,元注解是用于定义注解的注解,包括@Retention@Target@Inherited@Documented@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
  • 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。

Java内置注解

查看代码
class A{
public void test() { }
} class B extends A{
/**
* 重载父类的test方法
*/
@Override
public void test() {
}
/**
* 被弃用的方法
*/
@Deprecated
public void oldMethod() {
}
/**
* 忽略告警
*
* @return
*/
@SuppressWarnings("rawtypes")
public List processList() {
List list = new ArrayList();
return list;
}
}

Java 1.5开始自带的标准注解,包括@Override@Deprecated@SuppressWarnings

  • @Override:表示当前的方法定义将覆盖父类中的方法
  • @Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
  • @SuppressWarnings:表示关闭编译器警告信息

我们再具体看下这几个内置注解,同时通过这几个内置注解中的元注解的定义来引出元注解。

@Override

我们先来看一下这个注解类型的定义:

查看代码

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。这个注解的作用我们大家都不陌生,那就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

@Deprecated

这个注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。

@SuppressWarnings

这个注解我们也比较常用到,先来看下它的定义:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:

参数 作用 原描述
all 抑制所有警告 to suppress all warnings
boxing 抑制装箱、拆箱操作时候的警告 to suppress warnings relative to boxing/unboxing operations
cast 抑制映射相关的警告 to suppress warnings relative to cast operations
dep-ann 抑制启用注释的警告 to suppress warnings relative to deprecated annotation
deprecation 抑制过期方法警告 to suppress warnings relative to deprecation
fallthrough 抑制确在switch中缺失breaks的警告 to suppress warnings relative to missing breaks in switch statements
finally 抑制finally模块没有返回的警告 to suppress warnings relative to finally block that don’t return
hiding 抑制与隐藏变数的区域变数相关的警告 to suppress warnings relative to locals that hide variable()
incomplete-switch 忽略没有完整的switch语句 to suppress warnings relative to missing entries in a switch statement (enum case)
nls 忽略非nls格式的字符 to suppress warnings relative to non-nls string literals
null 忽略对null的操作 to suppress warnings relative to null analysis
rawtype 使用generics时忽略没有指定相应的类型 to suppress warnings relative to un-specific types when using
restriction 抑制与使用不建议或禁止参照相关的警告 to suppress warnings relative to usage of discouraged or
serial 忽略在serializable类中没有声明serialVersionUID变量 to suppress warnings relative to missing serialVersionUID field for a serializable class
static-access 抑制不正确的静态访问方式警告 to suppress warnings relative to incorrect static access
synthetic-access 抑制子类没有按最优方法访问内部类的警告 to suppress warnings relative to unoptimized access from inner classes
unchecked 抑制没有进行类型检查操作的警告 to suppress warnings relative to unchecked operations
unqualified-field-access 抑制没有权限访问的域的警告 to suppress warnings relative to field access unqualified
unused 抑制没被使用过的代码的警告 to suppress warnings relative to unused code

元注解

上述内置注解的定义中使用了一些元注解(注解类型进行注解的注解类),在JDK 1.5中提供了4个标准的元注解:@Target@Retention@Documented@Inherited, 在JDK 1.8中提供了两个元注解 @Repeatable@Native

@Target

Target注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。

Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。
Java源码
public enum ElementType {

    TYPE, // 类、接口、枚举类

    FIELD, // 成员变量(包括:枚举常量)

    METHOD, // 成员方法

    PARAMETER, // 方法参数

    CONSTRUCTOR, // 构造方法

    LOCAL_VARIABLE, // 局部变量

    ANNOTATION_TYPE, // 注解类

    PACKAGE, // 可用于修饰:包

    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增

    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}

@Retention & @RetentionTarget

Reteniton注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。

Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。

public enum RetentionPolicy {

    SOURCE,    // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}

为了验证应用了这三种策略的注解类有何区别,分别使用三种策略各定义一个注解类做测试。

查看代码
@Retention(RetentionPolicy.SOURCE)
public @interface SourcePolicy { }
@Retention(RetentionPolicy.CLASS)
public @interface ClassPolicy { }
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimePolicy { }

用定义好的三个注解类分别去注解一个方法。

查看代码
public class RetentionTest {

	@SourcePolicy
public void sourcePolicy() {
} @ClassPolicy
public void classPolicy() {
} @RuntimePolicy
public void runtimePolicy() {
}
}

通过执行 javap -verbose RetentionTest命令获取到的RetentionTest 的 class 字节码内容如下。

查看代码
{
public retention.RetentionTest();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0 public void sourcePolicy();
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 7: 0 public void classPolicy();
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 11: 0
RuntimeInvisibleAnnotations:
0: #11() public void runtimePolicy();
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 15: 0
RuntimeVisibleAnnotations:
0: #14()
}

从 RetentionTest 的字节码内容我们可以得出以下两点结论:

  • 编译器并没有记录下 sourcePolicy() 方法的注解信息;
  • 编译器分别使用了 RuntimeInvisibleAnnotationsRuntimeVisibleAnnotations 属性去记录了classPolicy()方法 和 runtimePolicy()方法 的注解信息;

@Documented

Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

以下代码在使用Javadoc工具可以生成@TestDocAnnotation注解信息。

查看代码
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target; @Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestDocAnnotation { public String value() default "default";
}
@TestDocAnnotation("myMethodDoc")
public void testDoc() { }

@Inherited

Inherited注解的作用:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

我们来测试下这个注解:

  • 定义@Inherited注解:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestInheritedAnnotation {
String [] values();
int number();
}
  • 使用这个注解
查看代码
@TestInheritedAnnotation(values = {"value"}, number = 10)
public class Person {
} class Student extends Person{
@Test
public void test(){
Class clazz = Student.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.toString());
}
}
}
  • 输出
xxxxxxx.TestInheritedAnnotation(values=[value], number=10)

即使Student类没有显示地被注解@TestInheritedAnnotation,但是它的父类Person被注解,而且@TestInheritedAnnotation@Inherited注解,因此Student类自动有了该注解。

@Repeatable (Java8)

什么是重复注解

允许在同一申明类型(类,属性,或方法)的多次使用同一个注解

JDK8之前

java 8之前也有重复使用注解的解决方案,但可读性不是很好,比如下面的代码:

JDK1.8之前
public @interface Authority {
String role();
} public @interface Authorities {
Authority[] value();
} public class RepeatAnnotationUseOldVersion { @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
public void doSomeThing(){
}
}

由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解。

Jdk8重复注解

我们再来看看java 8里面的做法:

JDK1.8
@Repeatable(Authorities.class)
public @interface Authority {
String role();
} public @interface Authorities {
Authority[] value();
} public class RepeatAnnotationUseNewVersion {
@Authority(role="Admin")
@Authority(role="Manager")
public void doSomeThing(){ }
}

不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点

总结

JEP120没有太多内容,是一个小特性,仅仅是为了提高代码可读性。这次java 8对注解做了2个方面的改进(JEP 104,JEP120),相信注解会比以前使用得更加频繁了

@Native (Java8)

使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可

注解与反射接口

定义注解后,如何获取注解中的内容呢?反射包java.lang.reflect下的AnnotatedElement接口提供这些方法。这里注意:只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的方法来访问Annotation信息。我们看下具体的先关接口

  • boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)

判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。注意:此方法会忽略注解对应的注解容器。

  • <T extends Annotation> T getAnnotation(Class<T> annotationClass)

返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

  • Annotation[] getAnnotations()

返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组。

  • <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)

返回该程序元素上存在的、指定类型的注解数组。没有注解对应类型的注解时,返回长度为0的数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。getAnnotationsByType方法与 getAnnotation的区别在于,getAnnotationsByType会检测注解对应的重复注解容器。若程序元素为类,当前类上找不到注解,且该注解为可继承的,则会去父类上检测对应的注解。

  • <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)

返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释。如果没有注释直接存在于此元素上,则返回null

  • <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)

返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释

  • Annotation[] getDeclaredAnnotations()

返回直接存在于此元素上的所有注解及注解对应的重复注解容器。与此接口中的其他方法不同,该方法将忽略继承的注解。如果没有注释直接存在于此元素上,则返回长度为零的一个数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。

自定义注解

当我们理解了内置注解, 元注解和获取注解的反射接口后,我们便可以开始自定义注解了。这个例子我把上述的知识点全部融入进来, 代码很简单:

定义注解
package com.pdai.java.annotation;

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 MyMethodAnnotation { public String title() default ""; public String description() default ""; }
使用注解
package com.pdai.java.annotation;

import java.io.FileNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; public class TestMethodAnnotation { @Override
@MyMethodAnnotation(title = "toStringMethod", description = "override toString method")
public String toString() {
return "Override toString method";
} @Deprecated
@MyMethodAnnotation(title = "old static method", description = "deprecated old static method")
public static void oldMethod() {
System.out.println("old method, don't use it.");
} @SuppressWarnings({"unchecked", "deprecation"})
@MyMethodAnnotation(title = "test method", description = "suppress warning static method")
public static void genericsTest() throws FileNotFoundException {
List l = new ArrayList();
l.add("abc");
oldMethod();
}
}
  • 用反射接口获取注解信息

在TestMethodAnnotation中添加Main方法进行测试:

查看代码
public static void main(String[] args) {
try {
// 获取所有methods
Method[] methods = TestMethodAnnotation.class.getClassLoader()
.loadClass(("com.pdai.java.annotation.TestMethodAnnotation"))
.getMethods(); // 遍历
for (Method method : methods) {
// 方法上是否有MyMethodAnnotation注解
if (method.isAnnotationPresent(MyMethodAnnotation.class)) {
try {
// 获取并遍历方法上的所有注解
for (Annotation anno : method.getDeclaredAnnotations()) {
System.out.println("Annotation in Method '"
+ method + "' : " + anno);
} // 获取MyMethodAnnotation对象信息
MyMethodAnnotation methodAnno = method
.getAnnotation(MyMethodAnnotation.class); System.out.println(methodAnno.title()); } catch (Throwable ex) {
ex.printStackTrace();
}
}
}
} catch (SecurityException | ClassNotFoundException e) {
e.printStackTrace();
}
}
  • 测试的输出
结果
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @java.lang.Deprecated()
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @com.pdai.java.annotation.MyMethodAnnotation(title=old static method, description=deprecated old static method)
old static method
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.genericsTest() throws java.io.FileNotFoundException' : @com.pdai.java.annotation.MyMethodAnnotation(title=test method, description=suppress warning static method)
test method
Annotation in Method 'public java.lang.String com.pdai.java.annotation.TestMethodAnnotation.toString()' : @com.pdai.java.annotation.MyMethodAnnotation(title=toStringMethod, description=override toString method)
toStringMethod

Why - 注解的实现

注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。

注解的应用场景

配置化到注解化 - 框架的演进

Spring 框架 配置化到注解化的转变。

继承实现到注解实现 - Junit3到Junit4

一个模块的封装大多数人都是通过继承和组合等模式来实现的,但是如果结合注解将可以极大程度提高实现的优雅度(降低耦合度)。而Junit3 到Junit4的演化就是最好的一个例子。

被测试类
public class HelloWorld {

 	public void sayHello(){
System.out.println("hello....");
throw new NumberFormatException();
} public void sayWorld(){
System.out.println("world....");
} public String say(){
return "hello world!";
} }
  • Junit 3 实现UT

通过继承 TestCase来实现,初始化是通过Override父类方法来进行,测试方式通过test的前缀方法获取。

查看代码
public class HelloWorldTest extends TestCase{
private HelloWorld hw; @Override
protected void setUp() throws Exception {
super.setUp();
hw=new HelloWorld();
} //1.测试没有返回值
public void testHello(){
try {
hw.sayHello();
} catch (Exception e) {
System.out.println("发生异常.....");
} }
public void testWorld(){
hw.sayWorld();
}
//2.测试有返回值的方法
// 返回字符串
public void testSay(){
assertEquals("测试失败", hw.say(), "hello world!");
}
//返回对象
public void testObj(){
assertNull("测试对象不为空", null);
assertNotNull("测试对象为空",new String());
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
hw=null;
}
}
  • Junit 4 实现UT

通过定义@Before,@Test,@After等等注解来实现。

JUnit4
public class HelloWorldTest {
private HelloWorld hw; @Before
public void setUp() {
hw = new HelloWorld();
} @Test(expected=NumberFormatException.class)
// 1.测试没有返回值,有别于junit3的使用,更加方便
public void testHello() {
hw.sayHello();
}
@Test
public void testWorld() {
hw.sayWorld();
} @Test
// 2.测试有返回值的方法
// 返回字符串
public void testSay() {
assertEquals("测试失败", hw.say(), "hello world!");
} @Test
// 返回对象
public void testObj() {
assertNull("测试对象不为空", null);
assertNotNull("测试对象为空", new String());
} @After
public void tearDown() throws Exception {
hw = null;
} }

这里我们发现通过注解的方式,我们实现单元测试时将更为优雅。如果你还期望了解Junit4是如何实现运行的呢?可以看这篇文章:JUnit4源码分析运行原理

自定义注解和AOP - 通过切面实现解耦

最为常见的就是使用Spring AOP切面实现统一的操作日志管理,我这里找了一个开源项目中的例子(只展示主要代码),给你展示下如何通过注解实现解耦的。

自定义log注解
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 模块
*/
public String title() default ""; /**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER; /**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE; /**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
}
  • 实现日志的切面, 对自定义注解Log作切点进行拦截

即对注解了@Log的方法进行切点拦截,

实现日志切面
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class); /**
* 配置织入点 - 自定义注解的包路径
*
*/
@Pointcut("@annotation(com.xxx.aspectj.lang.annotation.Log)")
public void logPointCut() {
} /**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
} /**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
} protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
} // 获取当前的用户
User currentUser = ShiroUtils.getSysUser(); // *========数据库日志=========*//
OperLog operLog = new OperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ShiroUtils.getIp();
operLog.setOperIp(ip);
// 返回参数
operLog.setJsonResult(JSONObject.toJSONString(jsonResult)); operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (currentUser != null) {
operLog.setOperName(currentUser.getLoginName());
if (StringUtils.isNotNull(currentUser.getDept())
&& StringUtils.isNotEmpty(currentUser.getDept().getDeptName())) {
operLog.setDeptName(currentUser.getDept().getDeptName());
}
} if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(controllerLog, operLog);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
} /**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(Log log, OperLog operLog) throws Exception {
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(operLog);
}
} /**
* 获取请求的参数,放到log中
*
* @param operLog
* @param request
*/
private void setRequestValue(OperLog operLog) {
Map<String, String[]> map = ServletUtils.getRequest().getParameterMap();
String params = JSONObject.toJSONString(map);
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} /**
* 是否存在注解,如果存在就获取
*/
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod(); if (method != null)
{
return method.getAnnotation(Log.class);
}
return null;
}
}
  • 使用@Log注解

以一个简单的CRUD操作为例, 这里展示部分代码:每对“部门”进行操作就会产生一条操作日志存入数据库。

实例 - CRUD操作
@Controller
@RequestMapping("/system/dept")
public class DeptController extends BaseController {
private String prefix = "system/dept"; @Autowired
private IDeptService deptService; /**
* 新增保存部门
*/
@Log(title = "部门管理", businessType = BusinessType.INSERT)
@RequiresPermissions("system:dept:add")
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated Dept dept) {
if (UserConstants.DEPT_NAME_NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) {
return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
}
return toAjax(deptService.insertDept(dept));
} /**
* 保存
*/
@Log(title = "部门管理", businessType = BusinessType.UPDATE)
@RequiresPermissions("system:dept:edit")
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(@Validated Dept dept) {
if (UserConstants.DEPT_NAME_NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) {
return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
} else if(dept.getParentId().equals(dept.getDeptId())) {
return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
}
return toAjax(deptService.updateDept(dept));
} /**
* 删除
*/
@Log(title = "部门管理", businessType = BusinessType.DELETE)
@RequiresPermissions("system:dept:remove")
@GetMapping("/remove/{deptId}")
@ResponseBody
public AjaxResult remove(@PathVariable("deptId") Long deptId) {
if (deptService.selectDeptCount(deptId) > 0) {
return AjaxResult.warn("存在下级部门,不允许删除");
}
if (deptService.checkDeptExistUser(deptId)) {
return AjaxResult.warn("部门存在用户,不允许删除");
}
return toAjax(deptService.deleteDeptById(deptId));
} // ...
}

同样的,你也可以看到权限管理也是通过类似的注解(@RequiresPermissions)机制来实现的。所以我们可以看到,通过注解+AOP最终的目标是为了实现模块的解耦。

常见问题

注解支持继承吗?

注解是不支持继承的

不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口.

虽然反编译后发现注解继承了Annotation接口,请记住,即使Java的接口可以实现多继承,但定义注解时依然无法使用extends关键字继承@interface。

区别于注解的继承,被注解的子类继承父类注解可以用@Inherited: 如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

引用

https://pdai.tech/md/java/basic/java-basic-x-annotation.html
https://blog.csdn.net/javazejian/article/details/71860633
https://blog.csdn.net/qq_20009015/article/details/106038023

Java基础 - 注解详解的更多相关文章

  1. JAVA基础——异常详解

    JAVA异常与异常处理详解 一.异常简介 什么是异常? 异常就是有异于常态,和正常情况不一样,有错误出错.在java中,阻止当前方法或作用域的情况,称之为异常. java中异常的体系是怎么样的呢? 1 ...

  2. Java基础数据类型详解

    在Java中的数据类型一共有8种,大致分为整型(4个)浮点型(2个)布尔(1)字符(1个) 分类 类型 默认值 占用字节 范围 整型 byte 0 1 = 8 bit -2^7 - 2^7 short ...

  3. JAVA基础——内部类详解

    JAVA内部类详解 在我的另一篇java三大特性的封装中讲到java内部类的简单概要,这里将详细深入了解java内部类的使用和应用. 我们知道内部类可分为以下几种: 成员内部类 静态内部类 方法内部类 ...

  4. java基础:数组详解以及应用,评委打分案例实现,数组和随机数综合,附练习案列

    1.数组 1.1 数组介绍 数组就是存储数据长度固定的容器,存储多个数据的数据类型要一致. 1.2 数组的定义格式 1.2.1 第一种格式 数据类型[] 数组名 示例: int[] arr;     ...

  5. Java基础 - 异常详解

    异常的层次结构 Throwable Throwable 是 Java 语言中所有错误与异常的超类. Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示 ...

  6. 【干货】用大白话聊聊JavaSE — ArrayList 深入剖析和Java基础知识详解(二)

    在上一节中,我们简单阐述了Java的一些基础知识,比如多态,接口的实现等. 然后,演示了ArrayList的几个基本方法. ArrayList是一个集合框架,它的底层其实就是一个数组,这一点,官方文档 ...

  7. java基础(十三)-----详解内部类——Java高级开发必须懂的

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 为什么要使用内部类 为什么要使用内部类?在<Think in java>中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能 ...

  8. Java基础——枚举详解

    前言: 在第一次学习面向对象编程时,我记得最深的一句话就是“万物皆对象”.于是我一直秉承着这个思想努力的学习着JAVA,直到学习到枚举(Enum)时,看着它颇为奇怪的语法……我一直在想,这TM是个什么 ...

  9. JAVA基础知识详解

    1. JVM是什么 JVM是Java Virtual Mechine的缩写.它是一种基于计算设备的规范,是一台虚拟机,即虚构的计算机. JVM屏蔽了具体操作系统平台的信息(显然,就像是我们在电脑上开了 ...

随机推荐

  1. MySQL安装,使用问题汇总

    一,安装问题 mysqld install时出现:The service already exists! 报错原因:以前机器上装过mysql没有卸载干净 解决方案: C:\windows\system ...

  2. dw中几个必须掌握的快捷键

    相信很多初学者,在使用软件制作网页的时候需要去软件操作界面点击按钮来实现编辑,现在给大家分享几个最常用到的快捷方式!这样能让大家在使用中更为方便,节约时间提高工作效率 加粗 Ctrl + B斜体 Ct ...

  3. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  4. libc++abi.dylib: terminating with uncaught exception of type NSException

    这是微信sdk注册时候报的错误 解决方法 选择Build Setting,在"Other Linker Flags"中加入"-Objc -all_load"

  5. 八大排序算法之直接插入排序(InsertionSort)

    常见的排序算法 今天复习[直接插入排序] 核心思想:有序数组中 找位置 -- 给无序数组第一个 找位置 ` public class InsertionSort { // 核心思想:有序数组中 找位置 ...

  6. 【外企测试面试、笔试】分享下历时8轮、30k+的外企面试全过程

    外企福利 薪酬体系完善(期权.股票等),定期薪酬市场调研,紧跟一线互联网大厂 加班很少很少 年假多,15-20天 国外免费旅游.旅游金 免费培训英语(还可能出国培训) 定期技术交流 免费零食 定期团建 ...

  7. 【C# 基础概念】静态常量和动态常量的区别

    C# 静态常量和动态常量的区别 C#中有两种常量类型分别为readonly(运行时常量)与const(编译时常量),readonly是变量的常量,const是字面量的常量本文将就这两种类型的不同特性进 ...

  8. win7重装系统过程关机 电脑开机黑屏 硬盘无法识别 无法使用u盘启动

    问题:win7重装系统中强制重启导致硬盘无法识别,开机后无法选择使用u盘启动盘启动,电脑黑屏,将硬盘拆掉可以使用u盘启动,使用SATA转接口在win7中有反应但无法识别 无法识别原因:重装系统过程中断 ...

  9. 十分钟掌握CSS基本内容

         (一) 定位      绝对定位和相对定位.其实用一句话说就是"子绝父相",理解这句话很多效果通过定位做出来的话都没问题了.一般平时用的都的情况都是子div设置为绝对定位 ...

  10. killall 、kill 、pkill 命令区别

    转至:https://zhuanlan.zhihu.com/p/87904563 killall 命令 Linux系统中的killall命令用于杀死指定名字的进程(kill processes by ...