什么是注解
注解(annotation):元数据,一种代码级别的说明,如同一张标签;
- 可以携带参数;
- 在特定场景下由外部解析产生作用;(在Spring容器启动初始化后由反射解析产生作用)
元注解(用于自定义注解)
@Documented 注解是否将包含在JavaDoc中,用的比较少;
@Retention 什么时候使用该注解,可选值有:
- source 在源代码阶段使用,如@Override,lombok的注解@Data、@EqualsAndHashCode等等。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
String staticConstructor() default "";
}
-
class 在代码编辑阶段使用,用的比较少。
-
runtime 在代码运行期使用,这个很常见。比如swagger、mybatisplus、spring、jackson等等。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ApiModel {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
String value() default "";
IdType type() default IdType.NONE;
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
}
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonIgnoreProperties {
}
@Target 注解用在什么地方,自定义注解时比用。比如上面列举的几个示例都有使用。
- TYPE 表示可以用来修饰类、接口、注解类型或枚举类型;
- PACKAGE 可以用来修饰包;
- PARAMETER 可以用来修饰参数;
- ANNOTATION_TYPE 可以用来修饰注解类型;
- FIELD 可以用来修饰属性(包括枚举的常量);
- CONSTRUCTOR 可以用来修饰构造器;
- LOCAL_VARIABLE 可以用来修饰局部变量;
@Inherited 是否允许子类继承该注解。
注解的解析
要获取类、方法或字段的注解信息,必须通过Java反射技术来获取Annotation对象。
自定义注解类
/**
* 自定义缓存注解
*
* @author wanglingqiang
* @date 2020/2/1 下午3:53
**/
@Retention(RetentionPolicy.RUNTIME) //运行期使用
@Target({ElementType.TYPE, ElementType.METHOD}) //约束,只能配置在类和方法上
public @interface DoSomething {
String key(); // 参数一,缓存的key
String cacheName(); //参数二,缓存的逻辑名称
boolean needLog() default false; //参数三,是否需要记录日志
}
业务类、业务方法中使用自定义的注解
/**
* 用户信息操作服务类
*
* @author wanglingqiang
* @date 2020/2/1 下午1:50
*/
@Service
//@DoSomething(key = "c-key", cacheName = "USER", needLog = true)
public class UserServiceImpl implements IUserService {
private TUserMapper userMapper;
private static final String CACHE_NAME = "USER";
@Override
//#id占位符,动态获取方法参数传递过来的id
//cacheName引用静态常量
@DoSomething(key = "#id", cacheName = CACHE_NAME, needLog = true)
public TUser getUserById(Integer id) {
System.out.println("----in----");
TUser user = userMapper.selectByPrimaryKey(id);
return user;
}
}
测试通过反射获取Annotation对象
@Test
public void testAnno(){
Class clazz = UserServiceImpl.class;
Class anno = DoSomething.class;
DoSomething annotation = null;
if (clazz.isAnnotationPresent(anno)) {
annotation = (DoSomething) clazz.getAnnotation(anno);
System.out.println(annotation.key());
System.out.println(annotation.cacheName());
System.out.println(annotation.needLog());
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(anno)) {
annotation = (DoSomething) method.getAnnotation(anno);
System.out.println(annotation.key());
System.out.println(annotation.cacheName());
System.out.println(annotation.needLog());
}
}
}
SpringMVC中是怎么使用注解的
对于一个http请求,为什么SpringMVC能准确的找到对应的Controller的某个方法进行处理?
### 日常使用代码示例:
@RestController
@RequestMapping("/visitor")
@Api(value = "访客控制类", tags = "访客控制类")
public class VisitorController {
@Autowired
private IVisitorService visitorService;
@ApiOperation(value = "根据id查询", notes = "根据id查询")
@RequestMapping(value = "/getById", method = RequestMethod.GET)
public Result<Visitor> getById(@RequestParam("id") String id) {
return Result.ok(visitorService.getById(id));
}
}
我们在Class类上添加@Controller或@RestController注解,在Method方法上添加@RequestMapping注解,然后就可以通过 “http://域名/项目名/visitor/getById?id=xxx” 访问我们Controller的某个方法了。
底层源码的实现流程:
处理器映射器HandlerMapping的实现
URL注册
http请求过来了,负责处理url的映射。HandlerMapping默认的实现是RequestMappingHandlerMapping。

RequestMappingHandlerMapping的抽象父类AbstractHandlerMethodMapping:
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
public void registerMapping(T mapping, Object handler, Method method) {
if (logger.isTraceEnabled()) {
logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
}
this.mappingRegistry.register(mapping, handler, method);
}
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
}
抽象父类AbstractHandlerMethodMapping实现了InitializingBean接口,Spring容器初始化完后会执行afterPropertiesSet(),再调用initHandlerMethods()。
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
// 获取容器里所有的Bean作为候选Bean,然后循环处理
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//判断是否是Controller、RequestMapping注解
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
/**
* Determine the names of candidate beans in the application context.
* @since 5.1
* @see #setDetectHandlerMethodsInAncestorContexts
* @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors
*/
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
/**
* Determine the type of the specified candidate bean and call
* {@link #detectHandlerMethods} if identified as a handler type.
* <p>This implementation avoids bean creation through checking
* {@link org.springframework.beans.factory.BeanFactory#getType}
* and calling {@link #detectHandlerMethods} with the bean name.
* @param beanName the name of the candidate bean
* @since 5.1
* @see #isHandler
* @see #detectHandlerMethods
*/
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
//isHandler()判断是否存在Controller、RequestMapping注解
if (beanType != null && isHandler(beanType)) {
//取出加了RequestMapping注解的Method,注册
detectHandlerMethods(beanName);
}
}
/**
* Look for handler methods in the specified handler bean.
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
//取出加了RequestMapping注解的Method放入Map,Key是java.lang.reflect.Method,value是URL路径
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//注册
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
private final MappingRegistry mappingRegistry = new MappingRegistry();
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//注册后放入mappingLookup
this.mappingRegistry.register(mapping, handler, method);
}
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//注册后放入mappingLookup,T是URL路径,value是封装的方法信息
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* Return all mappings and handler methods. Not thread-safe.
* @see #acquireReadLock()
*/
public Map<T, HandlerMethod> getMappings() {
return this.mappingLookup;
}
}
isHandler()判断是否是Controller、RequestMapping注解
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
}
public abstract class AnnotatedElementUtils {
public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
//Bean的class的isAnnotationPresent注解的class,判断Bean上是否存在指定注解
return element.isAnnotationPresent(annotationType);
}
// Exhaustive retrieval of merged annotations...
return findAnnotations(element).isPresent(annotationType);
}
}
以上列举了关键步骤的源码,通过以上步骤注册完成,URL和方法的对应关系放到Map集合里面。
HttpServletRequest请求来了映射到对应方法
关键源码如下:
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//获取URL地址
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
//通过URL找到对应的方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
现学现用,写代码示例
通常我们写的业务方法
在写代码前,先看看通常我们写的业务方法:
/**
* 用户信息操作服务类
*
* @author wanglingqiang
* @date 2020/2/1 下午1:50
*/
@Service
public class UserServiceImpl implements IUserService {
@Resource
private CacheService cacheService;
@Autowired
private ILogService logService;
@Autowired
private TUserMapper userMapper;
private static final String CACHE_NAME = "USER";
@Override
public TUser getUserById(Integer id) throws Throwable {
System.out.println("----in----");
//1.根据id从缓存获取数据
TUser cacheUser = cacheService.cacheResult(id, CACHE_NAME);
//2.判断缓存中数据是否存在,存在的话直接返回
if(cacheUser != null) {
return cacheUser;
}
TUser user = null;
try {
//3.缓存中没有,从数据库查询
user = userMapper.selectPrimaryKey(id);
} catch (Exception e) {
//4.查询数据库出现异常记录日志
logService.errorLog(id, UserServiceImpl.class.getSimpleName(), "getUserById()", e.getMessage());
}
//5.查询数据库正常记录日志
logService.infoLog(id, UserServiceImpl.class.getSimpleName(), "getUserById()", e.getMessage());
//6.user不为空放入缓存
if(user != null) {
cacheService.cachePut(id, user, CACHE_NAME);
}
return user;
}
}
代码的问题
这段代码的可以说是“通俗易懂”,所有的代码都写在一起,是我们最常见的代码。但它还有另一个特征是臃肿:
- 业务代码与技术代码耦合,缓存是锦上添花的技术代码,可以没有
- 业务代码与增值业务代码耦合,日志记录是增值代码,可以没有
- 多余的依赖关系,方法的核心功能可以不依赖CacheService、ILogService
带来的问题:
- 可读性差,代码示例中的代码相对简单且有注释,万一复杂且没注释呢
- 可复用性差,缓存、日志部分是通用的,但每个方法里面都要重复写一遍
- 可维护性差,代码都写在一起,容易改出bug
- 灵活性差,日志方法如果加一个参数,那所有使用的地方都要一起改一遍
- 运行期依赖,如果运行期缓存服务挂了,那核心功能也会受影响
改进的思路
- 单一职责原则,一个类或者一个接口只负责唯一一项职责,尽量设计出功能单一的接口
- 迪米特法则,对象应该对其他对象保持最少了解,如果没有必要直接调用就不要有依赖关系
- Spring AOP增加+自定义注解
改进后的代码
大概步骤:
- 创建自定义注解@DoSomething
- 将注解加在需要简化的方法上,加在UserServiceImpl类的getUserById()上
- 增加配置,编写切面类
- 简化业务代码
applicationContext.xml配置文件打开AOP切面功能。
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
利用JDK8获取形参名称,pom.xml添加-parameters
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
定义切面类:
/**
* 定义切面做增强,实现注解的功能
*
* @author wanglingqiang
* @date 2020/2/1 下午4:09
**/
@Component //首先得是Spring容器的Bean
@Aspect //定义为切面
public class DoSomethingAspect {
@Resource
private CacheService cacheService;
@Autowired
private ILogService logService;
/**
* 环绕通知增强
*
* @param pjp 连接点,是对增强方法的封装
* @param dst 自定义注解
* @return Object
* @throws Throwable
*/
//@Around("execution * com.enjoylearning.anno.service.*.*(..)") //指定切点,拦截包下面所有类的方法
//@Around("@annotation(com.enjoylearning.anno.annotation.DoSomething)") //进一步细化,拦截添加有DoSomething注解的切点方法
@Around("@annotation(dst)") //加参数DoSomething dst后,@annotation简化了,下面两行业可以注释掉
public Object doAround(ProceedingJoinPoint pjp, DoSomething dst) throws Throwable {
// Method method = ((MethodSignature) (pjp.getSignature())).getMethod();
// DoSomething dst = (DoSomething) method.getAnnotation(DoSomething.class);
String id = getKey(dst.key(), pjp); //dst.key()是#id,通过spel把占位符转换成实际值
String cacheName = dst.cacheName(); //USER
boolean needLog = dst.needLog(); //true
// System.out.println(id);
// System.out.println(cacheName);
// System.out.println(needLog);
//1.根据id从缓存获取数据
// TUser cacheUser = cacheService.cacheResult(id, CACHE_NAME);
//为了通用性,这里不用TUser用Object
Object resultObj = cacheService.cacheResult(id, cacheName);
//2.判断缓存中数据是否存在,存在的话直接返回
if (resultObj != null) {
return resultObj;
}
try {
//3.缓存中没有,从数据库查询
resultObj = pjp.proceed(); //真正的业务方法
} catch (Exception e) {
//4.查询数据库出现异常记录日志
if (needLog) {
//errorLog(id, UserServiceImpl.class.getSimpleName(), "getUserById", e.getMessage());
errorLog(id, pjp.getSourceLocation().getWithinType().getName(), pjp.getSignature().getName(), e.getMessage());
}
}
//5.查询数据库正常记录日志
if (needLog) {
infoLog(id, pjp.getSourceLocation().getWithinType().getName(), pjp.getSignature().getName(), resultObj.toString());
}
//6.user不为空放入缓存
if (resultObj != null) {
cacheService.cachePut(id, resultObj, cacheName); // jedis.put(key, result, cacheName);
}
return resultObj;
}
private String getKey(String key, ProceedingJoinPoint pjp) {
//Method method = ((MethodSignature) (pjp.getSignature())).getMethod();
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
Parameter[] parameters = method.getParameters(); //利用jdk8获取形参名称getParameters(),或者使用spring的工具类
String[] paramNames = new String[parameters.length];//Java Compiler和pom.xml添加-parameters
for (int i = 0; i < parameters.length; i++) {
// System.out.println(""+parameters[i].isNamePresent());
paramNames[i] = parameters[i].getName();
}
Object[] paramValues = pjp.getArgs();
return SpelParser.getKey(key, paramNames, paramValues);
}
/**
* 记录错误日志
*
* @Param id 业务id
* @Param className Service类名
* @Param method 切面拦截的方法名
* @Param errorInfo 错误信息
* @Return void
*/
private void errorLog(Object id, String className, String method, String errorInfo) {
System.err.println("[ERROR] id=" + id + ", className=" + className + ", method=" + method + ", errorInfo=" + errorInfo);
}
/**
* 记录正常日志
*
* @Param id 业务id
* @Param className Service类名
* @Param method 切面拦截的方法名
* @param returnInfo 响应结果
* @Return void
*/
private void infoLog(Object id, String className, String method, String returnInfo) {
System.out.println("[INFO] id=" + id + ", className=" + className + ", method=" + method + ", returnInfo=" + returnInfo);
}
}
SpEL解析类:
/**
* SpEL解析类
*
* @author wanglingqiang
* @date 2020/2/1 下午4:32
**/
public class SpelParser {
private static ExpressionParser parser = new SpelExpressionParser();
/**
* 获取占位符对应的实际值
*
* @Param key 占位符,如#id
* @Param paramNames 形参名称
* @Param args 参数值
* @Return java.lang.String
**/
public static String getKey(String key, String[] paramNames, Object[] args) {
//将key字符串解析成spel表达式
Expression exp = parser.parseExpression(key);
//初始化上下文
EvaluationContext context = new StandardEvaluationContext();
if (args.length <= 0) {
return null;
}
//将形参参数名和形参参数值配对并赋值到上下文中
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
//根据赋值上下文运算spel表达式
return exp.getValue(context, String.class);
}
public static void main(String[] args) {
String key = "#id+' '+#name"; //el表达式字符串
String[] paramNames = new String[]{"id", "name"};//参数名称
Object[] paramValues = new Object[]{1, "wlq"};
System.out.println(SpelParser.getKey(key, paramNames, paramValues));
}
}
本文介绍Java注解的基本概念及应用场景,特别是元注解的使用,并深入探讨Spring框架中如何利用注解实现请求映射及自定义注解配合AOP进行业务逻辑增强。
617

被折叠的 条评论
为什么被折叠?



