一、多态的实现方式
本文主要是使用自定义注解和反射机制,满足多业务的实现上的多态。
二、类的设计
三、自定义注解
3.1、类的实现
注意,这里增加了spring boot 的注解@Component,是为后期加载到spring容器里。
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标识端点类,类似springboot中@RestController的作用,
* 已组合@Component,在springboot启动时将自动创建端点类的单例实例
* 每个不同的应用id仅允许存在一个端点类,若有重复的应用Id,启动扫描将抛出错误并终止进程
* 每个端点类中,除@OnCommand之外的每个端点仅允许使用一次,若一个端点类中有重复使用的端点,启动扫描将抛出错误并终止进程
* 每个端点类中,可使用多个@OnCommand注解,但它的value值,即cmd,必须唯一,若一个端点类中有value重复的@OnCommand端点,启动扫描将抛出错误并终止进程
*
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Endpoints {
String appId() default "";
}
3.2、方法的重写
这里自定义了两个方法注解,在实现类的方法加上它即实现方法的重写。类似,接口和实现类的方法重写。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OnOpen {
String value() default "";
String appId() default "";
String endpointName() default "onOpen";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClose {
String value() default "";
String appId() default "";
String endpointName() default "onClose";
}
四、定义接口
import com.xxx.ws.test.service.dto.RequestContent;
public interface EndpointService {
void doOnOpen(RequestContent requestContent);
void doOnClose(RequestContent requestContent);
}
五、接口的实现
5.1、default默认实现
@Slf4j
@Endpoints(appId = "default")
public class DefaultEndpointImpl {
@OnOpen
public void onOpen(String message) {
log.info("default onOpen方法{}", message);
}
@OnClose
public void onClose(String message) {
log.info("default onClose方法{}", message);
}
}
5.2、admin业务的实现
@Slf4j
@Endpoints(appId = "admin")
public class AdminEndpointImpl {
@OnOpen
public void onOpen(String message) {
log.info("admin onOpen方法{}", message);
}
@OnClose
public void onClose(String message) {
log.info("admin onClose方法{}", message);
}
}
六、端点注册
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自动遍历所有的带@Endpoints注解的类,对每个端点进行注册
*/
@Slf4j
@Component
public class EndpointRegistry extends ApplicationObjectSupport {
private ConcurrentHashMap<String, Method> methodMapper = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, Object> instanceMapper = new ConcurrentHashMap<>();
private static final String METHOD_FORMAT_STR = "%s-%s";
private static final String ON_OPEN = "onOpen";
private static final String ON_CLOSE = "onClose";
public Object getEndpointInstance(String instanceKey) {
if (StringUtils.isEmpty(instanceKey)) {
return null;
}
Object instance = instanceMapper.get(instanceKey);
return instance;
}
public Method getOnOpen(String appId) {
return getMethod(ON_OPEN, appId);
}
public Method getOnClose(String appId) {
return getMethod(ON_CLOSE, appId);
}
/**
* 应用初始化的时候加载
*/
@PostConstruct
public void init() {
ApplicationContext context = getApplicationContext();
try {
// 找出所有@Endpoints注解的,需要注册端点的类的名称
String[] endpointBeanNames = context.getBeanNamesForAnnotation(Endpoints.class);
Set<Class<?>> endpointClasses = new HashSet<>(endpointBeanNames.length);
// 获取每个类的Class,后面需要遍历类内方法
for (String beanName : endpointBeanNames) {
endpointClasses.add(context.getType(beanName));
}
for (Class<?> endpointClass : endpointClasses) {
// 注册每一个类内的端点
registerEndpoints(context, endpointClass);
}
} catch (Exception e) {
log.error("endpoints init Error!", e);
System.exit(0);
}
}
private void registerEndpoints(ApplicationContext context, Class<?> endpointClass) {
Class currentClazz = endpointClass;
while (!currentClazz.equals(Object.class)) {
// 若当前类没有@Endpoints注解,则跳过,继续遍历其父类
if (!currentClazz.isAnnotationPresent(Endpoints.class)) {
currentClazz = currentClazz.getSuperclass();
continue;
}
// 从该类的Endpoints注解中获取appId
Endpoints endpoints = (Endpoints) currentClazz.getDeclaredAnnotation(Endpoints.class);
String appId = endpoints.appId();
// appId不允许重复
if (instanceMapper.containsKey(appId)) {
throw new IllegalStateException(String.format("exist duplicated appId: [%s]", appId));
} else {
Object bean = context.getAutowireCapableBeanFactory().getBean(currentClazz);
if (null != bean) {
instanceMapper.put(appId, bean);
} else {
log.warn(String.format("no bean instance can be found: [%s][%s]", appId, currentClazz.getName()));
}
}
// 获取类中所有的方法列表
Method[] methods = currentClazz.getDeclaredMethods();
// 检查每一个方法,注册类内的所有端点
this.registerMethods(methods, appId);
// 遍历该类的所有父类,查询是否有覆盖的方法,若检查父类已是Object时,结束遍历
currentClazz = currentClazz.getSuperclass();
}
}
private void registerMethods(Method[] methods, String appId) {
for (Method method : methods) {
// 仅注册含有自定义注解的方法
if (method.getAnnotation(OnOpen.class) != null) {
this.registerMethod(method,
assembleMethodKey(method.getAnnotation(OnOpen.class).endpointName(), appId));
} else if (method.getAnnotation(OnClose.class) != null) {
this.registerMethod(method,
assembleMethodKey(method.getAnnotation(OnClose.class).endpointName(), appId));
}
}
}
private void registerMethod(Method method, String endpointName) {
// 若该方法不是public方法,抛出方法不是public错误
this.checkPublic(method);
// 若已存在,且已存在方法与当前方法不是Override关系时,抛出端点重复定义错误
if (methodMapper.containsKey(endpointName)) {
if (!isMethodOverride(method, methodMapper.get(endpointName))) {
Method existMethod = methodMapper.get(endpointName);
throw new IllegalArgumentException(
String.format("endpoint method duplicated : [%s][%s] conflicts with [%s][%s]",
method.getDeclaringClass().getName(),
method.getName(),
existMethod.getDeclaringClass().getName(),
existMethod.getName()));
}
} else {
// 未注册过,则注册
methodMapper.put(endpointName, method);
}
}
private void checkPublic(Method m) {
if (!Modifier.isPublic(m.getModifiers())) {
throw new IllegalStateException(String.format(
"method access denied: method not public [%s][%s]",
m.getDeclaringClass().getName(),
m.getName()));
}
}
private boolean isMethodOverride(Method method1, Method method2) {
return (method1.getName().equals(method2.getName())
&& method1.getReturnType().equals(method2.getReturnType())
&& Arrays.equals(method1.getParameterTypes(), method2.getParameterTypes()));
}
private Method getMethod(String endpointName, String appId) {
return methodMapper.get(assembleMethodKey(endpointName, appId));
}
/**
* {appId}-{端点类型},如:admin-onOpen
*
* @param endpointName onOpen
* @param appId admin
* @return
*/
private String assembleMethodKey(String endpointName, String appId) {
return String.format(METHOD_FORMAT_STR, appId, endpointName);
}
}
七、端点Endpoint的工厂类
import com.xxx.ws.test.service.dto.RequestContent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
@Slf4j
@Service
public class EndpointServiceFactory implements EndpointService {
@Autowired
private EndpointRegistry endpointRegistry;
@Override
public void doOnOpen(RequestContent requestContent) {
String appId = requestContent.getAppId();
// 获取应用ID对应的类和方法
Object instance = endpointRegistry.getEndpointInstance(appId);
Method onOpen = endpointRegistry.getOnOpen(appId);
try {
onOpen.invoke(instance, requestContent.getMessage());
} catch (Exception e) {
log.error("出现异常", e);
}
}
@Override
public void doOnClose(RequestContent requestContent) {
String appId = requestContent.getAppId();
// 获取应用ID对应的类和方法
Object instance = endpointRegistry.getEndpointInstance(appId);
Method onOpen = endpointRegistry.getOnClose(appId);
try {
onOpen.invoke(instance, requestContent.getMessage());
} catch (Exception e) {
log.error("出现异常", e);
}
}
}
- RequestContent.java请求上下文
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RequestContent {
/**
* 应用类型
*/
private String appId;
/**
* 报文体
*/
private String message;
}
八、使用示例
@Autowired
private EndpointService endpointService;
@PostMapping("/api/v1/endpoint/test/{appId}")
public ResponseEntity<?> testEndpoint(@PathVariable String appId) {
endpointService.doOnOpen(new RequestContent(appId, "test open method"));
endpointService.doOnClose(new RequestContent(appId, "test close method"));
return ResponseEntity.ok(DateUtil.now());
}