文章來自公眾號(hào)三不猴歡迎關(guān)注我的公眾號(hào)姻蚓,公眾號(hào)內(nèi)回復(fù)666獲取面試資料应结,回復(fù)電子書獲取200本PDF電子書
前言
之前寫過一篇JNDI實(shí)現(xiàn)一個(gè)依賴注入的文章,很多小伙伴都表示很疑惑虐译,那玩意兒有啥用瘪板,包括這篇文章可能你也覺得沒啥用,確實(shí)在實(shí)際開發(fā)中都是使用spring來做依賴查找漆诽、依賴注入侮攀,那你有沒有想過在沒有spring的年代是怎么做依賴查找和依賴注入的?沒錯(cuò)就是可以使用JNDI厢拭。寫JNDI系列文章的目的是為了了解JAVAEE單體架構(gòu)是如何演變成現(xiàn)在spring 技術(shù)棧的兰英,這是一個(gè)系列后面將一步一步演進(jìn)成主流spring、spring boot供鸠、spring cloud風(fēng)格畦贸,具有Spring、SpringMVC完整功能的項(xiàng)目楞捂。歡迎關(guān)注了解后續(xù)薄坏。看完本文你將了解如果讓你來實(shí)現(xiàn)一個(gè)spring 的思路寨闹,大致流程胶坠。
實(shí)踐
上篇我們通過JNDI把xml中的元信息讀出,放入ServletContext中繁堡,使用了JNDI中的lookup方法作為依賴查找沈善。下面開始對(duì)之前的代碼進(jìn)行重構(gòu)。
首先對(duì)初始化方法就行重構(gòu)椭蹄,我們先把初始化的動(dòng)作定義成:初始化環(huán)境矮瘟、實(shí)例化組件、初始化組件三個(gè)步驟塑娇〕合溃看過spring源碼的小伙伴是不是直呼有內(nèi)味兒了。
public void init(ServletContext servletContext) throws RuntimeException {
ComponentContext.servletContext = servletContext;
servletContext.setAttribute(CONTEXT_NAME, this);
// 獲取當(dāng)前 ServletContext(WebApp)ClassLoader
this.classLoader = servletContext.getClassLoader();
initEnvContext();
instantiateComponents();
initializeComponents();
}
初始化環(huán)境
我們這個(gè)項(xiàng)目的環(huán)境就是初始化JNDI的上下文Context埋酬,所以代碼如下:
private void initEnvContext() throws RuntimeException {
if (this.envContext != null) {
return;
}
Context context = null;
try {
context = new InitialContext();
this.envContext = (Context) context.lookup(COMPONENT_ENV_CONTEXT_NAME);
} catch (NamingException e) {
throw new RuntimeException(e);
} finally {
close(context);
}
}
有初始肯定也有銷毀操作哨啃,代碼如下:(因?yàn)镴NDI有很多檢查異常烧栋,書寫時(shí)有諸多不便,所以這里用了一個(gè)FunctionalInterface 包裝一下拳球,不影響主流程审姓,當(dāng)做這個(gè)context.close()就好,下文不再提及)
private static void close(Context context) {
if (context != null) {
ThrowableAction.execute(context::close);
}
}
實(shí)例化組件
實(shí)例化組件的思路就是把根目錄下的元數(shù)據(jù)全部讀出來祝峻,并放入緩存map中魔吐。具體細(xì)節(jié)參照代碼注釋。
/**
* 實(shí)例化組件
*/
protected void instantiateComponents() {
// 遍歷獲取所有的組件名稱
List<String> componentNames = listAllComponentNames();
// 通過依賴查找莱找,實(shí)例化對(duì)象( Tomcat BeanFactory setter 方法的執(zhí)行酬姆,僅支持簡(jiǎn)單類型)
componentNames.forEach(name -> componentsMap.put(name, lookupComponent(name)));
}
private List<String> listAllComponentNames() {
return listComponentNames("/");
}
protected List<String> listComponentNames(String name) {
return executeInContext(context -> {
NamingEnumeration<NameClassPair> e = executeInContext(context, ctx -> ctx.list(name), true);
// 目錄 - Context
// 節(jié)點(diǎn) -
if (e == null) { // 當(dāng)前 JNDI 名稱下沒有子節(jié)點(diǎn)
return Collections.emptyList();
}
List<String> fullNames = new LinkedList<>();
while (e.hasMoreElements()) {
NameClassPair element = e.nextElement();
String className = element.getClassName();
Class<?> targetClass = classLoader.loadClass(className);
if (Context.class.isAssignableFrom(targetClass)) {
// 如果當(dāng)前名稱是目錄(Context 實(shí)現(xiàn)類)的話,遞歸查找
fullNames.addAll(listComponentNames(element.getName()));
} else {
// 否則奥溺,當(dāng)前名稱綁定目標(biāo)類型的話話辞色,添加該名稱到集合中
String fullName = name.startsWith("/") ?
element.getName() : name + "/" + element.getName();
fullNames.add(fullName);
}
}
return fullNames;
});
}
初始化實(shí)例
初始化的過程就是類似spring中使用了@Autowired我們要將JNDI上下文context中的實(shí)例注入進(jìn)去,這里我們使用反射調(diào)用set方法完成注入浮定。我們使用Resource注解相满,目標(biāo)是將使用了@Resource的字段注入上下文中實(shí)例。同時(shí)也對(duì)PostConstruct注解進(jìn)行處理桦卒。
protected void initializeComponents() {
componentsMap.values().forEach(component -> {
Class<?> componentClass = component.getClass();
// 注入階段 - {@link Resource}
injectComponents(component, componentClass);
// 處理PostConstruct
processPostConstruct(component, componentClass);
});
}
private void injectComponents(Object component, Class<?> componentClass) {
Stream.of(componentClass.getDeclaredFields())
.filter(field -> {
int mods = field.getModifiers();
return !Modifier.isStatic(mods) &&
field.isAnnotationPresent(Resource.class);
}).forEach(field -> {
Resource resource = field.getAnnotation(Resource.class);
String resourceName = resource.name();
Object injectedObject = lookupComponent(resourceName);
field.setAccessible(true);
try {
// 注入目標(biāo)對(duì)象
field.set(component, injectedObject);
} catch (IllegalAccessException e) {
}
});
}
private void processPostConstruct(Object component, Class<?> componentClass) {
Stream.of(componentClass.getMethods())
.filter(method ->
!Modifier.isStatic(method.getModifiers()) && // 非 static
method.getParameterCount() == 0 && // 沒有參數(shù)
method.isAnnotationPresent(PostConstruct.class) // 標(biāo)注 @PostConstruct
).forEach(method -> {
// 執(zhí)行目標(biāo)方法
try {
method.invoke(component);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
使用篇
我們這個(gè)較為完善的依賴注入依賴查找就算完成了立美,現(xiàn)在我們用它來整合JPA。不用spring boot去整合JPA應(yīng)該有很多小伙伴不會(huì)吧方灾。我們需要一個(gè)persistence.xml 文件悯辙。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="emf" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
</persistence-unit>
</persistence>
在我們上篇提到的Context.xml中加上一下內(nèi)容
<Resource name="jdbc/source"
auth="Container"
driverClassName="com.mysql.cj.jdbc.Driver"
maxIdle="30"
maxTotal="50"
maxWaitMillis="-1"
username="root"
password="root"
type="javax.sql.DataSource"
url="jdbc:mysql://localhost:3306/test?useSSL=true"/>
<!--
缺少指定 interface 類型的屬性
目標(biāo)注入的類型:javax.persistence.EntityManager
-->
<Resource name="bean/EntityManager" auth="Container"
type="study.jpa.DelegatingEntityManager"
persistenceUnitName="emf"
propertiesLocation="META-INF/jpa-datasource.properties"
factory="org.apache.naming.factory.BeanFactory" />
一個(gè)配置文件properties文件
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.id.new_generator_mappings=false
hibernate.connection.datasource=@jdbc/source
最后我們?cè)趯?shí)現(xiàn)一個(gè)EntityManager就大功告成了。
private EntityManagerFactory entityManagerFactory;
@PostConstruct
public void init() {
this.entityManagerFactory =
Persistence.createEntityManagerFactory(persistenceUnitName, loadProperties(propertiesLocation));
}
private Map loadProperties(String propertiesLocation) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL propertiesFileURL = classLoader.getResource(propertiesLocation);
Properties properties = new Properties();
try {
properties.load(propertiesFileURL.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
// 增加 JNDI 引用處理
ComponentContext componentContext = ComponentContext.getInstance();
for (String propertyName : properties.stringPropertyNames()) {
String propertyValue = properties.getProperty(propertyName);
if (propertyValue.startsWith("@")) {
String componentName = propertyValue.substring(1);
Object component = componentContext.getComponent(componentName);
properties.put(propertyName, component);
}
}
return properties;
}
public void persist(Object entity) {
getEntityManager().persist(entity);
}
這樣我們就可以通過調(diào)用persist方法來實(shí)現(xiàn)插入操作迎吵,另外由于篇幅有限本文只提供了部分核心代碼躲撰,感興趣的想獲取完整代碼的可以關(guān)注我加我私人微信我發(fā)給你。感謝閱讀到這里;鞣选B5啊!蔫巩!