1.前言
自定义注解目前在我使用过的项目中,主要用用作日志丰富,参数处理,其核心还是借助于Spring的AOP进行实现,本文将结合具体代码演示简单的自定义注解实现流程。
2.实现
2.1 定义User
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String name; }
2.2 定义UserDAO
@Component public class UserDao { public User findUserById(Integer id) { if(id > 10) { return null; } return new User(id, "user-" + id); } }
2.3 定义UserService
@Service public class UserService { private final UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; } public User findUserById(Integer id) { return userDao.findUserById(id); } }
2.4 定义Controller
@RequestMapping(value = "user/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public User findUser(@PathVariable("id") Integer id) { return userService.findUserById(id); }
此时浏览器访问:http://{domain}/user/1即可出现对应效果
{ "id": 1, "name": "user-1" }
2.5 定义自定义注解
import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CustomAnnotation { String name() default ""; String value() default ""; }
说明:
- @interface 不是interface,是注解类 定义注解
- Documented
- 这个Annotation可以被写入javadoc
- @Retention
- 修饰注解,是注解的注解,称为元注解
- SOURCE, // 编译器处理完Annotation后不存储在class中
- CLASS, // 编译器把Annotation存储在class中,这是默认值
- RUNTIME // 编译器把Annotation存储在class中,可以由虚拟机读取,反射需要
- @Target
- 注解的作用目标
- @Target(ElementType.TYPE) //接口、类、枚举、注解
- @Target(ElementType.FIELD) //字段、枚举的常量
- @Target(ElementType.METHOD) //方法
- @Target(ElementType.PARAMETER) //方法参数
- @Target(ElementType.CONSTRUCTOR) //构造函数
- @Target(ElementType.LOCAL_VARIABLE) //局部变量
- @Target(ElementType.ANNOTATION_TYPE) //注解
- @Target(ElementType.PACKAGE) //包
- 可以定义多个方法,每个方法在使用时参照下面的Controller使用即可,实际就是类似于@PostMapping这样的注解中使用过的value,method,produces等,如下:
2.6 AOP+Controller使用自定义注解
@CustomAnnotation(name = "findUser", value = "根据ID查找用户") @RequestMapping(value = "user/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public User findUser(@PathVariable("id") Integer id) { return userService.findUserById(id); }
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component @Aspect public class TestLogAspect { @Pointcut("@annotation(cn.test.CustomAnnotation)") private void pointcut() {} @Before("pointcut() && @annotation(annotation)") public void advice(JoinPoint joinPoint, CustomAnnotation annotation) { System.out.println( "类名:[" + joinPoint.getSignature().getDeclaringType().getSimpleName() + "],方法名:[" + joinPoint.getSignature().getName() + "]-日志内容-[" + annotation.value() + ", "+annotation.name()+ "]"); } }
3.总结
自定义注解其核心是借助于:@Target 和 @Rentention,@Documented组合实现,其实现还是需要依赖于Spring的AOP进行具体体现,除了上面的用作日志拦截,还可以自定义:数据验证注解,权限注解,缓存注解等多种用途,但其实现基本都遵循上述步骤。
4.自定义注解+过滤器实现登陆相关
4.1 定义自定义注解@Login
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.zhicall.majordomo.core.common.enums.YesOrNo; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Login { YesOrNo value(); }
4.2 过滤器匹配
package com.zhicall.majordomo.core.security.interceptor; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.method.HandlerMethod; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import com.alibaba.fastjson.JSON; import com.zhicall.care.realtime.util.ResultMessageBuilder; import com.zhicall.care.realtime.util.ResultMessageBuilder.ResultMessage; import com.zhicall.care.system.basic.BeanFactory; import com.zhicall.majordomo.core.common.constant.GlobalCst; import com.zhicall.majordomo.core.common.enums.YesOrNo; import com.zhicall.majordomo.core.security.annotation.Login; import com.zhicall.majordomo.core.security.constant.Cst; import com.zhicall.majordomo.core.security.util.UserAuthHelper; public class UserLoginInterceptor extends HandlerInterceptorAdapter { @SuppressWarnings({ "unchecked", "rawtypes" }) protected RedisTemplate<String, String> redisTemplate = (RedisTemplate) BeanFactory.getInstance().getBean("redisTemplate"); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod handlerMethod = (HandlerMethod) handler; Login login = handlerMethod.getMethodAnnotation(Login.class); // 方法被 @Login(YesOrNo.No)标记 表示不需要登陆即可访问 否者都要登录 if (login != null && YesOrNo.NO.equals(login.value())) { return true; } // 做鉴权 ...... } }
4.3 Controller中具体使用
@Login(YesOrNo.NO) @RequestMapping(value = "/filter", method = RequestMethod.POST) public @ResponseBody ResultMessageBuilder.ResultMessage filter(String companyId, String code) { List<TabInfoVo> merchantsInfoDtos = new ArrayList<>(); merchantsInfoDtos = historyTradeService.filter(companyId, code); return ok("查询成功", merchantsInfoDtos); }