Java動(dòng)態(tài)獲取實(shí)現(xiàn)類的方式詳解
應(yīng)用場(chǎng)景
支付的時(shí)候,有不同的渠道,比如微信還是支付寶?
這個(gè)時(shí)候,就需要根據(jù)渠道類型,選擇走哪個(gè)渠道。
讀
先來看下讀
/**
* 根據(jù)接口和注解@SupportCodes的值,獲取接口實(shí)現(xiàn)類
*
* @param interfaceClass 接口
* @param sopportCode 接口實(shí)現(xiàn)類的注解@SupportCodes的值
* @return 接口實(shí)現(xiàn)類
*/
public static <T> T getServiceImpl(Class<T> interfaceClass, String sopportCode)
{
// 如果沒有初始化,則初始化
if (null == serviceImplFactoryMap.get(interfaceClass)) {
// 初始化
init(interfaceClass);
if (null == serviceImplFactoryMap) {
return null;
}
}
// 如果初始化后,還是沒有,則返回null
if (null == serviceImplFactoryMap.get(interfaceClass)) {
return null;
}
// 返回接口實(shí)現(xiàn)類
return (T)((Map)serviceImplFactoryMap.get(interfaceClass)).get(sopportCode);
}
為什么要先看讀?讀是需求,因?yàn)橛凶x的需求,才需要實(shí)現(xiàn)寫的功能。
重點(diǎn)來看入?yún)?,任何功能,無非是入?yún)⒑统鰠ⅰ?/p>
入?yún)ⅲ航涌诤颓来a。
出參:渠道實(shí)現(xiàn)類。
那具體怎么實(shí)現(xiàn)呢?
類似這種需求,基本上都是map。key是渠道代碼,value實(shí)現(xiàn)類。
大概的實(shí)現(xiàn)思路,基本上就是這樣。存儲(chǔ)用到的數(shù)據(jù)結(jié)構(gòu)是map。
寫
剛才看了讀方法,再來看下寫。
讀,無非是從map讀。寫,也一樣,無非是把數(shù)據(jù)寫到map。
那具體怎么寫呢?直接看代碼
/**
* 初始化
*
* @param interfaceClass 接口
* @author javaself
*/
protected static <T> void init(Class<T> interfaceClass) {
log.info("begin to init " + interfaceClass);
synchronized (FactoryUtil.class) {
try {
// 如果已經(jīng)初始化,則返回
if (null != serviceImplFactoryMap.get(interfaceClass)) {
return;
}
Map<String, Object> serviceMap = new HashMap();
// 獲取所有實(shí)現(xiàn)類
Map<String, T> beans = appContext.getBeansOfType(interfaceClass); //實(shí)現(xiàn)類名字作為key,實(shí)現(xiàn)類實(shí)例作為value
Set<Entry<String, T>> entrySet = beans.entrySet();
Iterator<Entry<String, T>> iterator = entrySet.iterator();
// 遍歷所有實(shí)現(xiàn)類,獲取注解@SupportCodes的值,作為key,實(shí)現(xiàn)類實(shí)例作為value
while (iterator.hasNext()) {
Object interfaceServiceImpl = ((Entry)iterator.next()).getValue();
SupportCodes annotationValue = (SupportCodes)interfaceServiceImpl.getClass().getAnnotation(SupportCodes.class);
if (null != annotationValue) {
for (String flag : annotationValue.value()) { //可能有多個(gè)注解值,所以遍歷
//寫到map: key=注解值,value=實(shí)現(xiàn)類實(shí)例
serviceMap.put(flag, interfaceServiceImpl);
log.info(flag + "=>" + interfaceServiceImpl.getClass());
}
}
}
//寫到map: key=接口,value=上面的map
serviceImplFactoryMap.put(interfaceClass, serviceMap);
} catch (Exception e) {
log.error("init failed", e);
throw new RuntimeException(e);
}
}
log.info(interfaceClass + "inited");
}
寫方法的入?yún)⑹牵航涌凇?/p>
也就是說,根據(jù)接口,可以獲取不同實(shí)現(xiàn)類。具體是基于spring的獲取bean的功能。
拿到不同實(shí)現(xiàn)類之后,再根據(jù)注解代碼,組裝成key/value寫到map。key是注解代碼,value是實(shí)現(xiàn)類。
注解代碼是干嘛用的?就是前面的渠道代碼。屬于業(yè)務(wù)代碼。具體是基于自定義注解實(shí)現(xiàn)。自定義注解用的時(shí)候,有幾步,首先,是自定義注解,其次,注解到類上面去,最后,讀注解的值的時(shí)候直接使用spring提供的工具類即可。
到這里其實(shí)基本上已經(jīng)寫完了,核心思路就是怎么玩弄渠道代碼和渠道實(shí)現(xiàn)類。
何時(shí)寫?
讀的時(shí)候,寫。也就是說,第一次用到的時(shí)候,寫。而不是啟動(dòng)項(xiàng)目的時(shí)候,寫。
只需要寫一次即可,也就是說,只在第一次用到的時(shí)候,才寫。后面直接用即可。
寫的時(shí)候,注意并發(fā)問題。類似這種只寫一次,但是又需要注意并發(fā)問題的情況,一般直接使用同步關(guān)鍵字即可。
何時(shí)讀?
支付系統(tǒng)里面根據(jù)接口和渠道代碼,決定走哪個(gè)渠道實(shí)現(xiàn)類
IGetPayInstructionService payService = FactoryUtil.getServiceImpl(IGetPayInstructionService.class, channelCode);
渠道代碼的值,從哪里來?網(wǎng)關(guān)入口會(huì)判斷。具體的話,用微信還是支付寶掃碼的時(shí)候,可以根據(jù)請(qǐng)求頭判斷是哪個(gè)app。
完整代碼
工具類
package com.xxx.commons.factory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.stereotype.Component;
/**
* 工廠工具類:用于獲取接口實(shí)現(xiàn)類
*
* ---
* 應(yīng)用場(chǎng)景:當(dāng)一個(gè)接口有多個(gè)實(shí)現(xiàn)類時(shí),通過該工具類獲取對(duì)應(yīng)的實(shí)現(xiàn)類。
* 不同實(shí)現(xiàn)類,通過注解@SupportCodes來區(qū)分。獲取的時(shí)候,也是通過@SupportCodes的值來獲取。
*
* ---
* 舉例說明
*
* 不同渠道的支付接口,都實(shí)現(xiàn)了IPayService接口,如下:微信支付、支付寶支付
*
* @author javaself
*/
@Component
public class FactoryUtil
implements ApplicationContextAware
{
private static Log log = LogFactory.getLog(FactoryUtil.class);
private static Map<Class<?>, Map<String, Object>> serviceImplFactoryMap = new HashMap();
private static AbstractApplicationContext appContext;
/**
* 根據(jù)接口和注解@SupportCodes的值,獲取接口實(shí)現(xiàn)類
*
* @param interfaceClass 接口
* @param sopportCode 接口實(shí)現(xiàn)類的注解@SupportCodes的值
* @return 接口實(shí)現(xiàn)類
*/
public static <T> T getServiceImpl(Class<T> interfaceClass, String sopportCode)
{
// 如果沒有初始化,則初始化
if (null == serviceImplFactoryMap.get(interfaceClass)) {
// 初始化
init(interfaceClass);
if (null == serviceImplFactoryMap) {
return null;
}
}
// 如果初始化后,還是沒有,則返回null
if (null == serviceImplFactoryMap.get(interfaceClass)) {
return null;
}
// 返回接口實(shí)現(xiàn)類
return (T)((Map)serviceImplFactoryMap.get(interfaceClass)).get(sopportCode);
}
/**
* 初始化
*
* @param interfaceClass 接口
* @author javaself
*/
protected static <T> void init(Class<T> interfaceClass) {
log.info("begin to init " + interfaceClass);
synchronized (FactoryUtil.class) {
try {
// 如果已經(jīng)初始化,則返回
if (null != serviceImplFactoryMap.get(interfaceClass)) {
return;
}
Map<String, Object> serviceMap = new HashMap();
// 獲取所有實(shí)現(xiàn)類
Map<String, T> beans = appContext.getBeansOfType(interfaceClass); //實(shí)現(xiàn)類名字作為key,實(shí)現(xiàn)類實(shí)例作為value
Set<Entry<String, T>> entrySet = beans.entrySet();
Iterator<Entry<String, T>> iterator = entrySet.iterator();
// 遍歷所有實(shí)現(xiàn)類,獲取注解@SupportCodes的值,作為key,實(shí)現(xiàn)類實(shí)例作為value
while (iterator.hasNext()) {
Object interfaceServiceImpl = ((Entry)iterator.next()).getValue();
SupportCodes annotationValue = (SupportCodes)interfaceServiceImpl.getClass().getAnnotation(SupportCodes.class);
if (null != annotationValue) {
for (String flag : annotationValue.value()) { //可能有多個(gè)注解值,所以遍歷
//寫到map: key=注解值,value=實(shí)現(xiàn)類實(shí)例
serviceMap.put(flag, interfaceServiceImpl);
log.info(flag + "=>" + interfaceServiceImpl.getClass());
}
}
}
//寫到map: key=接口,value=上面的map
serviceImplFactoryMap.put(interfaceClass, serviceMap);
} catch (Exception e) {
log.error("init failed", e);
throw new RuntimeException(e);
}
}
log.info(interfaceClass + "inited");
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
appContext = (AbstractApplicationContext)applicationContext;
}
}
注解類
package com.xxx.commons.factory;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SupportCodes
{
String[] value();
}
接口實(shí)現(xiàn)類
/**
* 微信公眾號(hào)支付
*
*/
@SupportCodes("WeChatPay")
@Service
public class WeChatPayService implements IGetPayInstructionService {
以上就是Java動(dòng)態(tài)獲取實(shí)現(xiàn)類的方式詳解的詳細(xì)內(nèi)容,更多關(guān)于Java動(dòng)態(tài)獲取實(shí)現(xiàn)類的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringCloud 搭建企業(yè)級(jí)開發(fā)框架之實(shí)現(xiàn)多租戶多平臺(tái)短信通知服務(wù)(微服務(wù)實(shí)戰(zhàn))
這篇文章主要介紹了SpringCloud 搭建企業(yè)級(jí)開發(fā)框架之實(shí)現(xiàn)多租戶多平臺(tái)短信通知服務(wù),系統(tǒng)可以支持多家云平臺(tái)提供的短信服務(wù)。這里以阿里云和騰訊云為例,集成短信通知服務(wù),需要的朋友可以參考下2021-11-11
java使用Logback配置輸出日志內(nèi)容到文件示例代碼
這篇文章主要介紹了java?Logback輸出日志內(nèi)容到文件,要將logger.info的信息輸出到文件,您可以使用Logback配置,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09
spring boot tomcat版本升級(jí)的實(shí)現(xiàn)示例
本文主要介紹了spring boot tomcat版本升級(jí)的實(shí)現(xiàn)示例,將tomcat升級(jí)一個(gè)小版本升級(jí)到9.0.44版本,文中通過圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
一篇文章帶你了解一些Java反射的學(xué)習(xí)記錄
java反射機(jī)制是一個(gè)很好用的東西,用它可以解決很多死的東西,因?yàn)榉瓷錂C(jī)制的靈活行很大,有了他,我們就不要花太多的時(shí)間來寫操做數(shù)據(jù)庫的代碼了,這個(gè)可以很大的減少開發(fā)時(shí)間,而且代碼的可讀性好2021-09-09
spring kafka框架中@KafkaListener 注解解讀和使用案例
Kafka 目前主要作為一個(gè)分布式的發(fā)布訂閱式的消息系統(tǒng)使用,也是目前最流行的消息隊(duì)列系統(tǒng)之一,這篇文章主要介紹了kafka @KafkaListener 注解解讀,需要的朋友可以參考下2023-02-02
Spring Security實(shí)現(xiàn)兩周內(nèi)自動(dòng)登錄"記住我"功能
登錄過程中經(jīng)常使用的“記住我”功能,也就是我們經(jīng)常會(huì)在各種網(wǎng)站登陸時(shí)見到的"兩周內(nèi)免登錄",“三天內(nèi)免登錄”的功能。今天小編給大家分享基于Spring Security實(shí)現(xiàn)兩周內(nèi)自動(dòng)登錄"記住我"功能,感興趣的朋友一起看看吧2019-11-11
ElasticSearch如何設(shè)置某個(gè)字段不分詞淺析
最近在學(xué)習(xí)ElasticSearch官方文檔過程中發(fā)現(xiàn)的某個(gè)問題,記錄一下 希望能幫助到后面的朋友,下面這篇文章主要給大家介紹了關(guān)于ElasticSearch如何設(shè)置某個(gè)字段不分詞的相關(guān)資料,需要的朋友可以參考下2022-04-04

