上篇 Latke 源码解析(一)Servlet 部分讲解了 latke 有关 web 请求的 servlet 部分,这次深入了解一下它的 Ioc 部分内容。
前言
本文分析基于 latke2.3.6 版本,这部分内容主要涉及到了以下 2 个类库的使用,请留意。
javax.enterprise
依赖注入相关(Contexts and Dependency Injection for Java)javassist
,操作 java 字节码的类库
备注: 本人水平有限,若发现文章有误,请积极留言。
一、从监听器开始
同 Spring 一样,latke 通过配置监听器初始化 bean。在 latke-demo 的 web.xml
监听器部分如下
<listener>
<listener-class>org.b3log.latke.demo.hello.HelloServletListener</listener-class>
</listener>
从这里可以看出,该 demo 的监听器为 HelloServletListener
。下面看看 HelloServletListener
HelloServletListener.java
public class HelloServletListener extends AbstractServletListener {
@Override
public void contextInitialized(final ServletContextEvent servletContextEvent) {
Latkes.setScanPath("org.b3log.latke.demo.hello"); //设置项目扫描的包
super.contextInitialized(servletContextEvent); //调用父类的contextInitialized()方法
//省略其他操作
}
//省略其他方法
}
HelloServletListener
继承了 AbstractServletListener
。而 AbstractServletListener
是 latke 提供的默认监听器抽象类,在此类中已实现 bean 的查找注册等许多功能。此类也是我们稍后分析的重点,让我们先看一些它的定义。(为简洁,已去掉与 ioc 无关内容)
AbstractServletListener.java
public abstract class AbstractServletListener implements ServletContextListener, ServletRequestListener, HttpSessionListener {
/**
* Servlet context.
*/
private static ServletContext servletContext;
@Override
public void contextInitialized(final ServletContextEvent servletContextEvent) {
//省略其他配置项
try {
final Collection<Class<?>> beanClasses = Discoverer.discover(Latkes.getScanPath()); //按需要加载项目中的类文件
Lifecycle.startApplication(beanClasses); // 创建注册bean
} catch (final Exception e) {
//异常处理
}
CronService.start(); //定时任务
}
//其他方法
可以知道,HelloServletListener
通过继承 AbstractServletListener
实现了 ServletContextListener
这个监听器,可以监听 servlet 容器创建销毁事件。当项目启动时,contextInitialized()方法执行,bean 就可以被加载及初始化。从而为 servlet 部分的请求服务。
须知:
HelloServletListener
还实现了ServletRequestListener,ServletRequestListener
两个监听器,用于监听 request、session 变化,不过这里不是重点。
二、AbstractServletListener
我们已经知道,此类的 contextInitialized
方法在项目启动时执行,完成 bean
的创建工作。
具体的执行逻辑分为两步,对应方法内的两行代码
-
Discoverer.discover(Latkes.getScanPath())
扫描这些指定的类,选出其中包含特定注解的类(@RequestProcessor、@Service 等),并加载这些特定类。
-
Lifecycle.startApplication(beanClasses);
beanClasses 为上步得到的类,解析这些类依赖并创建为 bean。
1.discover 方法
discover 接受字符串形式的类路径,一般是项目的根目录包,也好理解,扫描就要扫描整个项目,把能注册的 bean 的类先找到再说。如 demo 项目中,HelloServletListener
传的就是 org.b3log.latke.demo.hello
。
在 discover 方法中,要知道:
- discover 的主要任务是找到特定类并加载它们。
- 除传入的类路径外,discover 还要扫描并处理一些内置的包,如
org.b3log.latke.remote
这个包。 - discover 使用了 javassist 类库中的 ClassFile,javassist 是一个用来处理 Java 字节码的类库。详细请参考 javassist 使用指南
具体的解析过程较为复杂繁琐。这里只看看大致流程的代码
public static Collection<Class<?>> discover(final String scanPath) throws Exception {
//将传入类包与内置包组合
final String[] paths = ArrayUtils.concatenate(splitPaths, BUILT_IN_COMPONENT_PKGS);
//遍历路径,将其转为磁盘绝对路径,便于javassist解析
for (String path : paths) {
if (!AntPathMatcher.isPattern(path)) {
path = path.replaceAll("\\.", "/") + "/**/*.class";
}
urls.addAll(ClassPathResolver.getResources(path));
}
for (URL url : urls) {
//javassist读取class文件,并将文件中类注解信息解析出来
//遍历一个类下所有注解
for (final Annotation annotation : annotations){
//包含`@RequestProcessor,Service,Repository,Named`注解将maybeBeanClass设置true
}
if (maybeBeanClass) {
clz = Thread.currentThread().getContextClassLoader().loadClass(className); //符合条件,加载此类
ret.add(clz);
}
}
return ret; //返回Class<?>类型集合信息
}
2. startApplication 方法
上步中,已经加载了这些 bean 类,下面就该解析这些 bean 的依赖关系并放在所谓的 bean 容器中以便于管理了。
startApplication 的代码不多,我们来看看
public static void startApplication(final Collection<Class<?>> classes, final BeanModule... beanModule) {
beanManager = LatkeBeanManagerImpl.getInstance(); //获得beanManager实例
applicationContext.setActive(true);
beanManager.addContext(applicationContext); //添加上下文
final Configurator configurator = beanManager.getConfigurator();
if (null != classes && !classes.isEmpty()) {
configurator.createBeans(classes); //classes为discover()的解析值,据此创建bean
}
//设置beanModule
}
其中最为关键的是 configurator.createBeans(classes);
这行代码,查看 ConfiguratorImpl
的 createBeans()
方法,为遍历调用 createBean()
方法创建。
public <T> LatkeBean<T> createBean(final Class<T> beanClass) {
try {
return (LatkeBean<T>) beanManager.getBean(beanClass); //若存在直接返回bean
} catch (final Exception e) {
LOGGER.log(Level.TRACE, "Not found bean [beanClass={0}], so to create it", beanClass);
}
if (!Beans.checkClass(beanClass)) { //检查是否是符合条件的bean,不能是接口或抽象类
//抛出异常
}
final String name = Beans.getBeanName(beanClass);
if (null == name) {
//打印日志,返回
}
//获取此bean@Qualifier注解信息
final Set<Annotation> qualifiers = Beans.getQualifiers(beanClass, name);
//获取此bean@Scope注解信息
final Class<? extends Annotation> scope = Beans.getScope(beanClass);
//获取此bean父类及实现的接口信息
final Set<Type> beanTypes = Beans.getBeanTypes(beanClass);
final Set<Class<? extends Annotation>> stereotypes = Beans.getStereotypes(beanClass);
//创建bean实例,此为真正创建的方法
final LatkeBean<T> ret = new BeanImpl<T>(beanManager, name, scope, qualifiers, beanClass, beanTypes, stereotypes);
beanManager.addBean(ret); //将bean添加到容器中
for (final Type beanType : beanTypes) {
addTypeClassBinding(beanType, beanClass);
}
for (final Annotation qualifier : qualifiers) {
addClassQualifierBinding(beanClass, qualifier);
addQualifierClassBinding(qualifier, beanClass);
}
return ret;
}
三、深入 bean 创建过程
BeanImpl 的构造方法是创建 bean 的关键,来看看
public BeanImpl(final LatkeBeanManager beanManager, final String name, final Class<? extends Annotation> scope,
final Set<Annotation> qualifiers, final Class<T> beanClass, final Set<Type> types,
final Set<Class<? extends Annotation>> stereotypes) {
this.beanManager = beanManager;
this.name = name;
this.scope = scope;
this.qualifiers = qualifiers;
this.beanClass = beanClass;
this.types = types;
this.stereotypes = stereotypes;
this.configurator = beanManager.getConfigurator();
javassistMethodHandler = new JavassistMethodHandler(beanManager);
final ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(beanClass);
proxyFactory.setFilter(javassistMethodHandler.getMethodFilter());
proxyClass = proxyFactory.createClass(); //得到该bean的Class对象
// ① 查看这个bean在构造器、方法、字段上的有无@Inject注解,有则存起来
annotatedType = new AnnotatedTypeImpl<T>(beanClass);
constructorParameterInjectionPoints = new HashMap<AnnotatedConstructor<T>, List<ParameterInjectionPoint>>();
constructorParameterProviders = new ArrayList<ParameterProvider<?>>();
methodParameterInjectionPoints = new HashMap<AnnotatedMethod<?>, List<ParameterInjectionPoint>>();
methodParameterProviders = new HashMap<AnnotatedMethod<?>, List<ParameterProvider<?>>>();
fieldInjectionPoints = new HashSet<FieldInjectionPoint>();
fieldProviders = new HashSet<FieldProvider<?>>();
//根据①中结果,处理依赖
initFieldInjectionPoints();
initConstructorInjectionPoints();
initMethodInjectionPoints();
}
主要有两个过程,一是通过 javassist
得到这个 bean 的 Class 对象,以便后续操作。再者初始化该类依赖。上面注释了流程。我们来看看怎样初始化字段上的 @Inject 注解
BeanImpl.java
private void initFieldInjectionPoints() {
//此bean中含有@Inject注解的set集合
final Set<AnnotatedField<? super T>> annotatedFields = annotatedType.getFields();
for (final AnnotatedField<? super T> annotatedField : annotatedFields) {
final Field field = annotatedField.getJavaMember();
if (field.getType().equals(Provider.class)) { // by provider
final FieldProvider<T> provider = new FieldProvider<T>(beanManager, annotatedField);
fieldProviders.add(provider);
final FieldInjectionPoint fieldInjectionPoint = new FieldInjectionPoint(this, annotatedField);
fieldInjectionPoints.add(fieldInjectionPoint);
} else { // 字段类型不是Provider走这个流程
//构造一个FieldInjectionPoint对象,加入此bean的fieldInjectionPoints中
final FieldInjectionPoint fieldInjectionPoint = new FieldInjectionPoint(this, annotatedField);
fieldInjectionPoints.add(fieldInjectionPoint);
}
}
}
至此,bean 的属性基本已构造完毕,以 HelloProcessor
这个类对应的 bean,我添加了一个 Service 依赖,如下
@RequestProcessor
public class HelloProcessor {
@Inject
private UserService userService;
@Before(adviceClass = HelloAdvice.class)
@RequestProcessing(value = {"/", "/index", "/index.*", "/**/ant/*/path"}, method = HTTPRequestMethod.GET)
public void index(final HTTPRequestContext context) {
//省略
}
}
则此 bean 到目前分析这,其属性构造如下图
亮的部分 fieldInjectionPoints
属性表示该 bean 字段上的依赖集合,可以看到 userService
这个依赖。至此 bean 就算基本创建完毕了。其他部分诸如分析依赖的工作,等下次有时间再分析吧。
参考文章:
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于