一、概述


下面列举开发中常见的注解

  • @Override:用于标识该方法继承自超类, 当父类的方法被删除或修改了,编译器会提示错误信息(我们最经常看到的toString()方法上总能看到这货)
  • @Deprecated:表示该类或者该方法已经不推荐使用,已经过期了,如果用户还是要使用,会生成编译的警告
  • @SuppressWarnings:用于忽略的编译器警告信息
  • Junit测试:@Test
  • Spring的一些注解:@Controller、@RequestMapping、@RequestParam、@ResponseBody、@Service、@Component、@Repository、@Resource、@Autowire
  • Java验证的注解:@NotNull、@Email

Override.java的注解源码如下:

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

}

二:注解基本知识

1. 注解数据类型

注解是写在.java文件中,使用@interface作为关键字, 所以注解也是Java的一种数据类型,从广泛的定义来说,Class、Interface、Enum、Annotation都属于Class类型。

2. 元注解

在创建注解的时候,需要使用一些注解来描述自己创建的注解,就是写在@interface上面的那些注解,这些注解被称为元注解,如在Override中看到的@Target、@Retention等。下面列出一些元注解

  • @Documented: 用于标记在生成javadoc时是否将注解包含进去,可以看到这个注解和@Override一样,里面什么东西也没有。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {

}
  • @Target:用于定义注解可以在什么地方使用,默认可以在任何地方使用,也可以指定使用的范围,开发中将注解用在类上(如@Controller)、字段上(如@Autowire)、方法上(如@RequestMapping)、方法的参数上(如@RequestParam)等比较常见。
  • TYPE : 类、接口或enum声明
  • FIELD: 域(属性)声明
  • METHOD: 方法声明
  • PARAMETER: 参数声明
  • CONSTRUCTOR: 构造方法声明
  • LOCAL_VARIABLE:局部变量声明
  • ANNOTATION_TYPE:注释类型声明
  • PACKAGE: 包声明

Target.java


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,

/** Field declaration (includes enum constants) */
FIELD,

/** Method declaration */
METHOD,

/** Formal parameter declaration */
PARAMETER,

/** Constructor declaration */
CONSTRUCTOR,

/** Local variable declaration */
LOCAL_VARIABLE,

/** Annotation type declaration */
ANNOTATION_TYPE,

/** Package declaration */
PACKAGE,

/** Type parameter declaration */
TYPE_PARAMETER,

/** Use of a type */
TYPE_USE
}
  • @Inherited:允许子类继承父类中的注解,可以通过反射获取到父类的注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {

}
  • @Constraint:用于校验属性值是否合法
@Documented
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {
Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}
  • @Retention:注解的声明周期,用于定义注解的存活阶段,可以存活在源码级别、编译级别(字节码级别)、运行时级别
  • SOURCE:源码级别,注解只存在源码中,一般用于和编译器交互,用于检测代码。如@Override, @SuppressWarings。
  • CLASS:字节码级别,注解存在于源码和字节码文件中,主要用于编译时生成额外的文件,如XML,Java文件等,但运行时无法获得。 如mybatis生成实体和映射文件,这个级别需要添加JVM加载时候的代理(javaagent),使用代理来动态修改字节码文件。
  • RUNTIME:运行时级别,注解存在于源码、字节码、java虚拟机中,主要用于运行时,可以使用反射获取相关的信息。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}

3. 注解的内容

在上面的注解源码中可以看到有的注解中没有任何内容,有的注解的有内容,看似像方法。

注解的内容的语法格式: ​​数据类型 属性名() default 默认值​​,数据类型用于描述属性的数据类型,默认值是说当没有给属性赋值时使用默认值,一般String使用空字符串”“作为默认值,数组一般使用空数组{ }作为默认值.

下面看一下SpringMVC中的RequestMapping的注解的声明

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";

@AliasFor("path")
String[] value() default {};

@AliasFor("value")
String[] path() default {};

RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}

使用SpringMVC中的RequestMapping注解



@RequestMapping(value = "/test",   method = RequestMethod.POST,  produces = {"application/json;charset=UTF-8;"})
public String test(){

}

4. 注解的使用场景

可以通过注解的声明周期来分析注解的使用场景:

  • SOURCE源码级别:给编译器使用,如@Override、@Deprecated 等, 这部分开发者应该使用的场景不多
  • CLASS:字节码级别,这部分也很少见到
  • RUNTIME:运行时级别,这个是最多的,几乎开发者使用到的注解都是运行时级别,运行时注解常用的有以下几种情况
    • 注解中没有任何属性的,空的注解,这部分注解通常起到一个标注的作用,如@Test、@Before、@After,通过获取这些标记注解在逻辑上做一些特殊的处理
    • 可以使用约束注解@Constraint来对属性值进行校验,如@Email, @NotNull等
    • 可以通过在注解中使用属性来配置一些参数,然后可以使用反射获取这些参数,这些注解没有其他特殊的功能,只是简单的代替xml配置的方式来配置一些参数。使用注解来配置参数这在Spring boot中得到了热捧,如@Configuration

关于配置方式xml vs annotation, 一般使用xml配置一些和业务关系不太紧密的配置,使用注解配置一些和业务密切相关的参数。

三:注解和反射基本API

// 获取某个类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
// 获取所有注解(包括父类中被Inherited修饰的注解)
public Annotation[] getAnnotations();
// 获取声明的注解(但是不包括父类中被Inherited修饰的注解)
public Annotation[] getDeclaredAnnotations();
// 判断某个对象上是否被某个注解进行标注
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

// 获取某个类声明的所有字段
public Field[] getDeclaredFields() throws SecurityException;
// 获取某个方法
public Method getMethod(String name, Class<?>... parameterTypes);

四:自定义注解

使用自定义注解+拦截器或者是AOP等可以进行权限的控制。

下面通过定义一个注解用来限制当用户访问接口时必须要登录的示例。


1、自定义注解RequiresLogin

package io.mykit.annotation.spring.provider;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义需要登录的注解,如果方法上存在这个注解,则说明这个方法是必须登录后才能访问
* @author liuyazhuang
*
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresLogin {

}

2、新建UserService接口

package io.mykit.annotation.spring.service;

/**
* 模拟用户接口
* @author liuyazhuang
*
*/
public interface UserService {

/**
* 获取用户信息
* @return
*/
String getUserInfo();
}

3、实现接口的的实现类UserServiceImpl

这里,我们在getUserInfo方法上标注@RequiresLogin注解

package io.mykit.annotation.spring.service.impl;

import org.springframework.stereotype.Service;

import io.mykit.annotation.spring.provider.RequiresLogin;
import io.mykit.annotation.spring.service.UserService;

@Service("userService")
public class UserServiceImpl implements UserService {

@Override
@RequiresLogin
public String getUserInfo() {
try {
return "[{'id': 1, 'username':'liuyazhuang', 'sex':'mail', 'age':'18', 'address':'chengdu'}]";
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}

}

4、新建LoginAdvices注解解析类

package io.mykit.annotation.spring.advices;

import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import io.mykit.annotation.spring.provider.RequiresLogin;

/**
* 利用切面编程的JoinPoint解析注解信息
*
* @author liuyazhuang
*
*/
public class LoginAdvices {

/**
* 解析注解信息,模拟检验登录状态
* @param joinPoint
* @throws Exception
*/
public void before(JoinPoint joinPoint) throws Exception {

Object target = joinPoint.getTarget();
String methodName = joinPoint.getSignature().getName();

System.out.println(target + "-------" + methodName);
Method method = target.getClass().getMethod(methodName);
boolean annotationPresent = method.isAnnotationPresent(RequiresLogin.class);
if (annotationPresent) {
// 用户必须登录
boolean isLogin = false;
if (!isLogin) {
System.out.println("访问该接口必须先登录");
throw new Exception("访问该接口必须先登录");
} else {
System.out.println("已登录...");
}
}else{
System.out.println("不需登录");
}
}
}

5、新建spring-context.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd"
default-autowire="byName">
<context:annotation-config />
<context:component-scan base-package="io.mykit.annotation.spring" />

<bean id="loginAdvices" class="io.mykit.annotation.spring.advices.LoginAdvices"/>

<!-- aop配置 -->
<aop:config proxy-target-class="true">
<!--切面 -->
<aop:aspect ref="loginAdvices">
<!-- 切点 -->
<aop:pointcut id="pointcut1" expression="execution(* io.mykit.annotation.spring.service.*.*(..))"/>
<!--连接通知方法与切点 -->
<aop:before method="before" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
</beans>

6、编写测试类AnnotationTest

package io.mykit.annotation.spring.provider;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import io.mykit.annotation.spring.service.UserService;

/**
* 测试
* @author liuyazhuang
*
*/
public class AnnotationTest {


/**
* 测试自定义注解实现登录验证
*/
@Test
public void testAnnotation(){
@SuppressWarnings("resource")
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/spring-context-annotation.xml"});
UserService userService = (UserService) context.getBean("userService");
System.out.println(userService.getUserInfo());
}

}

6、pom.xml依赖

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skip_maven_deploy>false</skip_maven_deploy>
<jdk.version>1.8</jdk.version>
<spring.version>4.1.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.1</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
</dependency>

<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>

</dependencies>

至此,我们基于Spring AOP和自定义注解实现的用户登录拦截器到此就完成了。