在Spring的体系中有很多种实现自定义注解的方式,在这里介绍一下如何通过AOP的方式来实现用户自定义的注解。
在这里实现一个对方法中的指定参数进行是否为null的判断的注解,以此为例展示自定义注解的方便之处。
配置环境
首先搭建Spring环境,在pom.xml中引入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tech.codemine</groupId>
<artifactId>annotation-in-action</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
</project>
接着在resources文件夹中建立一个applicationContext.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"
default-lazy-init="false">
<context:component-scan base-package="tech.codemine"/>
<aop:aspectj-autoproxy/>
<aop:config proxy-target-class="true"> </aop:config>
</beans>
然后建立四个使用到的注解,分别是
用于检验所有参数都不为null的@CheckAllNotNull
package tech.codemine.nullcheck.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface CheckAllNotNull {
}
用于检验指定参数不为null的@CheckNotNull
package tech.codemine.nullcheck.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface CheckNotNull {
}
以及用来为部分参数注解是否需要检验的@NotNull,与@CheckNotNull进行配合
package tech.codemine.nullcheck.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@Inherited
public @interface NotNull {
}
接着建立一个NullCheck.java文件用于处理注解
package tech.codemine.nullcheck.aspect;
import javafx.util.Pair;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import tech.codemine.nullcheck.annotation.NotNull;
import tech.codemine.nullcheck.exception.ParameterIsNullException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
@Auther: wangzhen25
@Date: 2018/11/20 16:51
@Description:
*/
@Aspect
@Component
public class NullCheckAspect {
// 在这里定义切点,切点为我们之前设置的注解@CheckAllNotNull
@Pointcut("@annotation(tech.codemine.nullcheck.annotation.CheckAllNotNull)")
public void checkAllNotNullPointcut() {
}
// 在这里定义切点,切点为我们之前设置的注解@CheckNotNull
@Pointcut("@annotation(tech.codemine.nullcheck.annotation.CheckNotNull)")
public void checkNotNullPointcut() {
}
// 在这里定义@CheckAllNotNull的增强方法
@Around("checkAllNotNullPointcut()")
public Object methodsAnnotatedWithCheckAllNotNull(ProceedingJoinPoint joinPoint) throws Throwable {
// 首先获取方法的签名,joinPoint中有相应的签名信息
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
// 通过方法的签名可以获取方法本身
Method method = signature.getMethod();
// 通过joinPoint获取方法的实际参数的值
Object[] args = joinPoint.getArgs();
// 对参数的值进行遍历判断,如果为null则抛出异常
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if(arg == null)
throw new ParameterIsNullException(method, i + 1);
}
// 最后如果校验通过则调用proceed方法进行方法的实际执行
return joinPoint.proceed();
}
// 在这里定义@CheckNotNull的增强方法
@Around("checkNotNullPointcut()")
public Object methodsAnnotatedWithCheckNotNull(ProceedingJoinPoint joinPoint) throws Throwable {
// 首先获取方法的签名,joinPoint中有相应的签名信息
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
// 通过方法的签名可以获取方法本身
Method method = signature.getMethod();
// 获取方法的参数列表,注意此方法需JDK8+
Parameter[] parameters = method.getParameters();
// 获取参数列表的实际的值
Object[] args = joinPoint.getArgs();
// 建立参数列表和实际的值的对应关系
Pair<Parameter, Object>[] parameterObjectPairs = new Pair[parameters.length];
for(int i = 0; i < parameters.length; i++) {
parameterObjectPairs[i] = new Pair<>(parameters[i], args[i]);
}
// 对参数进行遍历
for(int i = 0; i < parameterObjectPairs.length; i++) {
Pair<Parameter, Object> pair = parameterObjectPairs[i];
Parameter parameter = pair.getKey();
Object arg = pair.getValue();
// 获取参数的注解列表
NotNull[] notNulls = parameter.getAnnotationsByType(NotNull.class);
// 如果发现没有@NotNull注解则校验下一个参数
if(notNulls.length == 0) {
continue;
}
// 如果发现参数有@NotNull注解并且实际值为null则抛出异常
if(arg == null) {
throw new ParameterIsNullException(method, i + 1);
}
}
// 校验通过后继续往下执行实际的方法
return joinPoint.proceed();
}
}
以及一个专属的ParameterIsNullException
package tech.codemine.nullcheck.exception;
import java.lang.reflect.Method;
/**
@Auther: wangzhen25
@Date: 2018/11/20 18:20
@Description:
*/
public class ParameterIsNullException extends Exception {
public ParameterIsNullException(String message) {
super(message);
}
public ParameterIsNullException(Method method, int paramentIndex) {
super("[in method: " + method.getName() + "] the " + paramentIndex + "th parameter " + "can not be null");
}
}
项目的完整结构如下
功能测试
@CheckAllNotNull
package tech.codemine.nullcheck;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import tech.codemine.nullcheck.annotation.CheckAllNotNull;
import tech.codemine.nullcheck.annotation.CheckNotNull;
import tech.codemine.nullcheck.annotation.NotNull;
@Component
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
Main m = (Main) applicationContext.getBean("main");
m.test("hello", null);
}
@CheckAllNotNull
public void test(String var1, String var2) {
System.out.println(var1 + var2);
}
}
控制台输出
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at tech.codemine.nullcheck.Main$$EnhancerBySpringCGLIB$$ed31225e.test(<generated>)
at tech.codemine.nullcheck.Main.main(Main.java:17)
Caused by: tech.codemine.nullcheck.exception.ParameterIsNullException: [in method: test] the 2th parameter can not be null
at tech.codemine.nullcheck.aspect.NullCheckAspect.methodsAnnotatedWithCheckAllNotNull(NullCheckAspect.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
... 2 more
Process finished with exit code 1
@CheckNotNull
测试一
首先测试对第一个参数设为null但不使用@NotNull
package tech.codemine.nullcheck;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import tech.codemine.nullcheck.annotation.CheckAllNotNull;
import tech.codemine.nullcheck.annotation.CheckNotNull;
import tech.codemine.nullcheck.annotation.NotNull;
@Component
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
Main m = (Main) applicationContext.getBean("main");
m.test(null, null);
}
@CheckNotNull
public void test(String var1, String var2) {
System.out.println(var1 + var2);
}
}
控制台输出
nullnull
Process finished with exit code 0
测试二
现在使用@NotNull对参数进行约束
package tech.codemine.nullcheck;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import tech.codemine.nullcheck.annotation.CheckAllNotNull;
import tech.codemine.nullcheck.annotation.CheckNotNull;
import tech.codemine.nullcheck.annotation.NotNull;
@Component
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
Main m = (Main) applicationContext.getBean("main");
m.test(null, null);
}
@CheckNotNull
public void test(@NotNull String var1, String var2) {
System.out.println(var1 + var2);
}
}
控制台输出
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at tech.codemine.nullcheck.Main$$EnhancerBySpringCGLIB$$ed31225e.test(<generated>)
at tech.codemine.nullcheck.Main.main(Main.java:17)
Caused by: tech.codemine.nullcheck.exception.ParameterIsNullException: [in method: test] the 1th parameter can not be null
at tech.codemine.nullcheck.aspect.NullCheckAspect.methodsAnnotatedWithCheckNotNull(NullCheckAspect.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
... 2 more
Process finished with exit code 1
测试结束