文章来自公众号三不猴欢迎关注我的公众号,公众号内回复666获取面试资料,回复电子书获取200本PDF电子书
前言
之前写过一篇JNDI实现一个依赖注入的文章,很多小伙伴都表示很疑惑,那玩意儿有啥用,包括这篇文章可能你也觉得没啥用,确实在实际开发中都是使用spring来做依赖查找、依赖注入,那你有没有想过在没有spring的年代是怎么做依赖查找和依赖注入的?没错就是可以使用JNDI。写JNDI系列文章的目的是为了了解JAVAEE单体架构是如何演变成现在spring 技术栈的,这是一个系列后面将一步一步演进成主流spring、spring boot、spring cloud风格,具有Spring、SpringMVC完整功能的项目。欢迎关注了解后续。看完本文你将了解如果让你来实现一个spring 的思路,大致流程。
实践
上篇我们通过JNDI把xml中的元信息读出,放入ServletContext中,使用了JNDI中的lookup方法作为依赖查找。下面开始对之前的代码进行重构。
首先对初始化方法就行重构,我们先把初始化的动作定义成:初始化环境、实例化组件、初始化组件三个步骤。看过spring源码的小伙伴是不是直呼有内味儿了。
public void init(ServletContext servletContext) throws RuntimeException {
ComponentContext.servletContext = servletContext;
servletContext.setAttribute(CONTEXT_NAME, this);
// 获取当前 ServletContext(WebApp)ClassLoader
this.classLoader = servletContext.getClassLoader();
initEnvContext();
instantiateComponents();
initializeComponents();
}
初始化环境
我们这个项目的环境就是初始化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);
}
}
有初始肯定也有销毁操作,代码如下:(因为JNDI有很多检查异常,书写时有诸多不便,所以这里用了一个FunctionalInterface 包装一下,不影响主流程,当做这个context.close()就好,下文不再提及)
private static void close(Context context) {
if (context != null) {
ThrowableAction.execute(context::close);
}
}
实例化组件
实例化组件的思路就是把根目录下的元数据全部读出来,并放入缓存map中。具体细节参照代码注释。
/**
* 实例化组件
*/
protected void instantiateComponents() {
// 遍历获取所有的组件名称
List<String> componentNames = listAllComponentNames();
// 通过依赖查找,实例化对象( Tomcat BeanFactory setter 方法的执行,仅支持简单类型)
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
// 节点 -
if (e == null) { // 当前 JNDI 名称下没有子节点
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)) {
// 如果当前名称是目录(Context 实现类)的话,递归查找
fullNames.addAll(listComponentNames(element.getName()));
} else {
// 否则,当前名称绑定目标类型的话话,添加该名称到集合中
String fullName = name.startsWith("/") ?
element.getName() : name + "/" + element.getName();
fullNames.add(fullName);
}
}
return fullNames;
});
}
初始化实例
初始化的过程就是类似spring中使用了@Autowired我们要将JNDI上下文context中的实例注入进去,这里我们使用反射调用set方法完成注入。我们使用Resource注解,目标是将使用了@Resource的字段注入上下文中实例。同时也对PostConstruct注解进行处理。
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 {
// 注入目标对象
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 && // 没有参数
method.isAnnotationPresent(PostConstruct.class) // 标注 @PostConstruct
).forEach(method -> {
// 执行目标方法
try {
method.invoke(component);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
使用篇
我们这个较为完善的依赖注入依赖查找就算完成了,现在我们用它来整合JPA。不用spring boot去整合JPA应该有很多小伙伴不会吧。我们需要一个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中加上一下内容
<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 类型的属性
目标注入的类型: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" />
一个配置文件properties文件
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.id.new_generator_mappings=false
hibernate.connection.datasource=@jdbc/source
最后我们在实现一个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);
}
这样我们就可以通过调用persist方法来实现插入操作,另外由于篇幅有限本文只提供了部分核心代码,感兴趣的想获取完整代码的可以关注我加我私人微信我发给你。感谢阅读到这里!!!!