目录
在Spring项目中,有时因为某些需求,并不想直接调用实际的实例方法(业务分离),或者调用实际的实例方法存在困难,这时候,可以自定义代码执行器来间接的调用执行指定的实例方法,来实现业务分离(有点类似于代理设计模式)。中心思想是自定义注解,通过注解将自己的方法添加到自己的命令执行器管理器中,需要使用的时候按方法去取就好了。下面记录下具体的实现过程。
1.自定义注解
首先自定义两个注解,一个用来找到目标接口(@TargetInf),一个用来找到目标接口下的目标抽象方法(@TargetMethod)。使用的时候,这两个注解分别作用在目标接口和目标接口的抽象方法上。
package com.transfar.annotation;
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;
/**
* 自定义代理接口注解
*
* @author 皮锋
* @date 2018年4月3日 上午8:57:51
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetInf {
/**
* 接口
*/
public Class<? extends Object> inf();
}
package com.transfar.annotation;
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;
/**
* 自定义代理方法注解
*
* @author 皮锋
* @date 2018年4月3日 上午9:06:21
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetMethod {
public String method();
}
2.扫描器
创建业务代理扫描器,实现Spring的BeanPostProcessor接口,获得代理执行业务的方法,并将方法添加到命令执行器管理器。BeanPostProcessor接口可以在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。接口中两个方法不能返回null,如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象,因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中。
package com.transfar.core;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import com.transfar.annotation.TargetInf;
import com.transfar.annotation.TargetMethod;
import lombok.extern.slf4j.Slf4j;
/**
* 业务代理扫描器,获得代理执行业务的方法,添加到命令执行器管理器
*
* @author 皮锋
* @date 2018年4月3日 上午9:09:10
*/
@Slf4j
@Component("businessAgencyScaner")
public class BusinessAgencyScaner implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<? extends Object> clazz = bean.getClass();
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces != null && interfaces.length > 0) {
// 扫描类的所有接口父类
for (Class<?> interFace : interfaces) {
// 判断是否为代理执行业务的接口类
TargetInf vService = interFace.getAnnotation(TargetInf.class);
if (vService == null) {
continue;
}
// 找出命令方法
Method[] methods = interFace.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
TargetMethod vMethod = method.getAnnotation(TargetMethod.class);
if (vMethod == null) {
continue;
}
// 接口
Class<? extends Object> clz = vService.inf();
// 方法
String mtd = vMethod.method();
Invoker invoker = Invoker.valueOf(method, bean);
if (InvokerHolder.getInvoker(clz, mtd) == null) {
InvokerHolder.addInvoker(clz, mtd, invoker);
} else {
log.error("重复注册执行器!");
}
}
}
}
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
3.命令执行器
创建命令执行器,用于执行命令,此处指特定要执行的方法。
package com.transfar.core;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import lombok.extern.slf4j.Slf4j;
/**
* 命令执行器
*
* @author 皮锋
* @date 2018年4月3日 上午8:45:53
*/
@Slf4j
public class Invoker {
/**
* 方法
*/
private Method method;
/**
* 目标对象
*/
private Object target;
public static Invoker valueOf(Method method, Object target) {
Invoker invoker = new Invoker();
invoker.setMethod(method);
invoker.setTarget(target);
return invoker;
}
/**
* @description 执行
* @param paramValues
* @return Object
*/
public Object invoke(Object... paramValues) {
try {
return method.invoke(target, paramValues);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
log.error("", e);
}
return null;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
}
4.命令执行器管理器
创建命令执行器管理器,用于命令执行器的添加和获取。命令执行器管理器其实是一个map集合,里面存放着不重复的各个命令执行器,通过key进行添加和获取。
package com.transfar.core;
import java.util.HashMap;
import java.util.Map;
/**
* 命令执行器管理器
*
* @author 皮锋
* @date 2018年4月3日 上午8:53:09
*/
public class InvokerHolder {
/**
* 命令执行器
*/
private static Map<Class<? extends Object>, Map<String, Invoker>> invokers = new HashMap<>();
/**
* @description 获取执行器
* @param clazz
* @param method
* @return invoker
*/
public static Invoker getInvoker(Class<? extends Object> clazz, String method) {
Map<String, Invoker> map = invokers.get(clazz);
if (map != null) {
return map.get(method);
}
return null;
}
/**
* @description 添加执行器
* @param clazz
* @param method
* @param invoker
*/
public static void addInvoker(Class<? extends Object> clazz, String method, Invoker invoker) {
Map<String, Invoker> map = invokers.get(clazz);
if (map == null) {
map = new HashMap<>();
invokers.put(clazz, map);
}
map.put(method, invoker);
}
}
5. 测试使用
自定义注解、扫描器、执行器和执行器管理器在第一次写完之后,后续不需要再动,对业务代码没有任何侵入,程序员可以只关注具体业务的书写。当需要扩展的时候,也很容易实现。
这里只列举说明重点的几个接口和类:
1.在要执行的方法所对应的接口以及方法上分别加上注解,扫描器就能扫描到这个方法,然后添加到命名执行器管理器。
package com.transfar.service;
import com.transfar.annotation.TargetInf;
import com.transfar.annotation.TargetMethod;
/**
* 业务层接口
*
* @author 皮锋
* @date 2019年1月29日 下午5:05:37
*/
@TargetInf(inf = ITestService.class)
public interface ITestService {
String test(String param);
// 加了注解的方法将会添加到命令执行器管理器,注册到bean容器
@TargetMethod(method = "target")
String target(String param);
}
2. 通过命令执行器管理器,传入要执行的接口和抽象方法,获得指定的命令执行器,执行器就会执行对应这个抽象方法的具体实现方法,并返回方法执行结果。
package com.transfar.service.impl;
import org.springframework.stereotype.Service;
import com.transfar.core.Invoker;
import com.transfar.core.InvokerHolder;
import com.transfar.service.ITestService;
/**
* 业务层
*
* @author 皮锋
* @date 2019年1月29日 下午5:05:52
*/
@Service
public class TestServiceImpl implements ITestService {
@Override
public String test(String param) {
// 通过命令执行器管理器,获取指定的命令执行器
Invoker invoker = InvokerHolder.getInvoker(ITestService.class, "target");
// 执行命令,返回执行结果
Object object = invoker.invoke(new Object[] { param });
return object.toString();
}
@Override
public String target(String param) {
return "目标方法执行了,param = " + param;
}
}
package com.transfar.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.transfar.service.ITestService;
/**
* 测试控制器
*
* @author 皮锋
* @date 2019年1月29日 下午5:04:38
*/
@RestController
@RequestMapping("test")
public class TestController {
@Autowired
private ITestService iTestService;
/**
* url地址:http://localhost:8080/test/test?param=pifeng
*
* @param param
* @return string
*/
@GetMapping("test")
public String test(@RequestParam(name = "param") String param) {
return this.iTestService.test(param);
}
}
在浏览器输入地址:http://ocalhost:8080/test/test?param=pifeng,会返回如下结果。
6.延伸
根据需求,这里自定义的注解,可以按模块和方法划分,实现思路一样,这样能实现模块之间的解耦。
7.源码下载
https://download.youkuaiyun.com/download/paulangsky/10944491