细说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 ...
随机推荐
- 【THUWC2017】随机二分图(动态规划)
[THUWC2017]随机二分图(动态规划) 题面 BZOJ 洛谷 题解 如果每天边的限制都是\(0.5\)的概率出现或者不出现的话,可以把边按照二分图左侧的点的编号排序,然后设\(f[i][S]\) ...
- [APIO/CTSC 2007]数据备份(贪心+堆)
你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份.然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣. ...
- Spring Data JPA Batch Insertion
转自:https://www.jeejava.com/spring-data-jpa-batch-insertion/ Spring Data JPA Batch Insertion will sho ...
- ASP.NET MVC计划任务实现方法(定时执行某个功能)
系统中定时执行某个任务是比较常用的功能,如一个部门定期向上级部门上报数据是一个典型的例子,下面就简单说说在.net mvc中如何实现定时执行某个功能的方法. 1.首先修改Glocal.asax文件,在 ...
- vue使用element-ui 监听使用回车键事件
因为element-ui 对input做了封装,使用@keyup.enter="fn"触发Enter键事件就不会触发,解决:后面追加.native.@keyup.enter.nat ...
- 第二十五篇-Android 应用资源
这里介绍android的一些资源文件. 以一个登录界面为例. layout.xml <?xml version="1.0" encoding="utf-8" ...
- java 中 byte[]、File、InputStream 互相转换
1.将File.FileInputStream 转换为byte数组: File file = new File("test.txt"); InputStream input = n ...
- linux 系统调用之文件操作
fcntl 文件控制 open 打开文件 creat 创建新文件 close 关闭文件描述字 read 读文件 write 写文件 readv 从文件读入数据到缓冲数组中 writev 将缓冲数组里的 ...
- css的简单学习笔记
1.CSS的简介 *css :层叠样式表 **层叠: 一层一层. **样式表: 具有大量的属性和属性值 *使得页面的显示效果更加好. *css将网页内容和显示样式进行分离,提高了显示功能. *css不 ...
- Day30--Python--struct, socketserver
1. struct struct.pack 打包 def pack(fmt, *args): # known case of _struct.pack """ pack( ...