“Lookup方法”可以使Spring替换一个bean原有的,获取其它对象具体的方法,并自动返回在容器中的查找结果。
我们来看这个例子:
UserDao.java
在UserDao的构造函数中接受一个name参数,创建UserDao的对象会把自己的名字传递给userDao,这样userDao的create方法中就会把userDao的创建者打印出来。
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的关系仍然非常紧密。
public class UserManager ... {
public UserDao getUserDao() ...{
return new UserDao("UserManager.getUserDao()");
}
public void createUser() ...{
UserDao dao = getUserDao(); //通过getUserDao获得userDao
dao.create();
}
}
通过BeanFactory获得UserManager,并调用createUser方法。
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:
"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 method </ value >
</ 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。
Lookup方法的工作机制
通过这种机制我们可以在不修改原系统代码的情况下,可以轻易的把UserDao换成别的类型相容的对象而不会影响原系统。Spring是使用CGLIB在字节码级别动态实现出userManager的子类,并重写getUserDao方法的方式来实现这个神奇的功能的。
修改LookupMethodTest.java:
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()打印出了:
research.spring.beanfactory.ch2.UserManager$$EnhancerByCGLIB$$886cdee9@1238bd2
这个是CGLIG动态生成的类,而不是原来的UserManager的实例。所以请记住在任何时候只要定义了一个Bean的Lookup方法,那么这个Bean的实例将是一个CGLIB动态生成的实例而不是原来类的实例。
Spring允许在一个Bean中定义多个Lookup方法。
< 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:
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对Lookup方法也存在一些限制:
方法不能是private的,但可以是protected的。
方法不能是静态的。
在抽象类和接口上应用Lookup方法有一个比较有趣的用法,就是在抽象类上定义Lookup方法。你一定记得经典的工厂模式吧。定义一个抽象工厂,然后为每一类具体产品实现一个具体产品的工厂。
一个抽象工厂:
public abstract class Factory ... {
public abstract UserDao getProduct();
}
public class UserDaoFactory extends Factory ... {
public UserDao getProduct()...{
return new UserDao("UserDaoFactory");
}
}
用户可以通过:
new UserDaoFactory().getProduce();
来获取具体的UserDao产品。
但是如果有很多产品就需要做出实现出很多工厂如,DocumentDaoFactory、GroupDaoFactory等等,这样系统中会出现大量的工厂。工厂的泛滥并不能说明系统的设计是合理的。
既然Spring可以在抽象类上使用Lookup方法,那么我们就可以不同实现真的去实现那么多的子类了。我们可以在抽象类上直接定义Lookup方法和目标对象。用户直接通过抽象类来获得需要的产品对象。看下面这个例子:
Factory.java
public abstract class Factory ... {
public abstract Object getProduct();
}
如果指定userDaoFactory的类为一个抽象类,并且再这个bean里定义了Lookup方法,那么Spring会自动生成这个抽象类的子类实现。
"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 method </ value >
</ constructor-arg >
</ bean >
< bean name ="userDaoFactory" class ="research.spring.beanfactory.ch2.Factory" singleton ="false" >
< lookup-method name ="getProduct" bean ="userDao" />
</ bean >
</ beans >
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,在用户使用时使用不同抽象工厂的实例就可以了。
< 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 >
既然Sping可以动态实现抽象类的子类那么,它能不能动态创建出实现一个接口的类呢。答案时肯定的。上面的例子可以直接把Factory变成一个接口,仍然可以正常工作。
这里需要注意的是,只要在一个Bean上明确的定义了Lookup方法,Spring才会使用CGLIB来做原对象的字节码代理。如果一个没有定义Lookup方法的抽象类或接口是不能直接被Spring实例的。
本文介绍了Lookup方法的使用和工作原理,希望读者能够对Lookup方法有了比较深入的了解。虽然我的例子可以简化工厂模式,但是我并不鼓励大家在实际系统中这样做。因为我始终认为“工厂模式”只要在遗留系统中才会碰到。使用IoC模式基本上可以替代所有的对象创建模式。本章的例子只是为了说明Lookup方法如何使用,和Lookup方法的一些特殊情况。Lookup方法一般只在处理遗留代码时使用。