如何将动态代理对象交由Spring容器管理
如何将动态代理对象交由Spring容器管理,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
1. spring bean的实例化过程
我们平时工作日常开发中,要讲类的实例对象注入ioc容器,只需要在类上加上相应的注解即可实现。一般用的多的就是@Controller,@Service,@Component等注解,那么用都是这么用,其中的原理倒是不被人理解。先过一下spring bean的加载过程。
① spring中如何描述一个bean的
说到这里,我们先看看java中一个实例是如何描述的。
java中通过类来描述对象实例的,一个对象具有什么属性,方法,都是在类中进行定义。然后将其变异为class字节码文件,通过new关键字就能得到一个实例对象。
那么在spring中的bean是如何描述呢?
前部分相同,通过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@2d459bdaObject 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实现类
@Componentpublic 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@45dc7beSystem.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)@Documentedpublic @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 T1MyDao1 myDao1 = SpringContextUtils.getBean(MyDao1.class);myDao1.query(); //SELECT * FROM T2
6. 总结
spring真是在高扩展这方面做得很好,第三方插件可以实现不同的接口,实现对spring进行扩展。就像Mybatis整合到Spring。我们在日常开发中,为了应付多变的需求,设计出易扩展的程序是非常有必要的。一味的按照需求实现硬编码,后面业务变更,只有加班的份,然后就天天改,自己技术也局限了。整天被产品拖着鼻子走。天天加班,时间都花在重复的工作上了,对自己的成长没有啥好处。
看完上述内容,你们掌握如何将动态代理对象交由Spring容器管理的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!