² 背景
最近在为公司的技术改造做准备,我设计了一个提高Web开发效率的技术框架,为了增加框架的友好性和易用性,决定采用注解来代替配置文件,于是我查询了很多的资料,进行整理和学习。
² 概念
注解是JDK5引入的新特性,最初衍生自代码注释,但现在早已经超出了注释的范畴,以至于我很惶恐,不敢使用注释这个词汇来描述他,尽管现有的很多资料里仍然称其为注释。如果说反射使得很多技术实现(动态代理、依赖注入等)有了基础,那么注解就是使这些技术实现变得平民化的基础。
从class文件规范中可以看出,JDK5开始,class文件已经引入了注解描述片段。站在java虚拟机的角度来看,class保留和运行时保留的注解已经和java二进制码放在了同等的地位。虚拟机在加载class文件时,会为注解内容分配空间并进行解析,最终还会为注解和对应的二进制码建立关联。尽管这些注解不会被运行,但其对代码的说明能力,结合反射技术已经足够我们做太多的事情。
我们知道,java除了内置的注解(@Override、@Deprecated等)以外,还支持自定义注解(Struts、Hibernate等很多框架甚至java自身都实现了很多自定义注解)。当然,更为厉害的是元注解,元注解是用来描述注解的注解(光听着就觉得厉害了吧)。
要实现一个自定义注解,必须通过@interface关键字来定义。且在@interface之前,需要通过元注解来描述该注解的使用范围(@Target)、生命周期(@Retention)及其他(其他的不重要,所以领盒饭了)。
@Target用于描述注解的使用范围(即:被描述的注解可以用在什么地方),其取值有:
取值
|
描述
|
CONSTRUCTOR
|
用于描述构造器(领盒饭)。
|
FIELD
|
用于描述域(领盒饭)。
|
LOCAL_VARIABLE
|
用于描述局部变量(领盒饭)。
|
METHOD
|
用于描述方法。
|
PACKAGE
|
用于描述包(领盒饭)。
|
PARAMETER
|
用于描述参数。
|
TYPE
|
用于描述类或接口(甚至enum)。
|
@Retention用于描述注解的生命周期(即:被描述的注解在什么范围内有效),其取值有:
取值
|
描述
|
SOURCE
|
在源文件中有效(即源文件保留,领盒饭)。
|
CLASS
|
在class文件中有效(即class保留,领盒饭)。
|
RUNTIME
|
在运行时有效(即运行时保留)。
|
根据上述介绍,如果我需要定义一个用于对方法进行描述,且能在运行时可以读取到的自定义注解(假定我希望这个注解的名字是Sample)。那么,我就应该这样:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Sample {
public String value() default "";
}
OK,自定义注解已经写好了,那我们就可以在代码中使用这个注解了,如:
@Sample(value="I'm here.")
public void anyName() {
... ...
}
值得一提的是,在网上能搜索到的资料(中文的)几乎都是到此为止了。给人的感觉就像看美国大片,每到结束的时候总会给你一种未完待续的意味。事实上,我能容忍电影给我这样的感觉,因为这样会让我充满期待。而从技术的角度来说,我很厌恶这种感觉。
事实上,事情远没有结束。如果自定义注解以这样的形式存在,那么这种存在是没有任何实际意义的。
那么,我们接下来该做什么呢?
接下来我们应该编写自己的注解处理器。
嗯,再啰嗦一下,提到注解处理器,我又被N多资料误导了。很多资料都提到APT,或者AbstractProcessor。但事实上,我的理解是APT或者AbstractProcessor更多的用于:在非运行时进行增强处理(如:分析逻辑BUG,分析代码结构等等)。
回到注解处理器,注释处理器其实就是一段用于解释或处理自定义注解的代码而已,没有太多复杂的概念或者技术(嗯,先卖个关子,后面的实例会细说注解处理器的)。
² 实践
通过前文对自定义注解的了解,我猜想我应该这样做:
1. 结合实际需求规划注解的功能,以及定义如何解析注解
先说说我的需求吧:框架会把页面划分成N个分块,而每个分块都需要不同的类来处理输出内容,处理到不同的分块时,框架会自动创建对应的类实例(目前为止,没有任何问题)。接下来的问题就来了,每个分块处理类处理分块内容时,所需要的参数是不一样的(参数类型以及参数个数都不一样);因此,也不好定义一个固定的接口。当然,肯定有人会说可以把参数改成map,或Object数组。是的,这是一种解决办法,但是如果我用自定义注解,会不会能更好的完成这项工作呢?是的,答案在你我心中。
我们不妨设想一下:
如果处理类需要获取参数,那么这个处理类就给我注解某个方法(方法名任意,前文提到过:虚拟机会做好二者之间的关联),以说明该方法需要被框架预先调用一次(类似初始化方法)。同样的道理,在注解这个方法时,加入所需要的参数注解。
然后,在框架的处理程序中,我们先根据注解查找方法,如果该方法存在,则再次根据注解把对应的参数准备好,然后反射调用invoke方法。
OK,这样的设想应该是行得通的。
2. 定义并构造自定义注解
前文提到了我们需要对方法进行注解,而且注解中还需要包含参数信息。好吧,我的设想是定义两个注解:
@RenderParameter用于描述方法的参数,包括参数类型、参数来源等。
@RenderMethod用于描述方法(主要描述方法的参数列表)。
这里要提到一个小技巧:即注解可以使用数组(嗯嗯,待会会看到的)。
先来定义一下@RenderParameter吧:
… …
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
@Target({ElementType.METHOD}) // 注解对象为方法
public @interface RenderParameter {
// 参数类型
public enum ParameterType { STRING, SHORT, INT, BOOL, LONG, OBJECT };
// 参数值的来源
public enum ScopeType { NORMAL, SESSION, COOKIE, ATTRIBUTE, CUSTOM };
public String name(); // 参数名
public boolean ignoreCase() default false; // 匹配时是否忽略大小写
public ParameterType type() default ParameterType.STRING; // 参数类型
public ScopeType scope() default ScopeType.NORMAL; // 参数值来源
}
再看看@RenderMethod的定义:
… …
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
@Target({ElementType.METHOD}) // 注解对象为方法
public @interface RenderMethod {
public enum MethodType { INQUIRE };
public MethodType method() default MethodType.INQUIRE;
public RenderParameter[] parameters(); // 参数列表
}
至此,两个自定义注解已经完成,看看我应该如何使用他们:
@RenderMethod(parameters={@RenderParameter(name="logined", scope=ScopeType.SESSION),@RenderParameter(name="loginedUser", scope=ScopeType.SESSION)})
public void inquire(String logined, String loginedUser) {
if("true".equals(logined)) {
write(loginedUser + " is logined.");
} else {
write("No user logined.");
}
}
3. 构造自定义注解的处理方法(即注解处理器)
终于又说到注解处理器了,其实很简单:
… …
// 此处的renderer就是采用了自定义注解的类实例
for(Method method : renderer.getClass().getDeclaredMethods()) {
RenderMethod rm = (RenderMethod)method.getAnnotation(RenderMethod.class);
if(rm != null) {
int length = rm.parameters().length;
Object[] parameters = length > 0 ? buildParameters(rm.parameters()) : null;
try {
method.invoke(renderer, parameters);
} catch (IllegalArgumentException e) {
log.error(e.getMessage());
} catch (IllegalAccessException e) {
log.error(e.getMessage());
} catch (InvocationTargetException e) {
log.error(e.getMessage());
}
break;
}
}
… …
// 根据注解数组创建参数对象列表,供invoke使用
private Object[] buildParameters(RenderParameter[] parameters) {
Object[] objs = new Object[parameters.length];
int i = 0;
for(RenderParameter parameter : parameters) {
ScopeType scope = parameter.scope();
// 参数值来自request.getParameter
if(scope == ScopeType.NORMAL) {
String temp = request.getParameter(parameter.name());
String value = null;
if(temp != null && !"".equals(temp)) {
try {
byte[] bytes = temp.getBytes("iso-8859-1");
value = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage());
}
}
objs[i ++] = value;
// 参数值来自Session
} else if(scope == ScopeType.SESSION) {
objs[i ++] = request.getSession().getAttribute(parameter.name());
// 参数值来自Cookie
} else if(scope == ScopeType.COOKIE) {
for(Cookie cookie : request.getCookies()) {
if(cookie.getName().equals(parameter.name())) {
objs[i ++] = cookie.getValue();
break;
}
}
// 参数值来自request. getAttribute
} else if(scope == ScopeType.ATTRIBUTE) {
objs[i ++] = request.getAttribute(parameter.name());
}
}
return objs;
}
² 参考
1. 《java编程思想》
2. 《深入理解java虚拟机》
分享到:
相关推荐
自定义异常 使用断言 使用JDK Logging 使用Commons Logging 使用Log4j 使用SLF4J和Logback 反射 Class类 访问字段 调用方法 调用构造方法 获取继承关系 动态代理 注解 使用注解 定义注解 处理注解 ...
采用 AOP + 自定义注解 + Redis 实现限制IP接口访问次数 采用 Nacos 作为服务发现和配置中心,轻松完成项目的配置的维护 采用 Sentinel 流量控制框架,通过配置再也不怕网站被爆破 支持Markdown 编辑器 采用 Elastic...
11、自定义注解+AOP 12、权限中的概念梳理 13、登录.授权流程梳理 14、RuoYi-Vue3 15、动态菜单加载思路 16、Vue3中的数据加载 17、Promise 18、Vue3和Vue2的差异 19、Vue3中自定义插件 20、Vue3自定义指令 21、...
主要涉及反射与自定义注解的使用 @Bean 用于对象的自动创建 @Autowired 用于自动注入 Aware接口 用于在创建对象时候自动调用里面的方法 stn-apt 注解处理器学习使用,基于javapoet框架实现类的创建。 stn-cqxhat ...
JPA 使用@Query注解实现JPQL和本地自定义查询 JPA API 条件查询 (子查询,多表连接查询) JPA结合QueryDSL轻松完成复杂查询(自连接,多表连接查询) 参与文档: Pro JPA2中文版:精通Java持久化API 优秀博文读书笔记: ...
自定义注解插入发送切面 抽象消息存储 扩展元消息 项目结构说明 coolmq为实际包 microservice-demo们为spring-boot集成demo 使用说明 maven引入coolmq依赖 在项目启动中配置包扫描:@SpringBootApplication...
一、简介当下Java后端的SpringBoot微服务框架大火,原因离不开注解的使用,其简单易配置的注解方式使得更多的社区为其编写适用于SpringBoot的框架,也就是注解逐渐取代了传统的xml配置方式。那么注解在Android中也...
HIBERNATE - 符合Java习惯的关系数据库持久化 Hibernate参考文档 3.2 -------------------------------------------------------------------------------- 目录 前言 1. 翻译说明 2. 版权声明 1. Hibernate...
基于AAC实现组件生命周期观察实践控件实现LifecycleObserver接口,内部通过@OnLifecycleEvent注解声明生命周期事件public class LifecycleObserverDemo implements LifecycleObserver { @OnLifecycleEvent(Lifecycle...
Spring.NET以Java版的Spring框架为基础,将Spring.Java的核心概念与思想移植到了.NET平台上。 第一章 序言 第二章 简介 2.1.概述 2.2.背景 2.3.模块 2.4.许可证信息 2.5.支持 第三章 背景 3.1.控制反转 第...
过滤器机制简易RPC框架-客户端限流配置简易RPC框架-上下文简易RPC框架-代理简易RPC框架-熔断降级机制简易RPC框架-SPI熔断降级实现影响上下文机制,后续更新解决基于注解的锁Spring boot实践WEBValidator (未同步...
第12章:编写自定义插件/12.2 注解说明/12.2.2 @Parameter:获取pom.xml文件中属性 第12章:编写自定义插件/12.3 编写插件实例:统计代码行数/12.3.1 插件项目原型 第12章:编写自定义插件/12.3 编写插件实例:统计...
8.1.1 页面上:如何使用自定义结果组件构建Struts 2 Ajax应用程序 171 8.1.2 实现JSON结果类型 173 8.2 常用的结果类型 180 8.2.1 RequestDispatcher,也叫做dispatcher 180 8.2.2 ServletRedirectResult,也叫做...
自定义过滤器 读取Properties 项目源码 Spring Data JPA 的使用 JPA简单概念 快速上手 基本查询 复杂查询 关联查询 项目源码 模板引擎Thymeleaf 简单上手 常用标签汇总 项目源码 JPA 和 Thymeleaf ...
1. 在Tomcat中快速上手 1.1. 开始Hibernate之旅 1.2. 第一个持久化类 1.3. 映射cat 1.4. 与Cat同乐 1.5. 结语 2. 架构(Architecture) 2.1. 概况(Overview) 2.2. 实例状态 ...24. 最佳实践(Best Practices)
主要以学习为目的,围绕通信的部分实践一个轻量级的RPC框架。 1.基础实现 传输和协议 采用TCP协议为通信基础,基于Netty自定义数据包格式,心跳机制维持TCP单一长链接 注册中心 旨在基于ZooKeeper实现服务注册和服务...
符合Java习惯的关系数据库持久化 目录 前言 1. 翻译说明 2. 版权声明 1. 在Tomcat中快速上手 1.1. 开始Hibernate之旅 1.2. 第一个持久化类 1.3. 映射cat 1.4. 与Cat同乐 1.5. 结语 2. Hibernate入门 ...