细说java系列之注解

写在前面
Java从1.5版本之后开始支持注解,通过注解可以很方便地实现某些功能,使用得最普遍的就是Spring框架的注解,大大简化了Bean的配置。
注解仅仅是一种Java提供的工具,并不是一种编程模式。
单纯定义注解不能做任何事情,没有任何意义。除了注解之外,还需要编写注解处理器,通过注解处理器解析注解,完成特定功能。
注解作为Java的一个特性,它可以解决某些特定场景的问题,但并不是万能的,更不能被当做“银弹”。
在Java1.5+中自带了一些注解,这些注解被称为元注解。另外,还可以在应用程序中自定义注解。
自定义注解
关于自定义注解的语法及规则请自行Google,在这里通过自定义注解记录用户在业务系统中的操作日志。
/**
* 操作日志注解,对使用了该注解的Controller方法记录日志.
* @desc org.chench.test.web.annotation.OperationLog
* @date 2017年11月29日
*/
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface OperationLog {
enum OperationType {
/**
* 不是任何操作
*/
NONE,
/**
* 登录系统
*/
LOGIN,
/**
* 登出系统
*/
LOGOUT
}
/**
* 操作类型
* @return
*/
public OperationType type() default OperationType.NONE;
/**
* 操作别名
* @return
*/
public String alias() default "";
/**
* 日志内容
* @return
*/
public String content() default "";
}
编写注解处理器应用实践
在Serlvet中通过自定义注解记录用户操作日志
1.首先,需要定义了一个基础的Servlet,在其中实现对自定义注解的解析处理(即这个基础Serlvet就是注解处理器)。
/**
* Servlet基类,解析自定义注解。
* @desc org.chench.test.web.servlet.BaseServlet
* @date 2017年11月29日
*/
public abstract class BaseServlet extends HttpServlet {
private static final long serialVersionUID = 2824722588609684126L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
// 在基础Servlet类中对自定义注解进行解析处理
Method[] methods = getClass().getDeclaredMethods();
for(Method method : methods) {
if(method.isAnnotationPresent(OperationLog.class)) {
String methodName = method.getName();
OperationLog operationLog = method.getAnnotation(OperationLog.class);
System.out.println("方法名称: " + methodName + "\n" +
"操作类型: " + operationLog.type() + "\n" +
"操作别名:" + operationLog.alias() + "\n" +
"日志内容:" + operationLog.content());
}
}
}
}
2.其次,业务Servlet都应该继承自上述定义的基础Servlet,并在业务方法中使用自定义注解。
public class AnnotationTestServlet extends BaseServlet {
private static final long serialVersionUID = -854936757428055943L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
// 在业务Servlet的方法中使用自定义注解,通过基础Servlet解析注解记录操作日志
@OperationLog(type=OperationLog.OperationType.LOGIN, alias="员工登录", content="员工登录")
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("用户名: " + userName);
System.out.println("密码: " + password);
}
}
在Spring框架中通过自定义注解记录用户操作日志
由于Spring框架已经提供了HandlerInterceptor拦截器接口,所以对于业务方法进行拦截更加方便。
1.首先,在拦截器中解析自定义注解(即这个拦截器就是注解处理器)。
public class MyInterceptor implements HandlerInterceptor{
// action执行之前执行
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
// 生成视图之前执行
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView mv) throws Exception {
System.out.println("postHandle");
}
// 执行资源释放
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion");
// 记录操作日志
if(handler instanceof HandlerMethod) {
Method method = ((HandlerMethod) handler).getMethod();
if(method.isAnnotationPresent(OperationLog.class)) {
String methodName = method.getName();
OperationLog operationLog = method.getAnnotation(OperationLog.class);
System.out.println("方法名称: " + methodName + "\n" +
"操作类型: " + operationLog.type() + "\n" +
"操作别名:" + operationLog.alias() + "\n" +
"日志内容:" + operationLog.content());
}
}
try {
// 如果定义了全局异常处理器,那么在这里是无法获取到异常信息的: ex=null
// 需要通过别的方式获取处理异常
if (ex == null) {
ex = ThreadExceptionContainer.get();
System.out.println("异常信息:" + ex.getMessage());
}
} finally {
ThreadExceptionContainer.clear();
}
}
}
如上代码所示,由于只是需要记录操作日志,所以对于自定义注解的解析放在拦截器的afterCompletion()方法中,这样做是为了不影响正常的请求响应。
很显然,afterCompletion()方法的参数列表中存在一个Exception对象,理论上我们可以在这里获取到业务方法抛出的异常信息。
但是,如果已经在SpringMVC中定义了全局异常处理器,那么在这里是无法获取到异常信息的,如下为配置的默认全局异常处理器。
<!-- 使用Spring自带的全局异常处理器 -->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView">
<value>error/error</value>
</property>
<property name="defaultStatusCode">
<value>500</value>
</property>
<property name="warnLogCategory">
<value>org.springframework.web.servlet.handler.SimpleMappingExceptionResolver</value>
</property>
</bean>
这是因为Spring的实现就存在这个限制,参考:https://jira.spring.io/browse/SPR-8467,官方的解释是:
We are calling afterCompletion without an exception in case of what we consider a successful outcome, which includes an
exception having been handled (and turned into an error view) by a HandlerExceptionResolver. I guess the latter scenario
is what you're getting confused by? In other words, we effectively only pass in an actual exception there when no
HandlerExceptionResolver was available to handle it, that is, when it eventually gets propagated to the servlet container...
I see that this is debatable. At the very least, we should be more explicit in the javadoc there.
那么,如果我们确实需要在afterCompletion()中获取到业务方法抛出的异常信息,应该怎么做呢?
在这里,采用了通过ThreadLocal保存异常数据的方式实现。为此,我们需要扩展一下Spring自带的异常处理器类。
/**
* 自定义全局异常解析类
* @desc org.chench.test.springmvc.handler.MyExceptionResolver
* @date 2017年11月29日
*/
public class MyExceptionResolver extends SimpleMappingExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView mv = super.resolveException(request, response, handler, ex);
if(ex != null) {
// 当异常信息不为空时,保存到ThreadLocal中
ThreadExceptionContainer.set(ex);
}
return mv;
}
}
/**
* 保存线程上下文异常信息
* @desc org.chench.test.springmvc.util.ThreadExceptionContainer
* @date 2017年11月29日
*/
public class ThreadExceptionContainer {
private static final ThreadLocal<Exception> exceptionCache = new ThreadLocal<Exception>();
public static void set(Exception exception) {
exceptionCache.set(exception);
}
public static Exception get() {
return exceptionCache.get();
}
public static void clear() {
exceptionCache.remove();
}
private ThreadExceptionContainer() {
}
}
然后在Spring中使用扩展的全局异常处理器类:
<!-- 使用自定义扩展的全局异常处理器 -->
<bean id="exceptionResolver" class="org.chench.test.springmvc.handler.MyExceptionResolver">
<property name="defaultErrorView">
<value>error/error</value>
</property>
<property name="defaultStatusCode">
<value>500</value>
</property>
<property name="warnLogCategory">
<value>org.springframework.web.servlet.handler.SimpleMappingExceptionResolver</value>
</property>
</bean>
2.在Controller方法中使用自定义注解
// 在业务方法中使用自定义注解
@OperationLog(type=OperationType.LOGIN, alias="用户登录", content="用户登录")
@RequestMapping("/login")
public String login(HttpServletRequest req,
@RequestParam("username") String username,
@RequestParam("password") String password) throws NotFoundException, CredentialException {
Account account = accountService.getAccountByUsername(username);
// 用户不存在
if(account == null) {
logger.error("account not found! username: {}, password: {}", new Object[] {username, password});
throw new NotFoundException(String.format("account not found for username: %s", username));
}
// 密码不正确
if(!account.getPassword().equals(password)) {
logger.error("credentials error for account: " + username);
throw new CredentialException("credentials error for account: " + username);
}
req.getSession().setAttribute("account", account);
return "redirect:home";
}
自定义注解总结
实际上,在编写注解处理器时使用的是Java的另一个功能:反射机制。
本质上来讲,所谓的注解解析器就是利用反射机制获取在类,成员变量或者方法上的注解信息。
Java反射机制可以让我们在运行期获得任何一个类的字节码,包括接口、变量、方法等信息。还可以让我们在运行期实例化对象,通过调用get/set方法获取变量的值等。
也就是说,如果Java仅仅支持了注解,却未提供反射机制,实际上是不能做任何事情的,反射机制是我们能够在Java中使用注解的基础。
【参考】
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html 注解(Annotation)自定义注解入门
细说java系列之注解的更多相关文章
- Java系列之注解
Java系列之注解 Java 注解(Annotation)又称之为 Java 标注.元数据,是 Java 1.5 之后加入的一种特殊语法,通过注解可以标注 Java 中的类.方法.属性.参数.包等,可 ...
- 【java】细说 JAVA中 标注 注解(annotation)
Java注解是附加在代码中的一些元信息,用于一些工具在编译.运行时进行解析和使用,起到说明.配置的功能.注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用 下面我们来详细说说这个注解,到底是怎么一 ...
- 细说java系列之反射
什么是反射 反射机制允许在Java代码中获取被JVM加载的类信息,如:成员变量,方法,构造函数等. 在Java包java.lang.reflect下提供了获取类和对象反射信息的相关工具类和接口,如:F ...
- 细说java系列之泛型
什么是范型 简言之,范型是Java支持在编译期进行类型检查的机制. 这里面包含2层含义:其一,可以使用范型进行类型检查:其二,在编译期进行类型检查. 那么,什么叫做在编译期进行类型检查?可以在运行时进 ...
- 细说java系列之HashMap原理
目录 类图 源码解读 总结 类图 在正式分析HashMap实现原理之前,先来看看其类图. 源码解读 下面集合HashMap的put(K key, V value)方法探究其实现原理. // 在Hash ...
- java基础解析系列(六)---注解原理及使用
java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...
- 【转】O'Reilly Java系列书籍建议阅读顺序(转自蔡学庸)
Learning Java the O'Reilly's Way (Part I) Java 技术可以说是越来越重要了,不但可以用在计算机上,甚至连电视等家电用品,行动电话.个人数字助理(PDA)等电 ...
- 【细说Java】Java的重写与隐藏
重写与隐藏,有些书上或介绍上可能名称不一样,但都大差不差.以前只了解重写,隐藏也听说过,但没有详细了解过,趁现在,整理一下这两方面的内容吧. 首先,先说一下概念方面的东西. 重写 重写:子类继承了父类 ...
- springBoot系列-->springBoot注解大全
一.注解(annotations)列表 @SpringBootApplication:包含了@ComponentScan.@Configuration和@EnableAutoConfiguration ...
随机推荐
- Hdoj 4508.湫湫系列故事——减肥记I 题解
Problem Description 对于吃货来说,过年最幸福的事就是吃了,没有之一! 但是对于女生来说,卡路里(热量)是天敌啊! 资深美女湫湫深谙"胖来如山倒,胖去如抽丝"的道 ...
- Leetcode 80.删除排序数组中的重复项 II By Python
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. 示例 ...
- CANdbc编辑器的下载和入门介绍
candb 是汽车CAN总线dbc文件的常用编辑软件之一,下面介绍如何下载和安装,本人电脑使用的是win7 64bit. https://download.csdn.net/download/wuku ...
- kafka问题集锦
一. org.apache.kafka.common.errors.TimeoutException: Batch Expired 问题描述:通过java客户端访问kafka服务器,当生产者线程向ka ...
- Docker中如何删除image(镜像)
原文地址:http://yaxin-cn.github.io/Docker/how-to-delete-a-docker-image.html docker中删除images的命令是docker rm ...
- poj1456 Supermarket
书上用的方法是正着按照天数推,如果任务大于小根堆顶就替换,天数多于任务就加. 而我依稀记得以前洛谷上有一题也是这个,用时光倒流来求解,天数倒推,加任务,取大根堆顶即可. 我的代码实现: #includ ...
- (转)每天进步一点点——五分钟理解一致性哈希算法(consistent hashing)
背景:在redis集群中,有关于一致性哈希的使用. 一致性哈希:桶大小0~(2^32)-1 哈希指标:平衡性.单调性.分散性.负载性 为了提高平衡性,引入“虚拟节点” 每天进步一点点——五分钟理解一致 ...
- 【洛谷P2142 高精度减法】
题目描述 高精度减法 输入输出格式 输入格式: 两个整数a,b(第二个可能比第一个大) 输出格式: 结果(是负数要输出负号) 输入输出样例 输入样例#1: 复制 2 1 输出样例#1: 复制 1 说明 ...
- 【洛谷P1303 A*B Problem】
题目描述 求两数的积. 输入输出格式 输入格式: 两行,两个数. 输出格式: 积 输入输出样例 输入样例#1: 1 2 输出样例#1: 2 说明 每个数字不超过10^2000,需用高精 emm,显然本 ...
- canvas的api小结
HTML <canvas id="canvas"></canvas> Javascript var canvas=document.getElementBy ...