文章目录
一、依赖
在正常的SpringBoot项目中加入如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、自定义注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAop {
String value() default "";
String[] params() default {};
}
1、@Target
ElementType 是一个枚举类型,它规范了注解的使用位置;
public enum ElementType {
/** 类, 接口 (包括注解类型), 或 枚举 声明 */
TYPE,
/** 字段声明(包括枚举常量) */
FIELD,
/** 方法声明(Method declaration) */
METHOD,
/** 正式的参数声明 */
PARAMETER,
/** 构造函数声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包声明 */
PACKAGE,
/**
* 类型参数声明
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 使用的类型
*
* @since 1.8
*/
TYPE_USE
}
2、@Retention
RetentionPolicy这个枚举类型的常量描述保留注解的各种策略,它确定了注解的存活时间
public enum RetentionPolicy {
/**
* 注解只在源代码级别保留,编译时被忽略
*/
SOURCE,
/**
* 注解将被编译器在类文件中记录
* 但在运行时不需要JVM保留。这是默认的
* 行为.
*/
CLASS,
/**
*注解将被编译器记录在类文件中
*在运行时保留VM,因此可以反读。
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
3、@Documented
这个注解表明将会被javadoc记录, 如果类型声明被这个注解了,它将成为公共API的一部分。
三、解析注解
在使用自定义注解的时候基本是使用AOP的方式来处理问题的,同时也有标注的功能,通常接口的注解通过AOP可以起到拦截作用,下面演示一下注解的功能,还是上面的注解,可以知道这个注解可以使用在方法上、类型上和属性上,首先定义一个测试的实例对象并使用这个注解
@TestAop("Dept")
public class Dept {
private Long deptId;
@TestAop("deptName")
private String deptName;
private DataSourceType dataSource;
public Dept() { }
public Dept(Long deptId, String deptName, DataSourceType dataSource) {
this.deptId = deptId;
this.deptName = deptName;
this.dataSource = dataSource;
}
@Override
public String toString() {
return "Dept{" +
"deptId=" + deptId +
", deptName='" + deptName + '\'' +
", dataSource=" + dataSource +
'}';
}
/***************************省略setter和getter***********************************
}
这里可以看出对这个类使用了注解
在写一个方法来使用注解
@Service
public class DeptServiceImpl implements DeptService {
public static final Logger logger = LoggerFactory.getLogger(DeptServiceImpl.class);
@Override
@TestAop(value = "TEST_AOP", params = {"name", "dept"})
public String testAop(String name, Dept dept) {
logger.info(name);
logger.info(dept.toString());
logger.info("testAop");
return "testAop";
}
}
这里方法使用了注解,同时传入的参数是上面使用注解的类型
下面使用AOP去使用注解,这里就不介绍AOP的使用了,这里使用前置增强,后置增强,环绕增强和返回增强来说明
@Aspect
@Component
public class TestAopAspect {
public static final Logger logger = LoggerFactory.getLogger(TestAopAspect.class);
/**
* 设置使用这个注解的方法所在组成切面
*/
@Pointcut("@annotation(com.example.myproject.aop.annotation.TestAop)")
public void annotationPointcut() {
}
/**
* 前置增强
*
* @param joinPoint
*/
@Before("annotationPointcut()")
public void beforePointcut(JoinPoint joinPoint) {
// 此处进入到方法前 可以实现一些业务逻辑
logger.info("beforePointcut2");
}
/**
* 后置增强
*
* @param joinPoint
*/
@After("annotationPointcut()")
public void afterPointcut(JoinPoint joinPoint) {
// 此处进入到方法前 可以实现一些业务逻辑
logger.info("afterPointcut4");
}
/**
* 环绕增强
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("annotationPointcut()")
public String doAround(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("doAround1");
/**
* 获取添加了这个注解的方法实例
*/
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
/**
* 获取这个注解的信息
*/
TestAop testAop = method.getAnnotation(TestAop.class);
/**
* 输出这个方法上这个注解的属性信息
*/
logger.info(testAop.toString());
// 获取传入的参数,是有序的数组对象
Object[] args = joinPoint.getArgs();
// 获取传入的参数名称,是有序的数组对象
String[] argNames = signature.getParameterNames();
// 这里把参数名称和参数配对
HashMap<String, Object> params = new HashMap<>(2);
for (int i = 0, length = argNames.length; i < length; i++) {
params.put(argNames[i], args[i]);
}
// 获取参数的值
String name = params.get("name").toString();
Dept dept = (Dept) params.get("dept");
Object obj = params.get("dept");
// 这里对参数修改一下
dept.setDataSource(DataSourceType.db02);
args[0] = dept.getDeptName();
args[1] = dept;
// 这里获取传入参数的注解,也就是上面定义的Dept对象类名上的注解信息
TestAop deptAop = obj.getClass().getAnnotation(TestAop.class);
// 输出类型上的注解信息
logger.info(deptAop.toString());
// 通过反射的方式获取属性信息
Class clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// 获取属性上的注解的信息
TestAop fieldAop = field.getAnnotation(TestAop.class);
if (fieldAop != null) {
logger.info(fieldAop.toString());
logger.info(fieldAop.value() + ":" + field.getName() + "=" + field.get(obj));
}
}
/**
* 执行方法,传入我们修改后的参数,返回值是拦截的方法的返回值
* 这里要注意,已办情况下这个返回值要被这个环绕增强方法返回
* 否则拦截的方法使用时将没有返回值
*/
Object ottt = joinPoint.proceed(args);
logger.info("doAround3");
// 这里返回的值是拦截的方法返回值加上拼接的字段作为返回值,也将是拦截方法的返回值
return ottt + "doAround 返回值";
}
/**
* 返回增强
*
* @param joinPoint
* @param data
*/
@AfterReturning(pointcut = "annotationPointcut()", returning = "data")
public void doAfterReturning(JoinPoint joinPoint, Object data) {
//这里要注意,有环绕增强时一定要有返回值,否则这个data就没有值,这个data就是返回值
logger.info(data.toString());
logger.info("doAfterReturning5");
}
}
这里添加一个调用接口,使用web的方式调用方法测试
@RestController
public class HelloWorldController {
public static final Logger logger = LoggerFactory.getLogger(HelloWorldController.class);
@Autowired
private DeptService deptService;
@RequestMapping("/hello")
public String index() {
return deptService.testAop("Hello World",new Dept(1L,"hello", DataSourceType.db01));
}
}
启动主函数测试
浏览器返回值如上,我们可以知道这里的返回值应该是testAop
方法的返回值,但是这个方法的返回值应该是testAop
,而输出的结果是是被环绕增强修改过的,也从侧面说明如果环绕增强没有返回值,也将没有输出结果
查看后台
doAround1
@com.example.myproject.aop.annotation.TestAop(params=[name, dept], value=TEST_AOP)
@com.example.myproject.aop.annotation.TestAop(params=[], value=Dept)
@com.example.myproject.aop.annotation.TestAop(params=[], value=deptName)
deptName:deptName=hello
beforePointcut2
hello
Dept{deptId=1, deptName='hello', dataSource=db02}
testAop
doAround3
afterPointcut4
testAopdoAround 返回值
doAfterReturning5
通过后台我们可以知道增强的调用顺序
1、增强调用顺序
首先是环绕增强的前置部分,然后的前置增强到环绕增强的后置,然后是后置增强,最后是返回增强
2、环绕增强获取注解信息
-
获取接口参数
使用环绕增强的参数
ProceedingJoinPoint joinPoint
,通过getSignature()
方法获取拦截的实例然后通过实例的
getAnnotation
方法获取注解信息 -
获取类型的注解信息
通过这个类的
class
对象使用getAnnotation
可以获取 -
获取属性上的注解信息
通过反射原理可以获取属性的实例对象,然后通过
getAnnotation
方法获取注解信息
3、环绕增强修改传入接口方法的参数
这里要了解一下proceed
方法,有两个方法,无参的表示参数不修改,有参的参数是修改后要传入的参数,它表示执行的方法的位置是时机
所以我们修改了Dept
和name
的值,重新传入,发现参数修改成功了
4、环绕增强的接口方法的返回值
通过上面可以知道proceed
方法的返回值就是原来接口方法的返回值,经过环绕增强修改后,接口方法的实际返回值是修改后的,同时返回增强获取的返回值就是环绕增强的返回值,也就是被拦截的方法的有效返回值。
到这里就简单的介绍了自定义注解的使用了。