千家信息网

如何将动态代理对象交由Spring容器管理

发表于:2024-10-02 作者:千家信息网编辑
千家信息网最后更新 2024年10月02日,如何将动态代理对象交由Spring容器管理,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. spring bean的实例化过程我们平时
千家信息网最后更新 2024年10月02日如何将动态代理对象交由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。
特殊性质:
根据该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容器管理的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!

0