细说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 ...
随机推荐
- Centos install Python3
下载Python3wget http://python.org/ftp/python/3.4.1/Python-3.4.1.tar.xz 安装yum install openssl-devel bzi ...
- 搭建本地yum源并定时同步
在生产中内网的机器都是不能访问外网,所以需要搭建本地yum源.以中国科学科技大学的yum源为基准.http://mirrors.ustc.edu.cn/ 有些模块会同步失败,可以wget下载至指定位置 ...
- html标题、段落、换行与字符实体
通过 <h1>.<h2>.<h3>.<h4>.<h5>.<h6>,标签可以在网页上定义6种级别的标题.6种级别的标题表示文档的6 ...
- centos7下kafka集群安装部署
应用摘要: Apache kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写.Kafka是一种高吞吐量的 分布式发布订阅消息系统,是消息中间件的一种,用于构建实时 ...
- JavaWeb基础之Servlet简单实现用户登陆
学习javaweb遇到了一些坑,一些问题总结下来,记个笔记. 学习servlet遇到的一些坑: servlet实现用户登陆遇到的坑解决办法: https://www.cnblogs.com/swxj/ ...
- 洛谷P1072 Hankson的趣味题
这是个NOIP原题... 题意: 给定 a b c d 求 gcd(a, x) = b && lcm(c, x) = d 的x的个数. 可以发现一个朴素算法是从b到d枚举,期望得分50 ...
- 【洛谷 P2430 严酷的训练】
题目背景 Lj的朋友WKY是一名神奇的少年,在同龄人之中有着极高的地位... 题目描述 他的老师老王对他的程序水平赞叹不已,于是下决心培养这名小子. 老王的训练方式很奇怪,他会一口气让WKY做很多道题 ...
- 将本地html文件拖到IE8浏览器无法打开,直接弹出一个下载的对话框
查看一下注册表[HKEY_CLASSES_ROOT\.htm]和[HKEY_CLASSES_ROOT\.html]的ContentType值是否都为“text/html”
- 高DPI下界面错乱的解决方法和原理
来源: http://bbs.csdn.net/topics/370177760 我在win32 + c写的界面中解决办法,就是把字体的字号给固定了,这样做的结果就是,不管dpi是否有改变,界面中控件 ...
- TCHAR和CHAR类型的互转,string 转lpcwstr
https://www.cnblogs.com/yuguangyuan/p/5955959.html 没有定义UNICODE,所以它里面的字符串就是简单用" "就行了,创建工程的时 ...