springboot2.7 java8 問題 在使用工廠模式封裝service時,需要通過service的class獲取其類型註解,但是有些工廠類可以取到annotation註解,有些取不到 渠道註解: /** * xxx渠道註解 * */ @Target({ElementType.TYPE}) @ ...
springboot2.7 java8
問題
在使用工廠模式封裝service時,需要通過service的class獲取其類型註解,但是有些工廠類可以取到annotation註解,有些取不到
渠道註解:
/**
* xxx渠道註解
*
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XxxType {
/**
* 渠道的值為XxxTypeEnum枚舉
*/
XxxTypeEnum value();
}
enum:
/**
* 枚舉類
*/
@Getter
@AllArgsConstructor
public enum XxxTypeEnum {
X1("X1", "渠道1"),
X2("X2", "渠道2");
private final String code;
private final String message;
public static XxxTypeEnum getEnumByCode(String code){
for(XxxTypeEnum value:values()){
if(StringUtils.equals(value.code, code)){
return value;
}
}
throw new CommonException("未知的XXX類型");
}
}
工廠類:
@Component
public class XxxServiceFactory implements ApplicationContextAware {
/**
* xxx服務的映射集合
*/
private static final Map<XxxTypeEnum, XxxService> SERVICE_MAP = new HashMap<>();
/**
* 工廠方法獲取服務實現
*
* @param xxxType 渠道
* @return 服務
*/
public static XxxService getService(XxxTypeEnum xxxType) {
XxxService service = SERVICE_MAP.get(xxxType);
if (service == null) {
throw new CommonException("沒有匹配的服務實現類");
}
return service;
}
/**
* 初始化渠道枚舉-xxx服務的映射的映射
*
* @param applicationContext 應用上下文
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(XxxType.class);
if (ObjectUtil.isEmpty(serviceMap)) {
throw new CommonException("服務映射初始化失敗");
}
serviceMap.forEach((key, bean) -> {
if (!(bean instanceof XxxService)) {
throw new CommonException("註解:" + XxxType.class + ",只能用於" + XxxService.class + "的實現類中");
}
XxxService service = (XxxService) bean;
XxxType annotation = service.getClass().getAnnotation(XxxType.class);
// annotation 有時為null
SERVICE_MAP.put(annotation.value(), service);
});
}
}
public interface XxxService {
void test();
void doSomething();
}
渠道1服務實現類
/**
* 渠道1xxx服務實現類
*/
@XxxType(XxxTypeEnum.X1)
@Service
@Slf4j
public class X1XxxServiceImpl implements XxxService {
@Override
public void test() {
log.info("測試渠道1 test");
}
/**
* 此方法需要事務包裹
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void doSomething() {
log.info("測試渠道1 do something");
}
}
渠道2服務實現類
/**
* 渠道1xxx服務實現類
*/
@XxxType(XxxTypeEnum.X2)
@Service
@Slf4j
public class X2XxxServiceImpl implements XxxService {
@Override
public void test() {
log.info("測試渠道2 test");
}
@Override
public void doSomething() {
log.info("測試渠道2 do something");
}
}
解決
以上為部分代碼,項目啟動時,顯示渠道1服務實現類的annotation
為null
,直接npe,找了半天,發現是因為渠道1內的doSomething
方法添加事務註解,因為@Transactional
也是基於aop的,所以此時拿到的bean是代理對象,而代理對象的方法是不會把原來父類中的方法的註解加上去的,所以為null
,所以換了種方式
...
/**
* 初始化渠道枚舉-xxx服務的映射的映射
*
* @param applicationContext 應用上下文
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(XxxType.class);
if (ObjectUtil.isEmpty(serviceMap)) {
throw new CommonException("服務映射初始化失敗");
}
serviceMap.forEach((key, bean) -> {
if (!(bean instanceof XxxService)) {
throw new CommonException("註解:" + XxxType.class + ",只能用於" + XxxService.class + "的實現類中");
}
// XxxService service = (XxxService) bean;
// XxxType annotation = service.getClass().getAnnotation(XxxType.class);
// SERVICE_MAP.put(annotation.value(), service);
List<Annotation> list = AnnotationUtil.scanClass(bean.getClass());
list.stream().filter(annotation -> annotation instanceof XxxType).findFirst().ifPresent(annotation -> {
XxxType xxxType = (XxxType) annotation;
SERVICE_MAP.put(xxxType .value(), (XxxService) bean);
});
});
}
這樣就能正確的獲得註解,並完成工廠類的初始化啦