一.需求背景
为了是业务代码和功能的解耦,或者说减少代码的侵入性,像日志和登录拦截,往往都是在每个微服务中都编写相同的代码,为了使开发迅速和减少代码的编写往往都可以抽成一个starter,需要使用此功能的微服务依赖此jar包即可,无需编写相同的代码
此案列拦截非白名单用户的demo
二.demo 代码
新建springboot项目。作为一个插件被其他服务依赖,其实功能就是一个切面的功能方法抽取
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UserWhiteList {
String key() default "";
String result() default "";
}
@Configuration
@ConditionalOnClass(UserWhiteListProperties.class)
@EnableConfigurationProperties(UserWhiteListProperties.class)
// 注意在此类扫描此jar包需要使用的类,就不要再其他依赖此jar包的微服务扫描,只需关注UserWhiteListAutoConfig 就行
@ComponentScan(basePackages = {"com.example.*"})
public class UserWhiteListAutoConfig {
@Bean("userWhiteListConfig")
@ConditionalOnMissingBean
public String userWhiteListConfig(UserWhiteListProperties properties) {
return properties.getUsers();
}
}
@ConfigurationProperties("userwhitelist")
public class UserWhiteListProperties {
private String users;
public String getUsers() {
return users;
}
public void setUsers(String users) {
this.users = users;
}
}
package com.example.whitelist;
import com.alibaba.fastjson.JSON;
import com.example.whitelist.annotation.UserWhiteList;
import org.apache.commons.beanutils.BeanUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
/**
* @author Iron Man
* @Date 2021-04-21 10:36
*/
@Aspect
@Component
public class UserWhitelistAop {
private Logger log = LoggerFactory.getLogger(UserWhitelistAop.class);
@Resource
private String userWhiteListConfig;
@Pointcut("@annotation(com.example.whitelist.annotation.UserWhiteList)")
public void aopPoint() {
}
@Around("aopPoint()")
public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
Signature signature = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
UserWhiteList whiteList = method.getAnnotation(UserWhiteList.class);
String keyValue = getFiledValue(whiteList.key(), jp.getArgs());
log.info("userWhitelist handler method:{} value:{}", method.getName(), keyValue);
if (null == keyValue || "".equals(keyValue)) {
throw new Exception("未知用户");
}
String[] split = userWhiteListConfig.split(",");
for (String str : split) {
if (keyValue.equals(str)) {
return jp.proceed();
}
}
return returnObject(whiteList, method);
}
private Object returnObject(UserWhiteList whiteList, Method method) throws IllegalAccessException, InstantiationException {
Class<?> returnType = method.getReturnType();
String result = whiteList.result();
if ("".equals(result)) {
return returnType.newInstance();
}
return JSON.parseObject(result, returnType);
}
private String getFiledValue(String filed, Object[] args) {
String filedValue = null;
for (Object arg : args) {
try {
if (null == filedValue || "".equals(filedValue)) {
filedValue = BeanUtils.getProperty(arg, filed);
} else {
break;
}
} catch (Exception e) {
if (args.length == 1) {
return args[0].toString();
}
}
}
return filedValue;
}
}
resourdes 下META-INF spring.actories 下的类,服务启动时默认会引入此类,也可以在微服务中使用@Import注解来替代
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.whitelist.config.UserWhiteListAutoConfig
三.测试
1.新建一个测试的微服务 使用@UserWhiteList 注解切面来拦截请求
@RestController
public class UserController {
@UserWhiteList(key = "userId", result = "{\"code\":\"1111\",\"info\":\"非白名单用户被拦截!\"}")
@GetMapping(path = "/api/queryUserInfo")
public UserVo queryUserInfo(@RequestParam String userId) {
return new UserVo(userId, 20, "徐家汇美罗城五楼");
}
}
http://localhost:8081/api/queryUserInfo?userId=2222222222222
四.其他
1.starter 需要被打成jar包 即 <packaging>jar</packaging>
2.starter 中需要被使用的类需要被Spring托管,即ComponentScan 可以扫描到
3.spring.actories 中EnableAutoConfiguration 和 @Import 具有相同的效果,都会被当前启动类加载