丝袜人妻一区二区三区_少妇福利无码视频_亚洲理论片在线观看_一级毛片国产A级片

當(dāng)前位置:首頁 > 話題廣場 > 攻略專題 > 游戲問答

如何初始化接口?終于找到答案了自己動手實現(xiàn)springboot運行時新增/更新外部接口

作者|小代碼d

來源| urli

最近有一個要求。必須允許現(xiàn)有的springboot項目加載外部jar包以更新新的接口邏輯。

本著拿來主義的思維網(wǎng)上找了半天沒有找到類似的東西,唯一有點相似的還是spring-loaded但是這個東西據(jù)我網(wǎng)上了解有如下缺點:

  1、使用java agent啟動,個人傾向于直接使用pom依賴的方式

  2、不支持新增字段,新增方法,估計也不支持mybatis的xml加載那些吧,沒了解過

  3、只適合在開發(fā)環(huán)境IDE中使用,沒法生產(chǎn)使用

  無奈之下,我只能自己實現(xiàn)一個了,我需要實現(xiàn)的功能如下

  1、加載外部擴展jar包中的新接口,多次加載需要能完全更新

  2、應(yīng)該能加載mybatis、mybatis-plus中放sql的xml文件

  3、應(yīng)該能加載@Mapper修飾的mybatis的接口資源

  4、需要能加載其它被spring管理的Bean資源

  5、需要能在加載完成后更新swagger文檔

  總而言之就是要實現(xiàn)一個能夠擴展完整接口的容器,其實類似于熱加載也不同于熱加載,熱部署是監(jiān)控本地的class文件的改變,然后使用自動重啟或者重載,熱部署領(lǐng)域比較火的就是devtools和jrebel,前者使用自動重啟的方式,監(jiān)控你的classes改變了,然后使用反射調(diào)用你的main方法重啟一下,后者使用重載的方式,因為收費,具體原理也沒了解過,估計就是不重啟,只加載變過的class吧。而本文實現(xiàn)的是加載外部的jar包,這個jar包只要是個可訪問的URL資源就可以了。雖然和熱部署不一樣,但是從方案上可以借鑒,本文就是使用重載的方式,也就是只會更新擴展包里的資源。

  先來一個自定義的模塊類加載器

package com.rd;


import org.a;
import org.;
import org.Factory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.re;
import java.lang.re;
import java.net.URL;
import java.net.URLClassLoader;
import java.;
import java.;
import java.u;
import java.u;
import java.u;
import java.u;
import java.u;


/**
* 動態(tài)加載外部jar包的自定義類加載器
* @author rongdi
* @date 2021-03-06
* @blog
*/
public class ModuleClassLoader extends URLClassLoader {

private Logger logger = LoggerFac);

private final static String CLASS_SUFFIX = ".class";

private final static String XML_SUFFIX = ".xml";

private final static String MAPPER_SUFFIX = "mapper/";

//屬于本類加載器加載的jar包
private JarFile jarFile;

private Map<String, byte[]> classBytesMap = new HashMap<>();

private Map<String, Class<?>> classesMap = new HashMap<>();

private Map<String, byte[]> xmlBytesMap = new HashMap<>();

public ModuleClassLoader(ClassLoader classLoader, URL... urls) {
super(urls, classLoader);
URL url = urls[0];
String path = url.getPath();
try {
jarFile = new JarFile(path);
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = cla(name);
if (buf == null) {
return (name);
}
i(name)) {
return cla(name);
}
/**
* 這里應(yīng)該算是騷操作了,我不知道市面上有沒有人這么做過,反正我是想了好久,遇到各種因為spring要生成代理對象
* 在他自己的AppClassLoader找不到原對象導(dǎo)致的報錯,注意如果你限制你的擴展包你不會有AOP觸碰到的類或者@Transactional這種
* 會產(chǎn)生代理的類,那么其實你不用這么騷,直接在這里調(diào)用defineClass把字節(jié)碼裝載進去就行了,不會有什么問題,最多也就是
* 在加載mybatis的xml那里前后加三句話,
* 1、獲取并使用一個變量保存當(dāng)前線程類加載器
* 2、將自定義類加載器設(shè)置到當(dāng)前線程類加載器
* 3、還原當(dāng)前線程類加載器為第一步保存的類加載器
* 這樣之后mybatis那些xml里resultType,resultMap之類的需要訪問擴展包的Class的就不會報錯了。
* 不過直接用現(xiàn)在這種騷操作,更加一勞永逸,不會有mybatis的問題了
*/
return loadClass(name,buf);
}

/**
* 使用反射強行將類裝載的歸屬給當(dāng)前類加載器的父類加載器也就是AppClassLoader,如果報ClassNotFoundException
* 則遞歸裝載
* @param name
* @param bytes
* @return
*/
private Class<?> loadClass(String name, byte[] bytes) throws ClassNotFoundException {

Object[] args = new Object[]{name, bytes, 0, by};
try {
/**
* 拿到當(dāng)前類加載器的parent加載器AppClassLoader
*/
ClassLoader parent = ();
/**
* 首先要明確反射是萬能的,仿造org.的寫法,強行獲取被保護
* 的方法defineClass的對象,然后調(diào)用指定類加載器的加載字節(jié)碼方法,強行將加載歸屬塞給它,避免被spring的AOP或者@Transactional
* 觸碰到的類需要生成代理對象,而在AppClassLoader下加載不到外部的擴展類而報錯,所以這里強行將加載外部擴展包的類的歸屬給
* AppClassLoader,讓spring的cglib生成代理對象時可以加載到原對象
*/
Method classLoaderDefineClass = (Method) Acce(new PrivilegedExceptionAction() {
@Override
public Object run() throws Exception {
return Cla("defineClass",
S, byte[].class, In, In);
}
});
if(!cla()) {
cla(true);
}
return (Class<?>(parent,args);
} catch (Exception e) {
if(e instanceof InvocationTargetException) {
String message = ((InvocationTargetException) e).getTargetException().getCause().toString();
/**
* 無奈,明明ClassNotFoundException是個異常,非要拋個InvocationTargetException,導(dǎo)致
* 我這里一個不太優(yōu)雅的判斷
*/
i("java.lang.ClassNotFoundException")) {
String notClassName = me(":")[1];
i(notClassName)) {
throw new ClassNotFoundException(message);
}
notClassName = no();
byte[] bytes1 = cla(notClassName);
if(bytes1 == null) {
throw new ClassNotFoundException(message);
}
/**
* 遞歸裝載未找到的類
*/
Class<?> notClass = loadClass(notClassName, bytes1);
if(notClass == null) {
throw new ClassNotFoundException(message);
}
cla(notClassName,notClass);
return loadClass(name,bytes);
}
} else {
logger.error("",e);
}
}
return null;
}

public Map<String,byte[]> getXmlBytesMap() {
return xmlBytesMap;
}


/**
* 方法描述 初始化類加載器,保存字節(jié)碼
*/
public Map<String, Class> load() {

Map<String, Class> cacheClassMap = new HashMap<>();

//解析jar包每一項
Enumeration<JarEntry> en = jarFile.entries();
InputStream input = null;
try {
while ()) {
JarEntry je = en.nextElement();
String name = je.getName();
//這里添加了路徑掃描限制
if (CLASS_SUFFIX)) {
String className = name.replace(CLASS_SUFFIX, "").replaceAll("/", ".");
input = jarFile.getInputStream(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = in(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
byte[] classBytes = baos.toByteArray();
cla(className, classBytes);
} else if(XML_SUFFIX) && name.startsWith(MAPPER_SUFFIX)) {
input = jarFile.getInputStream(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = in(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
byte[] xmlBytes = baos.toByteArray();
xmlBy(name, xmlBytes);
}
}
} catch (IOException e) {
logger.error("",e);
} finally {
if (input != null) {
try {
in();
} catch (IOException e) {
e.printStackTrace();
}
}
}

//將jar中的每一個class字節(jié)碼進行Class載入
for ;String, byte[]> entry : cla()) {
String key = en();
Class<?> aClass = null;
try {
aClass = loadClass(key);
} catch (ClassNotFoundException e) {
logger.error("",e);
}
cac(key, aClass);
}
return cacheClassMap;

}

public Map<String, byte[]> getClassBytesMap() {
return classBytesMap;
}
}

 然后再來個加載mybatis的xml資源的類,本類解析xml部分是參考網(wǎng)上資料

package com.rd;

import org.a;
import org.a;
import org.a;
import org.a;
import org.a;
import org.a;
import org.a;
import org.a;
import org.a;
import org.a;
import org.myba;
import org.;
import org.Factory;
import java.io.ByteArrayInputStream;
import java.lang.re;
import java.util.*;

/**
* mybatis的ma和@Mapper加載類
* @author rongdi
* @date 2021-03-06
* @blog
*/
public class MapperLoader {

private Logger logger = LoggerFac);

private Configuration configuration;

/**
* 刷新外部mapper,包括文件和@Mapper修飾的接口
* @param sqlSessionFactory
* @param xmlBytesMap
* @return
*/
public Map<String,Object> refresh(SqlSessionFactory sqlSessionFactory, Map<String, byte[]> xmlBytesMap) {
Configuration configuration = ();
= configuration;

/**
* 這里用來區(qū)分mybatis-plus和mybatis,mybatis-plus的Configuration是繼承自mybatis的子類
*/
boolean isSupper = con().getSuperclass() == Con;
Map<String,Object> mapperMap = new HashMap<>();
try {
/**
* 遍歷外部傳入的xml字節(jié)碼map
*/
for;String,byte[]> entry:xmlBytesMap.entrySet()) {
String resource = en();
byte[] bytes = en();
/**
* 使用反射強行拿出configuration中的loadedResources屬性
*/
Field loadedResourcesField = isSupper
? con().getSuperclass().getDeclaredField("loadedResources")
: con().getDeclaredField("loadedResources");
loadedRe(true);
Set loadedResourcesSet = ((Set) loadedRe(configuration));
/**
* 加載mybatis中的xml
*/
XPathParser xPathParser = new XPathParser(new ByteArrayInputStream(bytes), true, con(),
new XMLMapperEntityResolver());
/**
* 解析mybatis的xml的根節(jié)點,
*/
XNode context = xPa("/mapper");
/**
* 拿到namespace,namespace就是指Mapper接口的全限定名
*/
String namespace = con("namespace");
Field field = con().getClass().getDeclaredField("knownMappers");
(true);

/**
* 拿到存放Mapper接口和對應(yīng)代理子類的映射map,
*/
Map mapConfig = (Map) (con());
/**
* 拿到Mapper接口對應(yīng)的class對象
*/
Class nsClass = Re(namespace);

/**
* 先刪除各種
*/
ma(nsClass);
loadedRe(resource);
con().remove(namespace);

/**
* 清掉namespace下各種緩存
*/
cleanParameterMa("/mapper/parameterMap"), namespace);
cleanResultMa("/mapper/resultMap"), namespace);
cleanKeyGenerator("insert|update|select|delete"), namespace);
cleanSqlElemen("/mapper/sql"), namespace);

/**
* 加載并解析對應(yīng)xml
*/
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(new ByteArrayInputStream(bytes),
(), resource,
().getSqlFragments());
xmlMa();

/**
* 構(gòu)造MapperFactoryBean,注意這里一定要傳入sqlSessionFactory,
* 這塊邏輯通過debug源碼試驗了很久
*/
MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(nsClass);
ma(sqlSessionFactory);
/**
* 放入map,返回出去給ModuleApplication去加載
*/
ma(namespace,mapperFactoryBean);
logger.info("refresh: '" + resource + "', success!");

}
return mapperMap;
} catch (Exception e) {
logger.error("refresh error",e.getMessage());
} finally {
ErrorCon().reset();
}
return null;
}

/**
* 清理parameterMap
*
* @param list
* @param namespace
*/
private void cleanParameterMap(List<XNode> list, String namespace) {
for (XNode parameterMapNode : list) {
String id = ("id");
con().remove(namespace + "." + id);
}
}

/**
* 清理resultMap
*
* @param list
* @param namespace
*/
private void cleanResultMap(List<XNode> list, String namespace) {
for (XNode resultMapNode : list) {
String id = re("id", re());
con().remove(id);
con().remove(namespace + "." + id);
clearResultMap(resultMapNode, namespace);
}
}

private void clearResultMap(XNode xNode, String namespace) {
for (XNode resultChild : xNode.getChildren()) {
if ("association".equal()) || "collection".equal())
|| "case".equal())) {
if ("select") == null) {
con()
.remove("id", re()));
con().remove(namespace + "."
+ re("id", re()));
if () != null && !re().isEmpty()) {
clearResultMap(resultChild, namespace);
}
}
}
}
}

/**
* 清理selectKey
*
* @param list
* @param namespace
*/
private void cleanKeyGenerators(List<XNode> list, String namespace) {
for (XNode context : list) {
String id = con("id");
con().remove(id + Selec);
con().remove(namespace + "." + id + Selec);

Collection<MappedStatement> mappedStatements = con();
List<MappedStatement> objects = new ArrayList<>();
Iterator<MappedStatement> it = ma();
while ()) {
Object object = it.next();
if (object instanceof MappedStatement) {
MappedStatement mappedStatement = (MappedStatement) object;
if ().equals(namespace + "." + id)) {
objec(mappedStatement);
}
}
}
ma(objects);
}
}

/**
* 清理sql節(jié)點緩存
*
* @param list
* @param namespace
*/
private void cleanSqlElement(List<XNode> list, String namespace) {
for (XNode context : list) {
String id = con("id");
con().remove(id);
con().remove(namespace + "." + id);
}
}

}

  上面需要注意的是,處理好xml還需要將XXMapper接口也放入spring容器中,但是接口是沒辦法直接轉(zhuǎn)成spring的BeanDefinition的,因為接口沒辦法實例化,而BeanDefinition作為對象的模板,肯定不允許接口直接放進去,通過看mybatis-spring源碼,可以看出這些接口都會被封裝成MapperFactoryBean放入spring容器中實例化時就調(diào)用getObject方法生成Mapper的代理對象。下面就是將各種資源裝載spring容器的代碼了

package com.rd;

import com.rd;
import com.rd;
import org.a;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

import java.lang.re;
import java.lang.re;
import java.net.URL;
import java.util.*;

/**
* 基于spring的應(yīng)用上下文提供一些工具方法
* @author rongdi
* @date 2021-03-06
* @blog
*/
public class ModuleApplication {

private final static String SINGLETON = "singleton";

private final static String DYNAMIC_DOC_PACKAGE = "dynamic.;;

private Set<RequestMappingInfo> extMappingInfos = new HashSet<>();

private ApplicationContext applicationContext;

/**
* 使用spring上下文拿到指定beanName的對象
*/
public <T> T getBean(String beanName) {
return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(beanName);
}

/**
* 使用spring上下文拿到指定類型的對象
*/
public <T> T getBean(Class<T> clazz) {
return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(clazz);
}

/**
* 加載一個外部擴展jar,包括springmvc接口資源,mybatis的@mapper和ma和spring bean等資源
* @param url jar url
* @param applicationContext spring context
* @param sqlSessionFactory mybatis的session工廠
*/
public void reloadJar(URL url, ApplicationContext applicationContext,SqlSessionFactory sqlSessionFactory) throws Exception {
= applicationContext;
URL[] urls = new URL[]{url};
/**
* 這里實際上是將spring的ApplicationContext的類加載器當(dāng)成parent傳給了自定義類加載器,很明自定義的子類加載器自己加載
* 的類,parent類加載器直接是獲取不到的,所以在自定義類加載器做了特殊的騷操作
*/
ModuleClassLoader moduleClassLoader = new ModuleClassLoader(), urls);
/**
* 使用模塊類加載器加載url資源的jar包,直接返回類的全限定名和Class對象的映射,這些Class對象是
* jar包里所有.class結(jié)尾的文件加載后的結(jié)果,同時mybatis的xml加載后,無奈地放入了
* moduleCla(),不是很優(yōu)雅
*/
Map<String, Class> classMap = moduleCla();

MapperLoader mapperLoader = new MapperLoader();

/**
* 刷新mybatis的xml和Mapper接口資源,Mapper接口其實就是xml的namespace
*/
Map<String, Object> extObjMap = ma(sqlSessionFactory, moduleCla());
/**
* 將各種資源放入spring容器
*/
registerBeans(applicationContext, classMap, extObjMap);
}

/**
* 裝載bean到spring中
*
* @param applicationContext
* @param cacheClassMap
*/
public void registerBeans(ApplicationContext applicationContext, Map<String, Class> cacheClassMap,Map<String,Object> extObjMap) throws Exception {
/**
* 將applicationContext轉(zhuǎn)換為ConfigurableApplicationContext
*/
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
/**
* 獲取bean工廠并轉(zhuǎn)換為DefaultListableBeanFactory
*/
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) con();

/**
* 有一些對象想給spring管理,則放入spring中,如mybatis的@Mapper修飾的接口的代理類
*/
if(extObjMap != null && !ex()) {
ex((beanName,obj) ->{
/**
* 如果已經(jīng)存在,則銷毀之后再注冊
*/
i(beanName)) {
de(beanName);
}
de(beanName,obj);
});
}

for ;String, Class> entry : cac()) {
String className = en();
Class<?> clazz = en();
if (clazz)) {
//將變量首字母置小寫
String beanName = S(className);
beanName = beanName.substring(".") + 1);
beanName = S(beanName);

/**
* 已經(jīng)在spring容器就刪了
*/
if (beanName)) {
de(beanName);
}
/**
* 使用spring的BeanDefinitionBuilder將Class對象轉(zhuǎn)成BeanDefinition
*/
BeanDefinitionBuilder beanDefinitionBuilder = BeanDe(clazz);
BeanDefinition beanDefinition = beanDe();
//設(shè)置當(dāng)前bean定義對象是單利的
beanDe(SINGLETON);
/**
* 以指定beanName注冊上面生成的BeanDefinition
*/
de(beanName, beanDefinition);
}

}

/**
* 刷新springmvc,讓新增的接口生效
*/
refreshMVC((ConfigurableApplicationContext) applicationContext);

}

/**
* 刷新springMVC,這里花了大量時間調(diào)試,找不到開放的方法,只能取個巧,在更新RequestMappingHandlerMapping前先記錄之前
* 所有RequestMappingInfo,記得這里一定要copy一下,然后刷新后再記錄一次,計算出差量存放在成員變量Set中,然后每次開頭判斷
* 差量那里是否有內(nèi)容,有就先unregiester掉
*/
private void refreshMVC(ConfigurableApplicationContext applicationContext) throws Exception {


Map<String, RequestMappingHandlerMapping> map = a().getBeansOfType);
/**
* 先拿到RequestMappingHandlerMapping對象
*/
RequestMappingHandlerMapping mappingHandlerMapping = map.get("requestMappingHandlerMapping");

/**
* 重新注冊mapping前先判斷是否存在了,存在了就先unregister掉
*/
if(!ex()) {
for(RequestMappingInfo requestMappingInfo:extMappingInfos) {
ma(requestMappingInfo);
}
}

/**
* 獲取刷新前的RequestMappingInfo
*/
Map<RequestMappingInfo, HandlerMethod> preMappingInfoHandlerMethodMap = ma();
/**
* 這里注意一定要拿到拷貝,不然刷新后內(nèi)容就一致了,就沒有差量了
*/
Set<RequestMappingInfo> preRequestMappingInfoSet = new HashSe());

/**
* 這里是刷新springmvc上下文
*/
a().getBeansOfType)
.forEach((key,value) ->{
value.afterPropertiesSet();
});

/**
* 獲取刷新后的RequestMappingInfo
*/
Map<RequestMappingInfo, HandlerMethod> afterMappingInfoHandlerMethodMap = ma();
Set<RequestMappingInfo> afterRequestMappingInfoSet = a();

/**
* 填充差量部分RequestMappingInfo
*/
fillSurplusRequestMappingInfos(preRequestMappingInfoSet,afterRequestMappingInfoSet);

/**
* 這里真的是不講武德了,每次調(diào)用value.afterPropertiesSet();如下urlLookup都會產(chǎn)生重復(fù),暫時沒找到開放方法去掉重復(fù),這里重復(fù)會導(dǎo)致
* 訪問的時候報錯Ambiguous handler methods mapped for
* 目標(biāo)是去掉RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping
* -> mappingRegistry -> urlLookup重復(fù)的RequestMappingInfo,這里的.getClass().getSuperclass().getSuperclass()相信會
* 很懵逼,如果單獨通過getClass().getDeclaredMethod("getMappingRegistry",new Class[]{})是無論如何都拿不到父類的非public非
* protected方法的,因為這個方法不屬于子類,只有父類才可以訪問到,只有你拿得到你才有資格不講武德的使用me(true)強行
* 訪問
*/
Method method = Re(mappingHandlerMapping,"getMappingRegistry",new Class[]{});
me(true);
Object mappingRegistryObj = me(mappingHandlerMapping,new Object[]{});
Field field = ma().getDeclaredField("urlLookup");
(true);
MultiValueMap<String, RequestMappingInfo> multiValueMap = (MultiValueMap)(mappingRegistryObj);
mul((key,list) -> {
clearMultyMapping(list);
});

}
/**
* 填充差量的RequestMappingInfo,因為已經(jīng)重寫過hashCode和equals方法所以可以直接用對象判斷是否存在
* @param preRequestMappingInfoSet
* @param afterRequestMappingInfoSet
*/
private void fillSurplusRequestMappingInfos(Set<RequestMappingInfo> preRequestMappingInfoSet,Set<RequestMappingInfo> afterRequestMappingInfoSet) {
for(RequestMappingInfo requestMappingInfo:afterRequestMappingInfoSet) {
if(!(requestMappingInfo)) {
ex(requestMappingInfo);
}
}
}

/**
* 簡單的邏輯,刪除List里重復(fù)的RequestMappingInfo,已經(jīng)寫了toString,直接使用ma()就可以區(qū)分重復(fù)了
* @param mappingInfos
*/
private void clearMultyMapping(List<RequestMappingInfo> mappingInfos) {
Set<String> containsList = new HashSet<>();
for(Iterator<RequestMappingInfo> iter = ma();i();) {
RequestMappingInfo mappingInfo = i();
String flag = ma();
i(flag)) {
i();
} else {
con(flag);
}
}
}

}

  上述有兩個地方很虐心,第一個就是刷新springmvc那里,提供的刷新springmvc上下文的方式不友好不說,刷新上下文后RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping -> mappingRegistry -> urlLookup屬性中會存在重復(fù)的路徑如下

  上述是我故意兩次加載同一個jar包后第二次走到刷新springmvc之后,可以看到擴展包里的接口,由于unregister所以沒有發(fā)現(xiàn)重復(fù),那些重復(fù)的路徑都是本身服務(wù)的接口,由于沒有unregister所以出現(xiàn)了大把重復(fù),如果這個時候訪問重復(fù)的接口,會出現(xiàn)如下錯誤

java.lang.IllegalStateException: Ambiguous handler methods mapped for '/error':

  意思就是匹配到了多個相同的路徑解決方法有兩種,第一種就是所有RequestMappingInfo都先unregister再刷新,第二種就是我調(diào)試很久確認(rèn)就只有urlLookup會發(fā)生重重復(fù),所以如下使用萬能的反射強行修改值,其實不要排斥使用反射,spring源碼中大量使用反射去強行調(diào)用方法,比如org.類摘抄如下:

classLoaderDefineClass = (Method) Acce(new PrivilegedExceptionAction() {
public Object run() throws Exception {
return Cla("defineClass",
S, byte[].class, In, In, Pro);
}
});
classLoaderDefineClassMethod = classLoaderDefineClass;
// Classic option: protected Cla method
if (c == null && classLoaderDefineClassMethod != null) {
if (protectionDomain == null) {
protectionDomain = PROTECTION_DOMAIN;
}
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
try {
if (!cla()) {
classLoaderDefineClassMe(true);
}
c = (Class) classLoaderDefineClassMe(loader, args);
}
catch (InvocationTargetException ex) {
throw new CodeGenerationException());
}
catch (Throwable ex) {
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new CodeGenerationException(ex);
}
}
}

  如上可以看出來像spring這樣的名家也一樣也很不講武德,個人認(rèn)為反射本身就是用來給我們打破規(guī)則用的,只有打破規(guī)則才會有創(chuàng)新,所以大膽使用反射吧。只要不遇到final的屬性,反射是萬能的,哈哈!所以我使用反射強行刪除重復(fù)的代碼如下:

/**
* 這里真的是不講武德了,每次調(diào)用value.afterPropertiesSet();如下urlLookup都會產(chǎn)生重復(fù),暫時沒找到開放方法去掉重復(fù),這里重復(fù)會導(dǎo)致
* 訪問的時候報錯Ambiguous handler methods mapped for
* 目標(biāo)是去掉RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping
* -> mappingRegistry -> urlLookup重復(fù)的RequestMappingInfo,這里的.getClass().getSuperclass().getSuperclass()相信會
* 很懵逼,如果單獨通過getClass().getDeclaredMethod("getMappingRegistry",new Class[]{})是無論如何都拿不到父類的非public非
* protected方法的,因為這個方法不屬于子類,只有父類才可以訪問到,只有你拿得到你才有資格不講武德的使用me(true)強行
* 訪問
*/
Method method = Re(mappingHandlerMapping,"getMappingRegistry",new Class[]{});
me(true);
Object mappingRegistryObj = me(mappingHandlerMapping,new Object[]{});
Field field = ma().getDeclaredField("urlLookup");
(true);
MultiValueMap<String, RequestMappingInfo> multiValueMap = (MultiValueMap)(mappingRegistryObj);
mul((key,list) -> {
clearMultyMapping(list);
});

/**
* 簡單的邏輯,刪除List里重復(fù)的RequestMappingInfo,已經(jīng)寫了toString,直接使用ma()就可以區(qū)分重復(fù)了
* @param mappingInfos
*/
private void clearMultyMapping(List<RequestMappingInfo> mappingInfos) {
Set<String> containsList = new HashSet<>();
for(Iterator<RequestMappingInfo> iter = ma();i();) {
RequestMappingInfo mappingInfo = i();
String flag = ma();
i(flag)) {
i();
} else {
con(flag);
}
}
}

  還有個虐心的地方是刷新swagger文檔的地方,這個swagger只有需要做這個需求時才知道,他封裝的有多菜,根本沒有刷新相關(guān)的方法,也沒有可以控制的入口,真的是沒辦法。下面貼出我解決刷新swagger文檔的調(diào)試過程,使用過swagger2的朋友們都知道,要想在springboot集成swagger2主要需要編寫的配置代碼如下

@Configuration
@EnableSwagger2
public class SwaggerConfig {

//swagger2的配置文件,這里可以配置swagger2的一些基本的內(nèi)容,比如掃描的包等等
@Bean
public Docket createRestApi() {
List<ResponseMessage> responseMessageList = new ArrayList<>();
re(new ResponseMessageBuilder().code(200).message("成功").responseModel(new ModelRef("Payload")).build());
Docket docket = new Docke)
.globalResponseMessage)
.globalResponseMessage)
.globalResponseMessage)
.apiInfo(apiInfo()).select()
//為當(dāng)前包路徑
.api("com.xxx")).pa()).build();
return docket;
}

//構(gòu)建 api文檔的詳細信息函數(shù),注意這里的注解引用的是哪個
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//頁面標(biāo)題
.title("使用 Swagger2 構(gòu)建RESTful API")
//創(chuàng)建人
.contact(new Contact("rongdi", "", "495194630@qq.com"))
//版本號
.version("1.0")
//描述
.description("api管理").build();
}

}

而訪問swagger的文檔請求的是如下接口/v2/api-docs

  通過調(diào)試可以找到swagger2就是通過實現(xiàn)了SmartLifecycle接口的DocumentationPluginsBootstrapper類,當(dāng)spring容器加載所有bean并完成初始化之后,會回調(diào)實現(xiàn)該接口的類(DocumentationPluginsBootstrapper)中對應(yīng)的方法start()方法,下面會介紹怎么找到這里的。

接著循環(huán)DocumentationPlugin集合去處理文檔

接著放入DocumentationCache中

然后再回到swagger接口的類那里,實際上就是從這個DocumentationCache里獲取到Documention

‘如果找不到解決問題的入口,我們至少可以找到訪問文檔的上面這個接口地址(出口),發(fā)現(xiàn)接口返回的文檔json內(nèi)容是從DocumentationCache里獲取,那么我們很明顯可以想到肯定有地方存放數(shù)據(jù)到這個DocumentationCache里,然后其實我們可以直接在addDocumentation方法里打個斷點,然后看調(diào)試左側(cè)的運行方法棧信息,就可以很明確的看到調(diào)用鏈路了

再回看我們接入swagger2的時候?qū)懙呐渲么a

//swagger2的配置文件,這里可以配置swagger2的一些基本的內(nèi)容,比如掃描的包等等
@Bean
public Docket createRestApi() {
List<ResponseMessage> responseMessageList = new ArrayList<>();
re(new ResponseMessageBuilder().code(200).message("成功").responseModel(new ModelRef("Payload")).build());
Docket docket = new Docke)
.globalResponseMessage)
.globalResponseMessage)
.globalResponseMessage)
.apiInfo(apiInfo()).select()
//為當(dāng)前包路徑
.api("com.xxx")).pa()).build();
return docket;
}

然后再看看下圖,應(yīng)該終于知道咋回事了吧,其實Docket對象我們僅僅需要關(guān)心的是basePackage,我們擴展jar包大概率接口所在的包和現(xiàn)有包不一樣,所以我們需要新增一個Docket插件,并加入DocumentationPlugin集合,然后調(diào)用DocumentationPluginsBootstrapper的stop()方法清掉緩存,再調(diào)用start()再次開始解析

具體實現(xiàn)代碼如下

/**
* 刷新springMVC,這里花了大量時間調(diào)試,找不到開放的方法,只能取個巧,在更新RequestMappingHandlerMapping前先記錄之前
* 所有RequestMappingInfo,記得這里一定要copy一下,然后刷新后再記錄一次,計算出差量存放在成員變量Set中,然后每次開頭判斷
* 差量那里是否有內(nèi)容,有就先unregiester掉
*/
private void refreshMVC(ConfigurableApplicationContext applicationContext) throws Exception {


Map<String, RequestMappingHandlerMapping> map = a().getBeansOfType);
/**
* 先拿到RequestMappingHandlerMapping對象
*/
RequestMappingHandlerMapping mappingHandlerMapping = map.get("requestMappingHandlerMapping");

/**
* 重新注冊mapping前先判斷是否存在了,存在了就先unregister掉
*/
if(!ex()) {
for(RequestMappingInfo requestMappingInfo:extMappingInfos) {
ma(requestMappingInfo);
}
}

/**
* 獲取刷新前的RequestMappingInfo
*/
Map<RequestMappingInfo, HandlerMethod> preMappingInfoHandlerMethodMap = ma();
/**
* 這里注意一定要拿到拷貝,不然刷新后內(nèi)容就一致了,就沒有差量了
*/
Set<RequestMappingInfo> preRequestMappingInfoSet = new HashSe());

/**
* 這里是刷新springmvc上下文
*/
a().getBeansOfType)
.forEach((key,value) ->{
value.afterPropertiesSet();
});

/**
* 獲取刷新后的RequestMappingInfo
*/
Map<RequestMappingInfo, HandlerMethod> afterMappingInfoHandlerMethodMap = ma();
Set<RequestMappingInfo> afterRequestMappingInfoSet = a();

/**
* 填充差量部分RequestMappingInfo
*/
fillSurplusRequestMappingInfos(preRequestMappingInfoSet,afterRequestMappingInfoSet);

/**
* 這里真的是不講武德了,每次調(diào)用value.afterPropertiesSet();如下urlLookup都會產(chǎn)生重復(fù),暫時沒找到開放方法去掉重復(fù),這里重復(fù)會導(dǎo)致
* 訪問的時候報錯Ambiguous handler methods mapped for
* 目標(biāo)是去掉RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping
* -> mappingRegistry -> urlLookup重復(fù)的RequestMappingInfo,這里的.getClass().getSuperclass().getSuperclass()相信會
* 很懵逼,如果單獨通過getClass().getDeclaredMethod("getMappingRegistry",new Class[]{})是無論如何都拿不到父類的非public非
* protected方法的,因為這個方法不屬于子類,只有父類才可以訪問到,只有你拿得到你才有資格不講武德的使用me(true)強行
* 訪問
*/
Method method = Re(mappingHandlerMapping,"getMappingRegistry",new Class[]{});
me(true);
Object mappingRegistryObj = me(mappingHandlerMapping,new Object[]{});
Field field = ma().getDeclaredField("urlLookup");
(true);
MultiValueMap<String, RequestMappingInfo> multiValueMap = (MultiValueMap)(mappingRegistryObj);
mul((key,list) -> {
clearMultyMapping(list);
});

/**
* 刷新swagger文檔
*/
refreshSwagger(applicationContext);
}


/**
* 刷新swagger文檔
* @param applicationContext
* @throws Exception
*/
private void refreshSwagger(ConfigurableApplicationContext applicationContext) throws Exception {
/**
* 獲取擴展包swagger的地址接口掃描包,如果有配置則執(zhí)行文檔刷新操作
*/
String extSwaggerDocPackage = a().getProperty(DYNAMIC_DOC_PACKAGE);
if (!S(extSwaggerDocPackage)) {
/**
* 拿到swagger解析文檔的入口類,真的不想這樣,主要是根本不提供刷新和重新加載文檔的方法,只能不講武德了
*/
DocumentationPluginsBootstrapper bootstrapper = a().getBean);
/**
* 不管愿不愿意,強行拿到屬性得到documentationPluginsManager對象
*/
Field field1 = boo().getDeclaredField("documentationPluginsManager");
(true);
DocumentationPluginsManager documentationPluginsManager = (DocumentationPluginsManager) (bootstrapper);

/**
* 繼續(xù)往下層拿documentationPlugins屬性
*/
Field field2 = documen().getDeclaredField("documentationPlugins");
(true);
PluginRegistry<DocumentationPlugin, DocumentationType> pluginRegistrys = (PluginRegistry<DocumentationPlugin, DocumentationType>) (documentationPluginsManager);
/**
* 拿到最關(guān)鍵的文檔插件集合,所有邏輯文檔解析邏輯都在插件中
*/
List<DocumentationPlugin> dockets = ();
/**
* 真的不能怪我,好端端,你還搞個不能修改的集合,強行往父類遞歸拿到unmodifiableList的list屬性
*/
Field unModList = Re(dockets,"list");
unModLi(true);
List<DocumentationPlugin> modifyerList = (List<DocumentationPlugin>) unModLi(dockets);
/**
* 這下老實了吧,把自己的Docket加入進去,這里的groupName為dynamic
*/
modi(createRestApi(extSwaggerDocPackage));
/**
* 清空罪魁禍?zhǔn)譊ocumentationCache緩存,不然就算再加載一次,獲取文檔還是從這個緩存中拿,不會完成更新
*/
boo();
/**
* 手動執(zhí)行重新解析swagger文檔
*/
boo();
}
}

public Docket createRestApi(String basePackage) {
List<ResponseMessage> responseMessageList = new ArrayList<>();
re(new ResponseMessageBuilder().code(200).message("成功").responseModel(new ModelRef("Payload")).build());
Docket docket = new Docke)
.groupName("dynamic")
.globalResponseMessage)
.globalResponseMessage)
.globalResponseMessage)
.apiInfo(apiInfo()).select()
//為當(dāng)前包路徑
.api(basePackage)).pa()).build();
return docket;
}

/**
* 構(gòu)建api文檔的詳細信息函數(shù)
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//頁面標(biāo)題
.title("SpringBoot動態(tài)擴展")
//創(chuàng)建人
.contact(new Contact("rongdi", "", "495194630@qq.com"))
//版本號
.version("1.0")
//描述
.description("api管理").build();
}

好了,下面給一下整個擴展功能的入口吧

package com.rd;

import com.rd.ModuleApplication;
import org.a;
import org.;
import org.Factory;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.;
import org.Aware;
import org.;
import org.;
import org.;

import java.net.URL;

/**
* 一切配置的入口
* @author rongdi
* @date 2021-03-06
* @blog
*/
@Configuration
public class DynamicConfig implements ApplicationContextAware {

private static final Logger logger = LoggerFac);

@Autowired
private SqlSessionFactory sqlSessionFactory;

private ApplicationContext applicationContext;

@Value("${dynamic.jar:/}")
private String dynamicJar;

@Bean
public ModuleApplication moduleApplication() throws Exception {
return new ModuleApplication();
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
= applicationContext;
}

/**
* 隨便找個事件ApplicationStartedEvent,用來reload外部的jar,其實直接在moduleApplication()方法也可以做
* 這件事,但是為了驗證容器初始化后再加載擴展包還可以生效,所以故意放在了這里。
* @return
*/
@Bean
@ConditionalOnProperty(prefix = "dynamic",name = "jar")
public ApplicationListener applicationListener1() {
return (ApplicationListener<ApplicationStartedEvent>) event -> {
try {
/**
* 加載外部擴展jar
*/
moduleApplication().reloadJar(new URL(dynamicJar),applicationContext,sqlSessionFactory);
} catch (Exception e) {
logger.error("",e);
}

};
}


}

再給個開關(guān)注解

package com.rd;

import com.rd.DynamicConfig;
import org.;

import java.lang.annotation.*;

/**
* 開啟動態(tài)擴展的注解
* @author rongdi
* @date 2021-03-06
* @blog
*/
@Retention)
@Target({Elemen})
@Documented
@Import({DynamicCon})
public @interface EnableDynamic {
}

  好了,至此核心代碼和功能都分享完了,詳細源碼和使用說明見github:

1.《如何初始化接口?終于找到答案了自己動手實現(xiàn)springboot運行時新增/更新外部接口》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點,與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。

2.《如何初始化接口?終于找到答案了自己動手實現(xiàn)springboot運行時新增/更新外部接口》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。

3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/gl/2119261.html

上一篇

0.32紅包是什么意思看這里!相親結(jié)束后,男子發(fā)0.52元紅包表白,遭拒絕后嘲諷女方太市儈

下一篇

0x0000008e藍屏代碼是什么意思?我來告訴你答案干貨!最全的藍屏死機的解決方法寶典,此文勝過十年經(jīng)驗電腦師傅

如何初始化接口?總結(jié)很全面速看!小技巧:使用對象機制實現(xiàn)組件初始化及銷毀

如何初始化接口?總結(jié)很全面速看!小技巧:使用對象機制實現(xiàn)組件初始化及銷毀

如何初始化接口相關(guān)介紹,問題 最近重新配置代碼時發(fā)現(xiàn),在特殊情況下,組件的破壞界面由于程序的異常而無法調(diào)用。這不是什么大問題(整個程序不正常,進程退出是即將發(fā)生的事),但對于有一點完美主義的我來說,這確實降低了程序的異常安全...

關(guān)于如何初始化接口,你需要知道這些Spring容器是怎么初始化的

關(guān)于如何初始化接口,你需要知道這些Spring容器是怎么初始化的

如何初始化接口相關(guān)介紹,彈簧容器是如何初始化的? 原創(chuàng)宣言 本人不支持簽署原創(chuàng)文章未經(jīng)許可轉(zhuǎn)載。 本公眾號所有文章均原創(chuàng),為了容易理解和記憶,文章以圖解為主、代碼為輔。如果您感興趣,歡迎關(guān)注! 文/吳瀟(Java Senio...

如何初始化接口?總結(jié)很全面速看!BIOS的初始化和引導(dǎo)加載程序

如何初始化接口?總結(jié)很全面速看!BIOS的初始化和引導(dǎo)加載程序

如何初始化接口相關(guān)介紹,BIOS是基本輸入/輸出系統(tǒng)(Basic Input/Output System)的縮寫,是硬件和軟件之間的接口,是非?;镜慕涌凇?BIOS提供了一組基本的操作系統(tǒng)使用的指令,系統(tǒng)啟動的成功與否依賴...

關(guān)于如何初始化接口,你需要知道這些MCU性能測試,CoreMark極簡入門教程

關(guān)于如何初始化接口,你需要知道這些MCU性能測試,CoreMark極簡入門教程

如何初始化接口相關(guān)介紹,MCU性能測試最著名的部分是CoreMark和Dhrystone。 CoreMark以每秒迭代為性能評估,而Dhrystone的DMIPS與Dhrystone標(biāo)準(zhǔn)有關(guān)。 本文討論CoreMark,先來...

如何初始化接口?終于找到答案了MyBatis初始化加載 Mapper 接口與XML文件

如何初始化接口?終于找到答案了MyBatis初始化加載 Mapper 接口與XML文件

如何初始化接口相關(guān)介紹,在MyBatis初始化過程中,大致有以下幾個步驟:分解所有IT類編制 創(chuàng)建Configuration全局配置對象時,TypeAliasRegistry別名注冊中心將添加Mybatis所需的相關(guān)類,并將...

關(guān)于如何初始化接口,你需要知道這些無法初始化windows sockets接口

關(guān)于如何初始化接口,你需要知道這些無法初始化windows sockets接口

如何初始化接口相關(guān)介紹,請試試這個方法。我也有這種情況?,F(xiàn)在用這個方法解決了??赡苁亲蛱斓淖詣痈?,可能出了問題,可能是單機。但是只要和互聯(lián)網(wǎng)接觸,就不能運行。今天Vista和Win7好像大部分都中獎了。(大衛(wèi)亞設(shè))。 應(yīng)該...

如何初始化接口看這里!什么是 COM 接口?

如何初始化接口看這里!什么是 COM 接口?

如何初始化接口相關(guān)介紹,如果你懂C#或Java語言,界面將是一個非常熟悉的概念。 接口是一個對象上一組操作的集合,不涉及任何實現(xiàn)的細節(jié),接口標(biāo)志著方法和實現(xiàn)的分離。計算機中這種現(xiàn)象叫做解耦(decoupled)。 在 C++...

關(guān)于如何初始化接口,你需要知道這些Spring Boot如何在啟動時初始化資源?實現(xiàn)CommandLineRunner接口

關(guān)于如何初始化接口,你需要知道這些Spring Boot如何在啟動時初始化資源?實現(xiàn)CommandLineRunner接口

如何初始化接口相關(guān)介紹,在實際工作中,項目啟動時需要執(zhí)行初始化任務(wù)(如初始化數(shù)據(jù)庫連接、Redis緩存等)的要求。 今天就給大家介紹 CommandLineRunner接口,幫助大家解決項目啟動初始化資源操作。 Comman...