1. spring bean的实例化过程
我们平时工作日常开发中,要讲类的实例对象注入ioc容器,只需要在类上加上相应的注解即可实现。一般用的多的就是@Controller,@Service,@Component等注解,那么用都是这么用,其中的原理倒是不被人理解。先过一下spring bean的加载过程。
① spring中如何描述一个bean的
java中通过类来描述对象实例的,一个对象具有什么属性,方法,都是在类中进行定义。然后将其变异为class字节码文件,通过new关键字就能得到一个实例对象。
前部分相同,通过Registrar注册到beanDefinition中,在spring中用beanDefinition对象来描述需要实例化的对象,包含了这个bean的名称,class,是否是抽象,是否为单例等信息,然后通过preInstantiateSingletons实例化到ioc容器中。我们要使用的对象的时候,就从ioc容器中去获取相应的对象即可。
在spring bean的实例化过程中,spring设计了很多拦截点,可以在动态的改变实例化对象的相关信息。达到在ioc容器中的对象和最开始注册到beanDefinition中的信息不同。
2. FactoryBean
现在来看看看FactoryBean。FactoryBean从名字来看以bean结尾那应该就是一个bean吧,没错它确实是一个bean,不同于普通Bean的是:它是实现了FactoryBean
特殊性质:
根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。
public interface FactoryBean {
//获取bean对应的实例对象
@Nullable
T getObject() throws Exception;
//获取factoryBean获取到的实例类型
@Nullable
Class> getObjectType();
//FactoryBean创建的实例是否是单实例
default boolean isSingleton() {
return true;
}
}
下面我们通过自定义一个FactoryBean来验证一下这个bean的特殊性质。先准备一个测试bean
public class TestBean {
}
编写自定义FactoryBean
@Component("myFactoryBean")
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new TestBean();
}
@Override
public Class> getObjectType() {
return TestBean.class;
}
}
编写测试类。从springioc容器中获取myFactoryBean实例。
Object bean = SpringContextUtils.getBean("myFactoryBean");
System.out.println(bean); //com.wj.factorybean.TestBean@2d459bda
Object bean = SpringContextUtils.getBean("&myFactoryBean");
System.out.println(bean); //com.wj.factorybean.MyFactoryBean@60ab895f
看到打印结果,验证了上述的性质。
3. BeanFactoryPostProcessor
BeanFactoryPostProcessor可以在对象实例化到ioc容器之前对原有的beanDefinition的一些属性进行设置更新。
先来看例子。准备连个bean,其中TestBean我们@omponent注解,而TestBean1不做处理
@Component("testBean")
public class TestBean {
}
public class TestBean1 {
}
编写BeanFactoryPostProcessor实现类
@Component
public class MyFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//获取testBean的beanDefinition
GenericBeanDefinition beanDefinition = (GenericBeanDefinition)beanFactory.getBeanDefinition("testBean");
//更改class为TestBean1
beanDefinition.setBeanClass(TestBean1.class);
}
}
编写测试类进行测试
Object bean = SpringContextUtils.getBean("testBean");
// com.wj.factorybean.TestBean1@45dc7be
System.out.println(bean);
我分明获取的是testBean的实例,结果尽然获取到的是TestBean1实例。说明我们通过BeanFactoryPostProcessor在对象实例化之前将对象更改了。所以我们获取到的是TestBean1。我们有了BeanFactoryPostProcessor,就可以在对象实例化到ioc容器之前对即将要实例化的对象做一些手脚。但是这仅仅是更新。那么我们要手动将对象注册到BeanDefinition呢。下面的ImportBeanDefinitionRegistrar就发挥用处了
4. ImportBeanDefinitionRegistrar
貌似说了这么多,都没扯到今天的主题上,动态代理对象????,压根没有提到啊。不急,待会就来,我们是一步步慢慢接近主题了。
ImportBeanDefinitionRegistrar可以动态将自己的对象注册到BeanDefinition,然后会spring的bean实例化流程,生成实例对象到ioc容器。
编写测试Dao接口,为什么要是接口呢?因为我们要利用代理生成Dao的实例对象啊
public interface MyDao {
void query();
}
编写自定义Registrar
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
这里用到前面定义的MyFactoryBean
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
//生成beanDefinition
GenericBeanDefinition beanDefinition = (GenericBeanDefinition)builder.getBeanDefinition();
//将beanDefinition注册
beanDefinitionRegistry.registerBeanDefinition(MyDao.class.getName(),beanDefinition);
}
}
更改MyFactoryBean,动态代理生成接口MyDao对象
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
//利用动态代理生成MyDao的实例对象
Object instance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{MyDao.class}, new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(”执行业务逻辑“);
return null;
}
});
return instance;
}
@Override
public Class> getObjectType() {
return MyDao.class;
}
}
自定义注解@MyScan,并通过@Import导入MyImportBeanDefinitionRegistrar。这样就会被spring扫描到
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MyImportBeanDefinitionRegistrar.class})
public @interface MyScan {
}
最后一步,在项目启动类加上@MyScan。并编写测试。调用Mydao
MyDao myDao = SpringContextUtils.getBean(MyDao.class);
myDao.execute(); //打印出”执行业务逻辑“
到这里我们就将动态代理的类交由ioc管理起来了。
5. 简单模拟Mybaitis中的动态代理Mapper接口执行sql
我们引申一下。我们不是有一个MyDao吗?并且在MyFactoryBean中代理实现的时候也是讲其硬编码写死的。MyImportBeanDefinitionRegistrar中也是写死的,这样可不行,那么我们要怎么将其写活呢。
在MyFactoryBean定义变量来接受class,并通过构造函数设置值。最后修改后的MyFactoryBean如下
public class MyFactoryBean implements FactoryBean {
private Class classzz;
public MyFactoryBean(Class classzz){
this.classzz = classzz;
}
@Override
public Object getObject() throws Exception {
//利用动态代理生成实例对象
Object instance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{classzz.class}, new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(”执行业务逻辑“);
return null;
}
});
return instance;
}
@Override
public Class> getObjectType() {
return this.classzz;
}
}
更改MyImportBeanDefinitionRegistrar逻辑,我们定义一个Class数据来模拟多个class。通过beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getTypeName());调用MyFactoryBean的有参构造函数生成MyFactoryBean。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//这里将数组写死了。我们可以定义一个包,扫描包下的所有接口class,这里就不做实现了,这里为了演示效果,多定义了一个接口MyDao1,跟MyDao定义相同的,代码就不贴出来了。
Class[] classes = {MyDao.class,MyDao1.class};
for (Class aClass : classes) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
GenericBeanDefinition beanDefinition = (GenericBeanDefinition)builder.getBeanDefinition();
//调用刚刚定义的MyFactoryBean有参构造函数
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getTypeName());
beanDefinitionRegistry.registerBeanDefinition(aClass.getName(),beanDefinition);
}
}
}
测试实例
MyDao myDao = SpringContextUtils.getBean(MyDao.class);
myDao.query(); //执行业务逻辑
MyDao1 myDao1 = SpringContextUtils.getBean(MyDao1.class);
myDao1.query(); //执行业务逻辑
有没有感觉到有点类似mybatis了,接口Mapper,没有任何实现,但是可以直接@Autowired进行调用,没错,就是在模拟Mybatis。不过,我们自己定义的@MyScan注解,它的是@MapperScan注解,后面参数为Mapper的包路径,我们这里就没有实现类,因为我们在MyImportBeanDefinitionRegistrar中定义数组来模拟包路径扫描class了。下面再完善一下,我们调用了连个Dao都执行了相同的逻辑。应该执行不同的sql查询才对啊。我们就来实现这点。
自定义@Select注解,这个注解就是用在Dao接口方法定义上的,value为sql语句
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Select {
String value() default "";
}
在Dao接口中使用@Select注解
public interface MyDao {
@Select("SELECT * FROM T1")
void query();
}
public interface MyDao1 {
@Select("SELECT * FROM T2")
void query();
}
在动态代理生成代理对象的InvocationHandler编写具体获取sql逻辑。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String value = method.getDeclaredAnnotation(Select.class).value();
System.out.println(value);
return null;
}
调用刚刚刚刚的测试方法,打印sql语句。
MyDao myDao = SpringContextUtils.getBean(MyDao.class);
myDao.query(); //SELECT * FROM T1
MyDao1 myDao1 = SpringContextUtils.getBean(MyDao1.class);
myDao1.query(); //SELECT * FROM T2
6. 总结
spring真是在高扩展这方面做得很好,第三方插件可以实现不同的接口,实现对spring进行扩展。就像Mybatis整合到Spring。我们在日常开发中,为了应付多变的需求,设计出易扩展的程序是非常有必要的。一味的按照需求实现硬编码,后面业务变更,只有加班的份,然后就天天改,自己技术也局限了。整天被产品拖着鼻子走。天天加班,时间都花在重复的工作上了,对自己的成长没有啥好处。