• Spring中老生常谈的FactoryBean


    本文完整代码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/factorybean

    FactoryBeanBeanFactory由于在命名上极其相似,一直以来困扰了不少的开发者。

    BeanFactory,耳熟能详的Spring核心接口,提供IoC容器的最基本功能。但要解释FactoryBean一句话可能就说不清楚了。我们将从下面的例子逐步说明,FactoryBean是什么,它提供了什么样的能力。

    /**
     * 布料
     * 包含颜色属性
     * Created by OKevin On 2019/9/3
     **/
    public class Cloth {
        private Red red;
    	//省略setter/getter方法
    }
    

    当初始化一个Cloth对象时,我希望Red对象也被赋值,此时我将在Cloth的构造方法中new一个Red对象。

    public Cloth() {
        red = new Red();
    }
    

    但是随着业务的发展,我希望Cloth的颜色属性将是Blue蓝色,这时我将修改代码将Red和Blue类抽象出一个Color接口,Cloth代码将重构:

    /**
     * 布料
     * 包含颜色属性
     * Created by OKevin On 2019/9/3
     **/
    public class Cloth {
        private Color color;
        public Cloth() {
            color = new Blue();
        }
    	//省略setter/getter方法
    }
    

    业务又进一步发展,Cloth类中的颜色属性将会根据一定的条件赋值为Red红色,此时我们将代码继续重构:

    /**
     * 布料
     * 包含颜色属性
     * Created by OKevin On 2019/9/3
     **/
    public class Cloth {
        private Color color;
        public Cloth() {
            if (condition()) {
            	color = new Blue();    
            } else {
                color = new Red();
            }
        }
    	//省略setter/getter方法
    }
    

    这样的代码的确能运行,但如果有新的条件继续加入到业务中,此时我们又将改动Cloth类的构造方法,而我们认为Cloth方法是一个比较核心的业务对象,不应该经常对它进行修改,并且在构造方法中对于Color对象创建过于冗余,不符合单一职责的原则,所以我们将Color对象的创建过程通过工厂方法模式来完成。

    静态工厂方法

    我们再次将Cloth类进行如下重构(为了使示例代码更加简洁,下面的示例将只创建Red对象):

    /**
     * 布料
     * 包含颜色属性
     * Created by OKevin On 2019/9/3
     **/
    public class Cloth {
        private Color color;
        public Cloth() {
            color = StaticColorFactory.newInstance();
        }
    	//省略setter/getter方法
    }
    
    /**
     * 静态工厂方法
     * Created by OKevin On 2019/9/3
     **/
    public class StaticColorFactory {
        public static Color getInstance() {
            return new Red();
        }
    }
    

    如果我们在Spring容器中要通过静态工厂方法,创建具体的对象实例应该怎么做呢?

    众所周知,要将一个对象实例交由Spring容器管理,我们通常是通过以下XML配置:

    <bean id="cloth" class="com.coderbuff.bean.Cloth">
    	<property name="color" ref="red"/>
    </bean>
    <bean id="red" class="com.coderbuff.bean.Red" />
    

    但此时,Red对象实例并不是由Spring容器管理,而是由静态工厂方法创建的,此时我们应该讲XML配置修改为以下方式:

    <bean id="cloth" class="com.coderbuff.bean.Cloth">
    	<property name="color" ref="red"/>
    </bean>
    <bean id="red" class="com.coderbuff.factory.StaticColorFactory" factory-method="getInstance" />
    

    这是Spring支持静态工厂方法创建对象实例的特定方式。这样我们就能在Spring中通过静态工厂方法创建对象实例。

    实例工厂方法

    有静态工厂方法,就有非静态工厂方法,区别就是方法不是静态的。

    /**
     * 实例工厂方法
     * Created by OKevin On 2019/9/3
     **/
    public class ColorFactory {
        public Color getInstance() {
            return new Red();
        }
    }
    

    实例工厂方法在Spring中XML配置略有不同:

    <bean id="cloth" class="com.coderbuff.bean.Cloth">
    	<property name="color" ref="red"/>
    </bean>
    <bean id="colorFactory" class="com.coderbuff.factory.ColorFactory"/>
    <bean id="red" factory-bean="colorFactory" factory-method="getInstance"/>
    

    通过配置可以看到,我们需要首先在Spring中实例化工厂,再通过工厂对象实例化Red对象。

    在有了对工厂方法在Spring中创建对象实例的认识后,FactoryBean实际上就是为我们简化这个操作。下面我们将通过FactoryBean来创建Red对象。

    FactoryBean

    /**
     * Created by OKevin On 2019/9/3
     **/
    public class ColorFactoryBean implements FactoryBean<Color> {
        public Color getObject() throws Exception {
            return new Red();
        }
        public Class<?> getObjectType() {
            return Red.class;
        }
        public boolean isSingleton() {
            return false;
        }
    }
    

    通过实现FactoryBean的方式,XML配置如下:

    <bean id="cloth" class="com.coderbuff.bean.Cloth">
    	<property name="color" ref="red"/>
    </bean>
    <bean id="red" class="com.coderbuff.factory.ColorFactoryBean"/>
    

    这样就不用像工厂方法那样配置相应的属性,直接按照普通的Bean注入即可,由于Spring内部做了特殊处理,此时名称为“red”的Bean并不是ColorFactoryBean,而是它方法中getObject中返回的对象。如果实在想要获取ColorFactoryBean的对象实例,则在Bean的名称前加入“&”即可(“&red”)。

    看到这里,是否对FactoryBean有了一点认识呢?FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象。

    我们知道AOP实际上是Spring在运行时创建了一个代理对象,也就是说这个对象,是我们在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP代理对象通过Java的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在Spring中就是——ProxyFactoryBean。

    ProxyFactoryBean

    我们将通过比较“古老”的方式创建一个Red对象的切面,在它的print方法执行前和执行后分别执行一条语句。之所以古老是因为我们往往通过注解的方式,而不会这么折磨自己去写一个切面对象。

    /**
     * 环绕通知
     * Created by OKevin On 2019/9/4
     **/
    public class LogAround implements MethodInterceptor {
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("调用目标方法【前】打印日志");
            Object result = invocation.proceed();
            System.out.println("调用目标方法【后】打印日志");
            return result;
        }
    }
    

    此时我们需要ProxyFactoryBean的介入为我们创建一个代理对象并由Spring容器管理,根据上面ColorFactoryBean的经验,ProxyFacoryBean也应该如下配置:

    <bean id="xxx" class="org.springframework.aop.framework.ProxyFactoryBean" />
    

    答案是肯定的,只是ProxyFactoryBean多了几个参数,既然是生成代理对象,那么目标对象、目标方法就必不可少,实际的XLM配置如下:

    <bean id="logAround" class="com.coderbuff.aop.LogAround"/>
    <bean id="red" class="com.coderbuff.bean.Red"/>
    <bean id="proxyRed" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<property name="proxyInterfaces" value="com.coderbuff.bean.Color"/>
    	<property name="interceptorNames" value="logAround"/>
    	<property name="target" ref="red"/>
    	<property name="proxyTargetClass" value="true"/>
    </bean>
    

    通过测试程序,ProxyFactoryBean的确生成了一个代理对象。

    public class ProxyFactoryBeanTest {
        private ClassPathXmlApplicationContext ctx;
        @Before
        public void init() {
            ctx = new ClassPathXmlApplicationContext("spring-proxyfactorybean.xml");
        }
        @Test
        public void testProxyFactory() {
            Red proxyRed = (Red) ctx.getBean("proxyRed");
            proxyRed.print();
        }
    }
    

    所以,FactoryBean为我们实例化Bean提供了一个更为灵活的方式,我们可以通过FactoryBean创建出更为复杂的Bean实例。

    本文完整代码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/factorybean

    这是一个能给程序员加buff的公众号 (CoderBuff)
  • 相关阅读:
    SharePoint 2013 安装.NET Framework 3.5 报错
    SharePoint 2016 配置工作流环境
    SharePoint 2016 站点注册工作流服务报错
    Work Management Service application in SharePoint 2016
    SharePoint 2016 安装 Cumulative Update for Service Bus 1.0 (KB2799752)报错
    SharePoint 2016 工作流报错“没有适用于此应用程序的地址”
    SharePoint 2016 工作流报错“未安装应用程序管理共享服务代理”
    SharePoint JavaScript API in application pages
    SharePoint 2016 每天预热脚本介绍
    SharePoint 无法删除搜索服务应用程序
  • 原文地址:https://www.cnblogs.com/yulinfeng/p/11456587.html
Copyright © 2020-2023  润新知