java注解详解及自定义注解实现

java在jdk1.5的时候引入了注解的概念引入的一种注释机制,官方对于注解的描述如下:

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

翻译成中文:

注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。

从官方的描述中我们可以得出以下结论

  1. 注解是一种元数据形式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
  2. 注解用来修饰,类、方法、变量、参数、包。
  3. 注解不会对所修饰的代码产生直接的影响。

一、元注解

java中的注解分成两部分:元注解和自定义注解。元注解,元即“原”,有原始的意思。java中为我们提供了4个原始的注解,分别

@Retention标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented标记这些注解是否包含在用户文档中。
@Target标记这个注解应该是哪种 Java 成员。
@Inherited标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)。

1.1、@Retention

注解是有生命的,他的生命周期包含源码阶段、编译阶段和运行阶段。而@Retention注解就是标识自定义注解的生命周期的,他的取值存在于java.lang.annotation.RetentionPolicy文件中

从源码中可以看出@Retention有三个取值:

  1. SOURCE:在源文件中有效,编译过程中会被忽略;
  2. CLASS:随源文件一起编译在class文件中,运行时忽略;
  3. RUNTIME:在运行时有效。

我们在解释一下这三个值详细的意思

RetentionPolicy.SOURCE当一个注解使用了@Retention注解,他的值为SOURCE时,表示这个注解只存在于源码中,在编译和运行阶段这个注解不会出现。这种方式常用于注释时,即只给阅读java文件时使用。
RetentionPolicy.CLASS(默认)当一个注解使用了@Retention注解,他的值为SOURCE时,表示这个注解在编译时被解析,可以在这个阶段做一些动作,一旦编译完成就会被JVM所忽略,也不会在运行期读取到。这种方式常用于在编译时需要做一些操作。
RetentionPolicy.RUNTIME当一个注解使用了@Retention注解,他的值为RUNTIME时,表示这个注解可以在运行时期被执行和获取,从而执行相应的代码和获取对应的属性。通常情况下

 1.2、@Documented

@Documented注解的作用是使得被指向的注解能随着java文件一起生成javadoc文档。

1.3、@Target

@Target注解的作用是用来标识自定义的注解的使用对象的,他的可选的属性值在java.lang.annotation.ElementType下,具体为:

TYPE
类、接口(包注解类型)、枚举
FIELD
属性
METHOD方法
PARAMETER方法形式参数
CONSTRUCTOR构造方法
LOCAL_VARIABLE局部变量
ANNOTATION_TYPE注解
PACKAGE
TYPE_PARAMETERjdk1.8新增属性。
TYPE_USEjdk1.8新增属性。

1.4、@Inherited

@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。

注意:@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

二、自定义注解+aop实现

在实际开发过程我们经常需要对某些特定的方法、接口做增强、拦截、打印日志、记录请求等操作,这个时候我们就可以使用一个自定义注解+aop切面来实现。

2.1、引入依赖

	<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>2.2.2.RELEASE</spring.version>
    </properties>

    <dependencies>
        <!--引入spring boot包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--引入web包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--引入aop包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.2、创建自定义注解

package com.sxx.web.annotation;

import com.sxx.web.enums.HandEnum;

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 PrintLog {

    /**
     * 接口名称
     */
    String value() default "";

    /**
     * 用户行为
     *
     * @return
     */
    HandEnum handle();

    /**
     * 是否将当前日志记录到数据库中
     */
    boolean save() default true;
}

注意: @interface表示这个这个注解,在编译之后会自动继承java.lang.annotation.Annotation

2.3、定义注解中枚举值(不需要可以忽略)

public enum HandEnum {

    SAVE("SAVE","保存"),
    UPDATE("UPDATE","更新"),
    SELECT("SELECT","查询"),
    DELETE("DELETE","删除");

    private String name;
    private String desc;
    HandEnum(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
}

 2.4、aop定义切面(重要)

package com.sxx.web.aspect;
import com.sxx.web.annotation.PrintLog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

@Aspect//使用该注解表明这是个切面
@Component//交给spring管理
public class PrintLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(PrintLogAspect.class);

    @Autowired
    HttpServletRequest request;
    
    //如果aroundMethod需要注入@PrintLog注解,则@annotation的值指向aroundMethod注解参数的形式参数
    //如果不需要注入@PrintLog对象,则@annotation的值为@PrintLog注解的全限定名
    @Around(value = "@annotation(printLog)")
    public Object aroundMethod(ProceedingJoinPoint point, PrintLog printLog) {
        try {
            String ip = request.getRemoteAddr();
            LOGGER.info(String.format("请求ip=%s,请求接口=%s,用户行为=%s", ip, printLog.value(), printLog.handle().getDesc()));
            //执行对象方法
            Object proceed = point.proceed();
            LOGGER.info(String.format("执行结果为=%s", proceed));
            //必须将原来方法的结果返回,除非你需要改变返回结果
            return proceed;
        } catch (Throwable throwable) {
            LOGGER.error("发生异常,异常=" + throwable.getMessage());
        }
        return "1222";
    }
}

2.5、定义controller

package com.sxx.web.controller;
import com.sxx.web.annotation.PrintLog;
import com.sxx.web.enums.HandEnum;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @RequestMapping("/user/info")
    @PrintLog(value = "/user/info", handle = HandEnum.SELECT)
    public String getUserInfo(String name){
        return "用户"+ name + "登录成功";
    }
}

2.6、执行结果

 

 

2.7、注意事项

 

        1、PrintLogAspect类中使用@Aspect是标注该类是切面类,有切面类必须要定义连接点,但是我在@Around中使用了@annotation(printLog),@annotation()表示匹配注解,值指向的是aroundMethod方法参数中注解的形式参数名,在实际执行中会自动去匹配定义连接点,因此不需要在此定义连接点,需要连接点的请看2.8章;

        2、使用@Around注解时,必须要调用proceed()方法,这步是调用被@PrintLog注解标注的方法(即目标方法),如果不调用,目标方法不会执行;

        3、使用@Around注解时,需要返回目标方法的结果,除非目标方法没有返回值可以不用处理,另一种情况是你不需要目标方法的返回值或你需要返回其他值。

2.8、常用其他情形

2.8.1、定义连接点

package com.sxx.web.aspect;
import com.sxx.web.annotation.PrintLog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

@Aspect//使用该注解表明这是个切面
@Component//交给spring管理
public class PrintLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(PrintLogAspect.class);

    @Autowired
    HttpServletRequest request;

    //定义aop连接点@annotation指向自定义注解,值为printLogCut的形式参数名
    //如果不需要引入PrintLog,则@Pointcut的值为@annotation(自定义注解全限定名)
    @Pointcut(value = "@annotation(printLog)")
    public void printLogCut(PrintLog printLog) {

    }
    //@Around环绕通知,value值为连接点方法+形参
    //如果不需要引入PrintLog,则@Around值为printLogCut(),即保证和连接点方法printLogCut一致
    //除此之外printLogCut(printLog)中的printLog也必须和aroundMethod的形参一致
    @Around(value = "printLogCut(printLog)")
    public Object aroundMethod(ProceedingJoinPoint point, PrintLog printLog) {
        try {
            String ip = request.getRemoteAddr();
            LOGGER.info(String.format("请求ip=%s,请求接口=%s,用户行为=%s", ip, printLog.value(), printLog.handle().getDesc()));
            //执行对象方法
            Object proceed = point.proceed();
            LOGGER.info(String.format("执行结果为=%s", proceed));
            //必须将原来方法的结果返回,除非你需要改变返回结果
            return proceed;
        } catch (Throwable throwable) {
            LOGGER.error("发生异常,异常=" + throwable.getMessage());
        }
        //异常时修改返回结果为1222
        return "1222";
    }
}

2.8.2、定义其他通知

package com.sxx.web.aspect;
import com.sxx.web.annotation.PrintLog;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

@Aspect//使用该注解表明这是个切面
@Component//交给spring管理
public class PrintLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(PrintLogAspect.class);

    @Autowired
    HttpServletRequest request;

    //定义aop连接点@annotation指向自定义注解,值为printLogCut的形式参数名
    //如果不需要引入PrintLog,则@Pointcut的值为@annotation(自定义注解全限定名)
    @Pointcut(value = "@annotation(printLog)")
    public void printLogCut(PrintLog printLog) {

    }
    //定义aop连接点@annotation指向自定义注解,值为printLogCut的形式参数名
    //如果不需要引入PrintLog,则@Around值为printLogCut(),即保证和连接点方法printLogCut一致
    //除此之外printLogCut(printLog)中的printLog也必须和aroundMethod的形参一致
    @Before(value = "printLogCut(printLog)")
    public void aroundMethod(JoinPoint point, PrintLog printLog) {
        try {
            String ip = request.getRemoteAddr();
            LOGGER.info(String.format("请求ip=%s,请求接口=%s,用户行为=%s", ip, printLog.value(), printLog.handle().getDesc()));
        } catch (Throwable throwable) {
            LOGGER.error("发生异常,异常=" + throwable.getMessage());
        }
    }
}

三、自定义注解+反射实现

复用2.5的controller,测试类如下

public class UserControllerTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserControllerTest.class);

    @Test
    public void test(){
        try {
            Class<?> aClass = Class.forName("com.sxx.web.controller.UserController");
            Method method = aClass.getMethod("getUserInfo", String.class);
            PrintLog printLog = method.getAnnotation(PrintLog.class);
            LOGGER.info(String.format("请求接口=%s,用户行为=%s", printLog.value(), printLog.handle().getDesc()));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

打印结果如下:

其实自定义注解在实际开发中用处很多,不过不管怎么用,其过程都是先定义注解,然后通过一系列过程拿到目标方法,当然这些过程可以是aop、反射,也可以是其他的,拿到目标方法之后在进行一系列的操作,如果自定义注解运用好的话可以大大的减少代码量和工作量,使得代码也看起来更加的简洁,后期维护也比较容易。

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值