细说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 ...
随机推荐
- NOIP2018 差点退役记
Day 1 不想说了,反正就是三个水题,直接来讲Day 2. Day 2 一上来,T1做法写的丑了点,折腾了一会,大概50min的样子写完了. T3一眼DDP--这玩意儿我就写过一个模板,还只写过一次 ...
- Square(斯特林反演)
题意 给出一个 \(n × m\) 大小的矩形,每个位置可以填上 \([1, c]\) 中的任意一个数,要求填好后任意两行互不等价且任意两列互不等价,两行或两列等价当且仅当对应位置完全相同,求方案数 ...
- linux添加lvm磁盘大小,命令行创建swap
添加硬盘 添加一块硬盘. 重新扫描硬盘 echo "- - -" > /sys/class/scsi_host/host0/scan echo "- - -&quo ...
- 【dfs】p1451 求细胞数量
题目描述 一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数.(1<=m,n<=100)? 输入输出格式## ...
- 「HNOI2016」最小公倍数 解题报告
「HNOI2016」最小公倍数 考虑暴力,对每个询问,处理出\(\le a,\le b\)的与询问点在一起的联通块,然后判断是否是一个联通块,且联通块\(a,b\)最大值是否满足要求. 然后很显然需要 ...
- One Person Game ZOJ - 3329(期望dp, 数学)
There is a very simple and interesting one-person game. You have 3 dice, namely Die1, Die2 and Die3. ...
- CANOE入门(一)
CANoe是Vector公司的针对汽车电子行业的总线分析工具,现在我用CANoe7.6版本进行介绍,其他版本功能基本差不多. 硬件我使用的是CAN case XL. 1,CANoe软件的安装很简单,先 ...
- CAS与ABA问题产生和解决
乐观锁和悲观锁 Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守.CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新. 性能对比: Syn ...
- A1146. Topological Order
This is a problem given in the Graduate Entrance Exam in 2018: Which of the following is NOT a topol ...
- (转)visual stdio 书签功能介绍
http://www.mycode.net.cn/tools/1615.html 使用 Visual Studio 开发过程中,你很容易遇到一种情况就是多个文件来回的切换,在每一块实现不同的业务,打开 ...