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

发布时间:2021-10-20 17:39:09 作者:柒染
来源:亿速云 阅读:280

如何将动态代理对象交由Spring容器管理,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

1. spring bean的实例化过程

我们平时工作日常开发中,要讲类的实例对象注入ioc容器,只需要在类上加上相应的注解即可实现。一般用的多的就是@Controller,@Service,@Component等注解,那么用都是这么用,其中的原理倒是不被人理解。先过一下spring bean的加载过程。

① spring中如何描述一个bean的

说到这里,我们先看看java中一个实例是如何描述的。
如何将动态代理对象交由Spring容器管理

java中通过类来描述对象实例的,一个对象具有什么属性,方法,都是在类中进行定义。然后将其变异为class字节码文件,通过new关键字就能得到一个实例对象。

那么在spring中的bean是如何描述呢?
如何将动态代理对象交由Spring容器管理

前部分相同,通过Registrar注册到beanDefinition中,在spring中用beanDefinition对象来描述需要实例化的对象,包含了这个bean的名称,class,是否是抽象,是否为单例等信息,然后通过preInstantiateSingletons实例化到ioc容器中。我们要使用的对象的时候,就从ioc容器中去获取相应的对象即可。

在spring bean的实例化过程中,spring设计了很多拦截点,可以在动态的改变实例化对象的相关信息。达到在ioc容器中的对象和最开始注册到beanDefinition中的信息不同。

2. FactoryBean

现在来看看看FactoryBean。FactoryBean从名字来看以bean结尾那应该就是一个bean吧,没错它确实是一个bean,不同于普通Bean的是:它是实现了FactoryBean<T>接口的Bean。
特殊性质:
根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。

public interface FactoryBean<T> {
    //获取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的一些属性进行设置更新。

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

先来看例子。准备连个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容器。
如何将动态代理对象交由Spring容器管理

编写测试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。我们在日常开发中,为了应付多变的需求,设计出易扩展的程序是非常有必要的。一味的按照需求实现硬编码,后面业务变更,只有加班的份,然后就天天改,自己技术也局限了。整天被产品拖着鼻子走。天天加班,时间都花在重复的工作上了,对自己的成长没有啥好处。

看完上述内容,你们掌握如何将动态代理对象交由Spring容器管理的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!

推荐阅读:
  1. 3 容器管理
  2. 如何将容器应用于Spring Boot项目

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring

上一篇:Spring Boot 默认指标从哪来

下一篇:Tomcat中的连接器是如何设计的

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》