不知道大家都没有在web开发过程中有遇到过这种情况,就是前端需要一个接口,我们都要写对应的
Controller
接口配置对应的url
,那有没有一种方式我们只需要写一个主的Controller
,是否可以通过Spring IOC
的依赖查找来实现接口的调用呢?
Spring IOC
大家可能都知道Spring的两大核心 一个是IOC(控制反转)
,一个AOP(面向切面)
。
IOC
是一种设计思想,在java开发中,IOC
意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部进行控制,
(1)基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。
(2)实现IOC
的技术手段:DI(依赖注入)
和 DL(依赖查找)
,Spring
中的核心机制就是DI(依赖注入)
。通俗来说就是ServiceImpl
类中,有Dao
对象,那就是ServiceImpl
依赖了Dao
。
依赖注入(Depedency Injection)
意思是自身对象中的内置对象是通过注入的方式进行创建。依赖注入有两种实现方式:Setter
方式(传值方式)和构造器方式(引用方式)。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean
中的setter
)或者是构造子传递给需要的对象。相对于IoC
而言,依赖注入(DI)
更加准确地描述了IoC
的设计理念。所谓依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
依赖查找(Dependency Lookup)
依赖查找一共有两种类型:依赖拖拽(DP)
和 上下文依赖查找(CDL)
我们实现用注解的方式来编写类restful
接口用到的就是上下文依赖查找方式:
上下文依赖查找的实现方式:
(1)使用beanFactory.getBean("")
来获取。
(2)使用applicationContext.getBeansWithAnnotation(class)
自定义实现Restful接口
实现类Restful接口我们可以从这几步开始:
- 自定义注解
- 编写公共接口
- 使用上下文查找来实现接口查找
自定义注解
自定义两个注解一个作用在类上,一个作用在方法上:
ApiRestService
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiRestService {
}
ApiRestMethod
@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiRestMethod {
String apiCode() default "";
}
编写web统一调用接口
@RestController
@RequestMapping("/rest/service")
@Slf4j
public class RestApiServiceController {
/**
* 服务入口
* CrossOrigin实现跨域访问
* @param apiQuaryParam
* @return AjaxResult
*
*/
@CrossOrigin(origins = "*", maxAge = 3600)
@PostMapping(value = "/api")
@ResponseBody
public AjaxResult api(@RequestBody(required = false) RestApiQuaryParam apiQuaryParam) {
return ApiUtil.getApi(apiQuaryParam);
}
//请求参数返回示例
/**
* {
* "data": {
* "storeNo": "050166",
* "userName": "admin",
* "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdG9yZU5vIjoiMDUwMTY2IiwiZXhwIjoxNjM5NDY5Mzg3LCJ1dWlkIjoiMTIzMTI0MTI0MTMiLCJ1c2VybmFtZSI6ImFkbWluIn0.qbkEcfcq1mZdtH6Cu4UBNaddAVP-1hOHcKJiuqeQVjA"
* },
* "message": "操作成功",
* "status": 200
* }
* */
}
注意:前端传递需要传递给后端服务接口apiCode
Spring IOC过滤查找到对应后端服务
编写Spring IOC
工具类
@Slf4j
@Component
public class SpringIocHelper implements ApplicationContextAware, InitializingBean {
/**
* 实现ApplicationContextAware接口,通过setApplicationContext回调注入applicationContext
*/
private static ApplicationContext applicationContext;
/**
* api method缓存
* 实现InitializingBean,通过afterPropertiesSet方法初始化缓存
*/
private static Map<String,Map<Object, Method>> apiMap = new HashMap(256);
public static AjaxResult invoke(String apiCode, RestApiQuaryParam apiQuaryParam) {
//获取服务接口
Map<Object, Method> methodMap = match(apiCode);
if (CollectionUtils.isEmpty(methodMap)) {
return AjaxResult.error(1010, String.format("请检查apiCode【%s】是否正确", apiCode));
}
for (Object service : methodMap.keySet()) {
Method method = methodMap.get(service);
try {
Class<?>[] paramClass = method.getParameterTypes();
if (paramClass.length > 0) {
if (paramClass[0].getName().equals(UhiApiQuaryParam.class.getName())) {
return AjaxResult.success(method.invoke(service, apiQuaryParam));
}
}
return AjaxResult.success(method.invoke(service));
} catch (RestApiBaseException | InvocationTargetException | IllegalArgumentException | IllegalAccessException e) {
log.error("service paramJson:{}", apiQuaryParam != null ? JSONObject.toJSONString(apiQuaryParam) : "", e);
if (e instanceof InvocationTargetException) {
return AjaxResult.error(1004, ((InvocationTargetException) e).getTargetException().getMessage());
}
}
}
return AjaxResult.error(1000, "服务异常,请联系服务人员");
}
/**
* 寻址查到apiCOde对应的类以及方法名称
*
* @param apiCode 接口编码
* @return Map
*/
private static Map<Object, Method> match(String apiCode) {
return apiMap.get(apiCode);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringIocHelper.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() {
//会触发getBean 从而保证所有ApiService对应的bean比SpringHelper先初始化完成
Map<String, Object> map = applicationContext.getBeansWithAnnotation(ApiService.class);
for (Object service : map.values()) {
//获取本类中的所有方法
Method[] methods = service.getClass().getDeclaredMethods();
for (Method method : methods) {
//获取该方法的上所有的注解
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
//判断是否存在该注解
if (!annotation.toString().contains(ApiRestMethod.class.getSimpleName())) {
continue;
}
ApiRestMethod apiMethod = ApiRestMethod.class.cast(annotation);
//判断是否存在数据
String apiCode = apiMethod.apiCode();
if (apiMap.containsKey(apiCode)) {
throw new RuntimeException("find too many match method [" + apiCode + "]");
} else {
Map<Object, Method> methodMap = new HashMap<>();
methodMap.put(service, method);
apiMap.put(apiCode, methodMap);
}
}
}
}
}
}
注意:需在启动类后加入获取上下文:
//复制上下文
SpringHelper.setApplicationContext(applicationContext);
服务端类注解使用
如下图所示:
@Service
@ApiRestService
public class LoginApiService {
/**
* api登录调用接口获取token
*
* @param apiQuaryParam 请求参数
*/
@ApiRestMethod(apiCode = "REST_LOGIN")
public Map<String, Object> login(RestApiQuaryParam apiQuaryParam) throws RestApiBaseException {
Map<String, Object> objectMap = new HashMap<>();
try {
objectMap.put("token", TokenUtils.token(apiQuaryParam.getUserName(), apiQuaryParam.getUuid(), apiQuaryParam.getStoreNo()));
objectMap.put("userName", apiQuaryParam.getUserName());
objectMap.put("storeNo", apiQuaryParam.getStoreNo());
return objectMap;
} catch (Exception e) {
throw new RestApiBaseException(e.getMessage());
}
}
}