“Lookup方法”可以使Spring替换一个bean原有的,获取其它对象具体的方法,并自动返回在容器中的查找结果。
我们来看这个例子:
UserDao.java
在UserDao的构造函数中接受一个name参数,创建UserDao的对象会把自己的名字传递给userDao,这样userDao的create方法中就会把userDao的创建者打印出来。
package research.spring.beanfactory.ch2;
public class UserDao {
private String name="";
public UserDao(String name){
this.name=name;
}
public void create(){
System.out.println("create user from - "+name);
}
}
UserManager.java
在这段代码中UserManager依靠getUserDao方法来获取UserDao对象。由于在getUserDao方法里显示的声明了如何去实例一个UserDao,所以上面的代码不符合IoC模式的风格。虽然使用GetUserDao封装了UserDao的创建过程,但是UserManager和UserDao的关系仍然非常紧密。
package research.spring.beanfactory.ch2;
public class UserManager {
public UserDao getUserDao() {
return new UserDao("UserManager.getUserDao()");
}
public void createUser() {
UserDao dao = getUserDao(); //通过getUserDao获得userDao
dao.create();
}
}
LookupMethodTest.java
通过BeanFactory获得UserManager,并调用createUser方法。
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class LookupMethodTest {
public static void main(String[] args) {
XmlBeanFactory factory=new
XmlBeanFactory(new ClassPathResource("research/spring/beanfactory/ch2/context.xml"));
UserManager manager=(UserManager) factory.getBean("userManager");
manager.createUser(); //create a User
}
}
context.xml
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
bean>
<bean name="userDao class="research.spring.beanfactory.ch2.UserDao" >
bean>
beans>
运行LookupMethodTest你会看到屏幕输入” create user from - UserManager.getUserDao()”。
由于是遗留系统,所以我们不能修改UserManager。现在我希望让这个UserManager依赖的Dao对象由spring管理,而不修改原有的代码。
在这个场景中我们就可以利用Spring提供的“Lookup方法”来替换原有的getUserDao方法,实现自动获取userDao的功能。修改context.xml:
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
bean>
<bean name="userDao" class="research.spring.beanfactory.ch2.UserDao" >
<constructor-arg>
<value>lookup methodvalue>
constructor-arg>
bean>
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.UserDaoFactory">
bean>
beans>
再次运行LookupMethodTest你会看到不同的输出结果“create user from - lookup method”。字符串“lookup method”是通过构造函数注入给userDao的。原来的userManager.java并没有作任何修改,仍然是通过UserDao dao = getUserDao();来获得userDao的。这说明Spring已经替换了原有的getUserDao方法的实现,当执行getUserDao时Spring会在容器中寻找指定的Bean,并返回这个Bean。
通过这种机制我们可以在不修改原系统代码的情况下,可以轻易的把UserDao换成别的类型相容的对象而不会影响原系统。Spring是使用CGLIB在字节码级别动态实现出userManager的子类,并重写getUserDao方法的方式来实现这个神奇的功能的。
修改LookupMethodTest.java:
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class LookupMethodTest {
public static void main(String[] args) {
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml"));
UserManager manager=(UserManager) factory.getBean("userManager");
System.out.println(manager.toString()); //打印userManager的信息
manager.createUser(); //create a User
}
}
我们在获取UserManager的实例后打印出这个实例的信息,再次运行LookupMethodTest你会看到:
注意manager.toString()打印出了:
这个是CGLIG动态生成的类,而不是原来的UserManager的实例。所以请记住在任何时候只要定义了一个Bean的Lookup方法,那么这个Bean的实例将是一个CGLIB动态生成的实例而不是原来类的实例。
Spring允许在一个Bean中定义多个Lookup方法。
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
<lookup-method name="getOtherDao" bean="otherDao" />
bean>
上面的做法是合法的,并且可以正常工作。
虽然Spring会用CGLIB动态生成一个带有Lookup方法bean的子类,但是这并不影响Spring完成其它的功能。Sping还是允许Lookup方法和setXXX、构造函数注入以及后面我们将介绍的自动依赖检查和自动装配的功能同时使用。
Spring还允许Lookup方法中定义的方法带有参数,但是Sping不会处理这些参数。
修改UserManager:
package research.spring.beanfactory.ch2;
public class UserManager {
private UserDao dao;
public void setDao(UserDao dao) {
this.dao = dao;
}
public UserDao getUserDao(String daoName) {
return new UserDao("UserManager.getUserDao()");
}
public void createUser() {
UserDao dao = getUserDao(“userDao”); //通过getUserDao获得userDao
dao.create();
}
}
虽然方法上由参数,但是上面的代码可以正常工作。Spring不会处理这些参数。
Spring对Lookup方法也存在一些限制:
- 方法不能是private的,但可以是protected的。
- 方法不能是静态的。
有一个比较有趣的用法,就是在抽象类上定义Lookup方法。你一定记得经典的工厂模式吧。定义一个抽象工厂,然后为每一类具体产品实现一个具体产品的工厂。
一个抽象工厂:
package research.spring.beanfactory.ch2;
public abstract class Factory {
public abstract UserDao getProduct();
}
具体一类产品的工厂:
package research.spring.beanfactory.ch2;
public class UserDaoFactory extends Factory{
public UserDao getProduct(){
return new UserDao("UserDaoFactory");
}
}
用户可以通过:
new UserDaoFactory().getProduce();
来获取具体的UserDao产品。
但是如果有很多产品就需要做出实现出很多工厂如,DocumentDaoFactory、GroupDaoFactory等等,这样系统中会出现大量的工厂。工厂的泛滥并不能说明系统的设计是合理的。
既然Spring可以在抽象类上使用Lookup方法,那么我们就可以不同实现真的去实现那么多的子类了。我们可以在抽象类上直接定义Lookup方法和目标对象。用户直接通过抽象类来获得需要的产品对象。看下面这个例子:
Factory.ava
package research.spring.beanfactory.ch2;
public abstract class Factory {
public abstract Object getProduct();
}
context.xml
如果指定userDaoFactory的类为一个抽象类,并且再这个bean里定义了Lookup方法,那么Spring会自动生成这个抽象类的子类实现。
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
bean>
<bean name="userDao" class="research.spring.beanfactory.ch2.UserDao" >
<constructor-arg>
<value>lookup methodvalue>
constructor-arg>
bean>
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false">
<lookup-method name="getProduct" bean="userDao" />
bean>
beans>
Test.java
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class LookupMethodTest {
public static void main(String[] args) {
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml"));
//获得抽象工厂
Factory abstractFactory=(Factory) factory.getBean("userDaoFactory");
UserDao userDao=(UserDao) abstractFactory.getProduct();
System.out.println(userDao.toString());
userDao.create();
}
}
运行Test你会看到:
research.spring.beanfactory.ch2.UserManager$$EnhancerByCGLIB$$cc2f8f1c@12c7568
create user from - lookup method
对,这个结果和上面的例子是完全一样的。UserDao并没有改变,我们通过抽象的Factory获得了具体的UserDao的实例。这样即使系统中很多的具体产品我们也不需要实现每类产品的工厂类了。只需要在系统中配置多个抽象工厂,并且配置每个工厂的singlton为false,在用户使用时使用不同抽象工厂的实例就可以了。
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false">
<lookup-method name="getProduct" bean="userDao" />
bean>
<bean name="documentDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false">
<lookup-method name="getProduct" bean="documentDao" />
bean>
Spring不关心抽象类中的定义的lookup方法是否时抽象的,Spring都会重写这个方法。
既然Sping可以动态实现抽象类的子类那么,它能不能动态创建出实现一个接口的类呢。答案时肯定的。上面的例子可以直接把Factory变成一个接口,仍然可以正常工作。
这里需要注意的是,只要在一个Bean上明确的定义了Lookup方法,Spring才会使用CGLIB来做原对象的字节码代理。如果一个没有定义Lookup方法的抽象类或接口是不能直接被Spring实例的。
本文介绍了Lookup方法的使用和工作原理,希望读者能够对Lookup方法有了比较深入的了解。虽然我的例子可以简化工厂模式,但是我并不鼓励大家在实际系统中这样做。因为我始终认为“工厂模式”只要在遗留系统中才会碰到。使用IoC模式基本上可以替代所有的对象创建模式。本章的例子只是为了说明Lookup方法如何使用,和Lookup方法的一些特殊情况。Lookup方法一般只在处理遗留代码时使用。