注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。
1.基本使用
java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。
1.1 声明定义一个注解
(1)五要素:
修饰符:访问修饰符必须为public,不写默认为pubic;
关键字:关键字为@interface
注解名:注解名称为自定义注解的名称
注解类型元素:注解类型元素是注解中内容,可以理解成自定义接口的实现部分;
元注解:使用元注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OAuth
{
String loginUrl() default WapUrl.LOGINPAGE;
}
1.2 常用元注解
(1)Target
描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述方法变量
TYPE:用于描述类、接口或enum类型
(2)Retention
表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为:
SOURCE:在源文件中有效,编译过程中会被忽略
CLASS:随源文件一起编译在class文件中,运行时忽略
RUNTIME:在运行时有效
只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解
1.3 获取注解
可以通过反射获取注解,比如获取@ResponseBody注解
// 判断是否是AJAX请求,AJAX请求一般使用@ResponseBody
private boolean isJsonRequest(HandlerMethod handler)
{
ResponseBody responseBody = handler.getMethodAnnotation(ResponseBody.class);
boolean isJsonRequest = false;
if (responseBody != null)
{
isJsonRequest = true;
}
return isJsonRequest;
}
2.应用场景
2.1 自定义注解+拦截器 实现登录校验
使用spring拦截器实现这样一个功能,如果方法上加了@OAuth,则表示用户该接口需要登录才能访问(没有登录直接跳转到登录页面),否则不需要登录。
(1)先定义一个OAuth注解,默认值是登录页面地址
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OAuth
{
String loginUrl() default WapUrl.LOGINPAGE;
}
(2)添加登录注解
接口方法添加我们的登录注解@OAuth,要求登录才能访问
// 添加购物车商品
@OAuth
@ResponseBody
@RequestMapping(value = WebURL.CART_ADD, method = RequestMethod.POST)
public ResponseJSON actionCartAdd(@RequestBody @Valid CartAddModRequest cartAddModRequest, BindingResult bindingResult)
{
}
(3)实现登录拦截逻辑
如果当前用户没有登录,直接跳转到登录页面
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 反射获取方法上的OAuth注解
OAuth oAuth = handlerMethod.getMethodAnnotation(OAuth.class);
// 登录用户信息
OAuthUser oAuthUser = authService.getOAuth(sid);
if (oAuth != null) {
// 不处于登录状态
if (oAuthUser == null || oAuthUser.getCustId() == null) {
// 判断是否是AJAX请求
boolean isJsonRequest = isJsonRequest((HandlerMethod) handler);
// redirect到登录界面
String baseUrl = Constants.BASE_SERVER;
String returnUrl = baseUrl + request.getRequestURI() + (request.getQueryString() != null ?
"?" + request.getQueryString() :
"");
response.setStatus(403);
if (Strings.isNullOrEmpty(oAuth.loginUrl())) {
response.sendRedirect(
baseUrl + WapUrl.LOGINPAGE + "?returnUrl=" + URLEncoder.encode(returnUrl, "utf-8"));
} else {
response.sendRedirect(
baseUrl + oAuth.loginUrl() + "?returnUrl=" + URLEncoder.encode(returnUrl, "utf-8"));
}
return false;
}
}
return true;
}
2.2 自定义注解+拦截器 实现限制访问
(1)先定义一个Follow注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Follow
{
// 触屏版(域名配置)是否允许访问
boolean isWapAccess() default false;
// APP是否允许访问
boolean isAppAccess() default false;
// 只需要openId, 并不强制用户关注
boolean onlyOpenId() default false;
}
(2)添加触屏版是否允许访问注解
@Follow(isWapAccess = false)
@RequestMapping(value = WapUrl.TODAY_SHOW_LIST_FOLLOW, method = RequestMethod.GET)
public String todayWxList(@ModelAttribute("model") ModelMap model)
{
return todayList(model);
}
(3)实现登录拦截逻辑
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 反射获取方法上的Follow注解
Follow follow = handlerMethod.getMethodAnnotation(Follow.class);
if (follow != null) {
if (follow.isAppAccess() && WebCookiesUtil.getIsAppLogin()) {
return true;
}
if (!follow.isWapAccess()) {
redirectWechatPage(request, response);
return false;
} else {
// 有follow标签+非微信访问+允许wap访问
}
}
}
2.3 自定义注解+AOP 实现日志保存
此例子使用Spring boot 实现
(1)导入切面需要的依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(2)定义一个注解@SysLog
// 系统日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
(3)方法上添加日志注解
// 获取费用信息,汇总后的
@SysLog("查询费用信息,主页面")
@PostMapping("getExpense")
public ResultDto getExpense(@RequestBody Map<String, Object> map)
{
return sExpenseService.getExpense(map);
}
(4)定义一个切面类,实现拦截逻辑
/**
* 系统日志,切面处理类
*/
@Aspect
@Component
@Slf4j
public class SysLogAspect {
private final SysLogService sysLogService;
@Autowired
public SysLogAspect(SysLogService sysLogService) {
this.sysLogService = sysLogService;
}
//PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名,切面最主要的就是切点,所有的故事都围绕切点发生。
//logPointCut是一个切点名字,@Around注解使用,表示围绕该切点发生
@Pointcut("@annotation(com.hao24.common.annotation.SysLog)")
public void logPointCut() {
}
// logPointCut1是另外一个切点名字
@Pointcut("execution(* com.hao24.modules..*.*Controller.*(..))")
public void logPointCut1()
{
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveSysLog(point, time);
return result;
}
//保存到数据库中
private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLogEntity sysLog = new SysLogEntity();
SysLog syslog = method.getAnnotation(SysLog.class);
if(syslog != null){
//注解上的描述
sysLog.setOperation(syslog.value());
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//请求的参数
Object[] args = joinPoint.getArgs();
try{
String params = new Gson().toJson(args);
sysLog.setParams(params);
}catch (Exception ignored){
}
//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
//设置IP地址
sysLog.setIp(IpUtils.getIpAddr(request));
//用户名
String username = ((TblSysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
sysLog.setUsername(username);
sysLog.setTime(time);
sysLog.setCreateDate(new Date());
//保存系统日志
sysLogService.save(sysLog);
}
}