共计 15246 个字符,预计需要花费 39 分钟才能阅读完成。
作者:小傅哥
博客:https://bugstack.cn
积淀、分享、成长,让本人和别人都能有所播种!😄
一、前言
老司机,你的砖怎么搬的那么快?
是有劲?是技巧?是后门?总之,那个老司机的代码总是能够很快的实现产品每次新增的需要,就像他俩是一家似的!而你就不一样了,不只产品经理还有经营、测试的小姐姐,都得给你买吃的,求着你连忙把 Bug 修修,否则都来不及上线了。
那为啥他人的代码总是能够很快的扩大新性能,而你的代码素来不能被重构只能被重写,小需要小改、大需要大改,没需要呢?没需要本人跑着跑着也能解体,中午被运维薅起来:“你这怎么又有数据库慢查问,把他人业务都利落胯了!”
有人说 30 岁的人都,还和刚毕业的做一样的活,是没提高的! 这太扯淡了,同样是同样的活,但做进去的后果可不肯定是一样的,有人能用 ifelse
把产品性能凑进去,也有人能够把需要拆解成各个功能模块,定义接口、抽象类、实现和继承,使用设计模式构建出一套新增需要时候能疾速实现,呈现问题能精确定位的代码逻辑。这就像有人问:“树上有十只鸟,一枪打死一只,还有几只?”,你会想到什么?枪声大吗、鸟笼了吗、鸟被绑树上了吗、有鸟残疾的吗、鸟被打死了吗、打鸟的人眼睛好使吗、算肚子里怀孕的鸟吗、打鸟犯法吗、边上树还有其余鸟吗等等,这些都是一个职业技术人在一个行业磨难进去的教训,不是 1 天 2 天看几本书,刷几个洗脑文章能排汇的。
二、指标
交给 Spring 治理的 Bean 对象,肯定就是咱们用类创立进去的 Bean 吗?创立进去的 Bean 就永远是单例的吗,没有可能是原型模式吗?
在汇合 Spring 框架下,咱们应用的 MyBatis 框架中,它的核心作用是能够满足用户不须要实现 Dao 接口类,就能够通过 xml 或者注解配置的形式实现对数据库执行 CRUD 操作,那么在实现这样的 ORM 框架中,是怎么把一个数据库操作的 Bean 对象交给 Spring 治理的呢。
因为咱们在应用 Spring、MyBatis 框架的时候都能够晓得,并没有手动的去创立任何操作数据库的 Bean 对象,有的仅仅是一个接口定义,而这个接口定义居然能够被注入到其余须要应用 Dao 的属性中去了,那么这一过程最外围待解决的问题,就是须要实现把简单且以代理形式动态变化的对象,注册到 Spring 容器中。而为了满足这样的一个扩大组件开发的需要,就须要咱们在现有手写的 Spring 框架中,增加这一能力。
三、计划
对于提供一个能让使用者定义简单的 Bean 对象,性能点十分不错,意义也十分大,因为这样做了之后 Spring 的生态种子孵化箱就此提供了,谁家的框架都能够在此规范上实现本人服务的接入。
但这样的性能逻辑设计上并不简单,因为整个 Spring 框架在开发的过程中就曾经提供了各项扩大能力的 接茬
,你只须要在适合的地位提供一个接茬的解决接口调用和相应的性能逻辑实现即可,像这里的指标实现就是对外提供一个能够二次从 FactoryBean 的 getObject 办法中获取对象的性能即可,这样所有实现此接口的对象类,就能够裁减本人的对象性能了。MyBatis 就是实现了一个 MapperFactoryBean 类,在 getObject 办法中提供 SqlSession 对执行 CRUD 办法的操作 整体设计构造如下图:
- 整个的实现过程包含了两局部,一个解决单例还是原型对象,另外一个解决 FactoryBean 类型对象创立过程中对于获取具体调用对象的
getObject
操作。 SCOPE_SINGLETON
、SCOPE_PROTOTYPE
,对象类型的创立获取形式,次要辨别在于AbstractAutowireCapableBeanFactory#createBean
创立实现对象后是否放入到内存中,如果不放入则每次获取都会从新创立。- createBean 执行对象创立、属性填充、依赖加载、前置后置解决、初始化等操作后,就要开始做执行判断整个对象是否是一个 FactoryBean 对象,如果是这样的对象,就须要再继续执行获取 FactoryBean 具体对象中的
getObject
对象了。整个 getBean 过程中都会新增一个单例类型的判断factory.isSingleton()
,用于决定是否应用内存寄存对象信息。
四、实现
1. 工程构造
small-spring-step-09
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
└── ApiTest.java
工程源码 : 公众号「bugstack 虫洞栈」,回复:Spring 专栏,获取残缺源码
Spring 单例、原型以及 FactoryBean
性能实现类关系,如图 10-2
- 以上整个类关系图展现的就是增加 Bean 的实例化是单例还是原型模式以及 FactoryBean 的实现。
- 其实整个实现的过程并不简单,只是在现有的 AbstractAutowireCapableBeanFactory 类以及继承的抽象类 AbstractBeanFactory 中进行扩大。
- 不过这次咱们把 AbstractBeanFactory 继承的 DefaultSingletonBeanRegistry 类,两头加了一层 FactoryBeanRegistrySupport,这个类在 Spring 框架中次要是解决对于 FactoryBean 注册的撑持操作。
2. Bean 的作用范畴定义和 xml 解析
cn.bugstack.springframework.beans.factory.config.BeanDefinition
public class BeanDefinition {
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
private Class beanClass;
private PropertyValues propertyValues;
private String initMethodName;
private String destroyMethodName;
private String scope = SCOPE_SINGLETON;
private boolean singleton = true;
private boolean prototype = false;
// ...get/set
}
- singleton、prototype,是本次在 BeanDefinition 类中新减少的两个属性信息,用于把从 spring.xml 中解析到的 Bean 对象作用范畴填充到属性中。
cn.bugstack.springframework.beans.factory.xml.XmlBeanDefinitionReader
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {for (int i = 0; i < childNodes.getLength(); i++) {
// 判断元素
if (!(childNodes.item(i) instanceof Element)) continue;
// 判断对象
if (!"bean".equals(childNodes.item(i).getNodeName())) continue;
// 解析标签
Element bean = (Element) childNodes.item(i);
String id = bean.getAttribute("id");
String name = bean.getAttribute("name");
String className = bean.getAttribute("class");
String initMethod = bean.getAttribute("init-method");
String destroyMethodName = bean.getAttribute("destroy-method");
String beanScope = bean.getAttribute("scope");
// 获取 Class,不便获取类中的名称
Class<?> clazz = Class.forName(className);
// 优先级 id > name
String beanName = StrUtil.isNotEmpty(id) ? id : name;
if (StrUtil.isEmpty(beanName)) {beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
// 定义 Bean
BeanDefinition beanDefinition = new BeanDefinition(clazz);
beanDefinition.setInitMethodName(initMethod);
beanDefinition.setDestroyMethodName(destroyMethodName);
if (StrUtil.isNotEmpty(beanScope)) {beanDefinition.setScope(beanScope);
}
// ...
// 注册 BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
}
}
}
- 在解析 XML 解决类 XmlBeanDefinitionReader 中,新减少了对于 Bean 对象配置中 scope 的解析,并把这个属性信息填充到 Bean 定义中。
beanDefinition.setScope(beanScope)
3. 创立和批改对象时候判断单例和原型模式
cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
Object bean = null;
try {bean = createBeanInstance(beanDefinition, beanName, args);
// 给 Bean 填充属性
applyPropertyValues(beanName, bean, beanDefinition);
// 执行 Bean 的初始化办法和 BeanPostProcessor 的前置和后置解决办法
bean = initializeBean(beanName, bean, beanDefinition);
} catch (Exception e) {throw new BeansException("Instantiation of bean failed", e);
}
// 注册实现了 DisposableBean 接口的 Bean 对象
registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
// 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE
if (beanDefinition.isSingleton()) {addSingleton(beanName, bean);
}
return bean;
}
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
// 非 Singleton 类型的 Bean 不执行销毁办法
if (!beanDefinition.isSingleton()) return;
if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
}
}
// ... 其余性能
}
- 单例模式和原型模式的区别就在于是否寄存到内存中,如果是原型模式那么就不会寄存到内存中,每次获取都从新创建对象,另外非 Singleton 类型的 Bean 不须要执行销毁办法。
- 所以这里的代码会有两处批改,一处是 createBean 中判断是否增加到 addSingleton(beanName, bean);,另外一处是 registerDisposableBeanIfNecessary 销毁注册中的判断
if (!beanDefinition.isSingleton()) return;
。
4. 定义 FactoryBean 接口
cn.bugstack.springframework.beans.factory.FactoryBean
public interface FactoryBean<T> {T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();}
- FactoryBean 中须要提供 3 个办法,获取对象、对象类型,以及是否是单例对象,如果是单例对象仍然会被放到内存中。
5. 实现一个 FactoryBean 注册服务
cn.bugstack.springframework.beans.factory.support.FactoryBeanRegistrySupport
public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {
/**
* Cache of singleton objects created by FactoryBeans: FactoryBean name --> object
*/
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();
protected Object getCachedObjectForFactoryBean(String beanName) {Object object = this.factoryBeanObjectCache.get(beanName);
return (object != NULL_OBJECT ? object : null);
}
protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {if (factory.isSingleton()) {Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {object = doGetObjectFromFactoryBean(factory, beanName);
this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
}
return (object != NULL_OBJECT ? object : null);
} else {return doGetObjectFromFactoryBean(factory, beanName);
}
}
private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){
try {return factory.getObject();
} catch (Exception e) {throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);
}
}
}
- FactoryBeanRegistrySupport 类次要解决的就是对于 FactoryBean 此类对象的注册操作,之所以放到这样一个独自的类里,就是心愿做到不同畛域模块下只负责各自须要实现的性能,防止因为扩大导致类收缩到难以保护。
- 同样这里也定义了缓存操作 factoryBeanObjectCache,用于寄存单例类型的对象,防止反复创立。在日常应用用,根本也都是创立的单例对象
- doGetObjectFromFactoryBean 是具体的获取 FactoryBean#getObject() 办法,因为既有缓存的解决也有对象的获取,所以额定提供了 getObjectFromFactoryBean 进行逻辑包装,这部分的操作形式是不和你日常做的业务逻辑开发十分类似。从 Redis 取数据,如果为空就从数据库获取并写入 Redis
6. 扩大 AbstractBeanFactory 创建对象逻辑
cn.bugstack.springframework.beans.factory.support.AbstractBeanFactory
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {protected <T> T doGetBean(final String name, final Object[] args) {Object sharedInstance = getSingleton(name);
if (sharedInstance != null) {
// 如果是 FactoryBean,则须要调用 FactoryBean#getObject
return (T) getObjectForBeanInstance(sharedInstance, name);
}
BeanDefinition beanDefinition = getBeanDefinition(name);
Object bean = createBean(name, beanDefinition, args);
return (T) getObjectForBeanInstance(bean, name);
}
private Object getObjectForBeanInstance(Object beanInstance, String beanName) {if (!(beanInstance instanceof FactoryBean)) {return beanInstance;}
Object object = getCachedObjectForFactoryBean(beanName);
if (object == null) {FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
object = getObjectFromFactoryBean(factoryBean, beanName);
}
return object;
}
// ...
}
- 首先这里把 AbstractBeanFactory 原来继承的 DefaultSingletonBeanRegistry,批改为继承 FactoryBeanRegistrySupport。因为须要扩大出创立 FactoryBean 对象的能力,所以这就想一个链条服务上,截出一个段来解决额定的服务,并把链条再链接上。
- 此处新减少的性能次要是在 doGetBean 办法中,增加了调用
(T) getObjectForBeanInstance(sharedInstance, name)
对获取 FactoryBean 的操作。 - 在 getObjectForBeanInstance 办法中做具体的 instanceof 判断,另外还会从 FactoryBean 的缓存中获取对象,如果不存在则调用 FactoryBeanRegistrySupport#getObjectFromFactoryBean,执行具体的操作。
五、测试
1. 当时筹备
cn.bugstack.springframework.test.bean.IUserDao
public interface IUserDao {String queryUserName(String uId);
}
- 这个章节咱们删掉 UserDao,定义一个 IUserDao 接口,之所这样做是为了通过 FactoryBean 做一个自定义对象的代理操作。
cn.bugstack.springframework.test.bean.UserService
public class UserService {
private String uId;
private String company;
private String location;
private IUserDao userDao;
public String queryUserInfo() {return userDao.queryUserName(uId) + "," + company + "," + location;
}
// ...get/set
}
- 在 UserService 新批改了一个原有 UserDao 属性为 IUserDao,前面咱们会给这个属性注入代理对象。
2. 定义 FactoryBean 对象
cn.bugstack.springframework.test.bean.ProxyBeanFactory
public class ProxyBeanFactory implements FactoryBean<IUserDao> {
@Override
public IUserDao getObject() throws Exception {InvocationHandler handler = (proxy, method, args) -> {Map<String, String> hashMap = new HashMap<>();
hashMap.put("10001", "小傅哥");
hashMap.put("10002", "八杯水");
hashMap.put("10003", "阿毛");
return "你被代理了" + method.getName() + ":" + hashMap.get(args[0].toString());
};
return (IUserDao) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserDao.class}, handler);
}
@Override
public Class<?> getObjectType() {return IUserDao.class;}
@Override
public boolean isSingleton() {return true;}
}
- 这是一个实现接口 FactoryBean 的代理类 ProxyBeanFactory 名称,次要是模仿了 UserDao 的原有性能,相似于 MyBatis 框架中的代理操作。
- getObject() 中提供的就是一个 InvocationHandler 的代理对象,当有办法调用的时候,则执行代理对象的性能。
3. 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userService" class="cn.bugstack.springframework.test.bean.UserService" scope="prototype">
<property name="uId" value="10001"/>
<property name="company" value="腾讯"/>
<property name="location" value="深圳"/>
<property name="userDao" ref="proxyUserDao"/>
</bean>
<bean id="proxyUserDao" class="cn.bugstack.springframework.test.bean.ProxyBeanFactory"/>
</beans>
- 在配置文件中,咱们把 proxyUserDao 这个代理对象,注入到 userService 的 userDao 中。与上一章节相比,去掉了 UserDao 实现类,转而用代理类替换
4. 单元测试(单例 & 原型)
@Test
public void test_prototype() {
// 1. 初始化 BeanFactory
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.registerShutdownHook();
// 2. 获取 Bean 对象调用办法
UserService userService01 = applicationContext.getBean("userService", UserService.class);
UserService userService02 = applicationContext.getBean("userService", UserService.class);
// 3. 配置 scope="prototype/singleton"
System.out.println(userService01);
System.out.println(userService02);
// 4. 打印十六进制哈希
System.out.println(userService01 + "十六进制哈希:" + Integer.toHexString(userService01.hashCode()));
System.out.println(ClassLayout.parseInstance(userService01).toPrintable());
}
- 在 spring.xml 配置文件中,设置了 scope=”prototype” 这样就每次获取到的对象都应该是一个新的对象。
- 这里判断对象是否为一个会看到打印的类对象的哈希值,所以咱们把十六进制哈希打印进去。
测试后果
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@1b0375b3
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@2f7c7260
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@1b0375b3 十六进制哈希:1b0375b3
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 b3 75 03 (00000001 10110011 01110101 00000011) (58045185)
4 4 (object header) 1b 00 00 00 (00011011 00000000 00000000 00000000) (27)
8 4 (object header) 9f e1 01 f8 (10011111 11100001 00000001 11111000) (-134094433)
12 4 java.lang.String UserService.uId (object)
16 4 java.lang.String UserService.company (object)
20 4 java.lang.String UserService.location (object)
24 4 cn.bugstack.springframework.test.bean.IUserDao UserService.userDao (object)
28 1 boolean UserService$$EnhancerByCGLIB$$4cabb984.CGLIB$BOUND true
29 3 (alignment/padding gap)
32 4 net.sf.cglib.proxy.NoOp UserService$$EnhancerByCGLIB$$4cabb984.CGLIB$CALLBACK_0 (object)
36 4 (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
Process finished with exit code 0
- 对象前面的这一小段字符串就是 16 进制哈希值,在对象头哈希值寄存的后果上看,也有对应的数值。只不过这个后果是倒过去的。
- 另外能够看到 cabb984@1b0375b3、cabb984@2f7c7260,这两个对象的结尾 16 进制哈希值并不一样,所以咱们的原型模式是失效的。
5. 单元测试(代理对象)
@Test
public void test_factory_bean() {
// 1. 初始化 BeanFactory
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.registerShutdownHook();
// 2. 调用代理办法
UserService userService = applicationContext.getBean("userService", UserService.class);
System.out.println("测试后果:" + userService.queryUserInfo());
}
- 对于 FactoryBean 的调用并没有太多不一样,因为所有的不同都曾经被 spring.xml 配置进去了。当然你能够间接调用 spring.xml 配置的对象
cn.bugstack.springframework.test.bean.ProxyBeanFactory
测试后果
测试后果:你被代理了 queryUserName:小傅哥, 腾讯, 深圳
Process finished with exit code 0
- 从测试后果来看,咱们的代理类 ProxyBeanFactory 曾经完满替换掉了 UserDao 的性能。
- 尽管看上去这一点实现并不简单,甚至有点简略。但就是这样一点点核心内容的设计了,解决了所有须要和 Spring 联合的其余框架交互链接问题。如果对此类内容感兴趣,也能够浏览小傅哥《中间件设计和开发》
六、总结
- 在 Spring 框架整个开发的过程中,后期的各个性能接口类扩大的像收缩了似的,但到前期在欠缺性能时,就没有那么难了,反而深刻了解后会感觉性能的补充,都比较简单。只须要再所遇畛域范畴内进行扩大相应的服务实现即可。
- 当你仔细阅读完对于 FactoryBean 的实现以及测试过程的应用,当前再须要应用 FactoryBean 开发相应的组件时候,肯定会十分分明它是如何创立本人的简单 Bean 对象以及在什么时候初始化和调用的。遇到问题也能够疾速的排查、定位和解决。
- 如果你在学习的过程中感觉这些类、接口、实现、继承,穿梭的很简单,一时半会脑子还反馈不过去。那么你最好的形式是入手去画画这些类关系图,梳理下实现的构造,看看每个类在干什么。看只能是晓得,入手能力学会!
七、系列举荐
- 你说,怎么把 Bean 塞到 Spring 容器?
- 《Java 面经手册》PDF,全书 417 页 11.5 万字,完稿 & 发版!
- 数学,离一个程序员有多近?
- 工作 3 年,看啥材料能月薪 30K?
- PPT 画成这样,述职问难还能过吗?