通常情况下,我们使用的bean都是单例的,如果一个bean需要依赖于另一个bean的时候,可以在当前bean中声明另外一个bean引用,然后注入依赖的bean,此时被依赖的bean在当前bean中自始至终都是同一个实例。
1,lookup-method (方法查找)
直接看一个案例
public class Service1{
}
public class Service2{
private Service1 service1;
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
this.service1 = service1;
}
}
上面2个类,Service1和Service2,而Service2中需要用到Service1 ,可以通过setService1将Service1注入到Service2中。
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="service1" class="com.chen.chat.chen.Service1" scope="prototype"/>
<bean id="service2" class="com.chen.chat.chen.Service2">
<property name="service1" ref="service1"/>
</bean>
</beans>
上面service1的scope是prototype,表示service1是多例的,每次从容器中获取serviceA都会返回一个新的对象。
而service2的scope没有配置,默认是单例的,通过property元素将serviceA注入。
测试输出
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml");
//从容器中按照类型查找Service1对应的bean。
System.out.println(context.getBean(Service1.class));//1
System.out.println(context.getBean(Service1.class));//2
System.out.println("service2中的service1");
Service2 service2 = context.getBean(Service2.class);
//获取service2中的service1对象
System.out.println(service2.getService1());//3
System.out.println(service2.getService1());//4
}
}
com.chen.chat.chen.Service1@6ddf90b0
com.chen.chat.chen.Service1@57536d79
service2中的service1
com.chen.chat.chen.Service1@3b0143d3
com.chen.chat.chen.Service1@3b0143d3
可以看出,1和2输出了不同的Service1,而3和4输出的是同一个serviceA,这是因为service2是单例的,service2中的service1会在容器创建service2的时候,从容器中获取一个service1将其注入到service2中,所以自始至终service2中的service1都是同一个对象。
如果我们希望service2中每次使用Service1的时候,Service1都是一个新的实例,我们怎么实现呢?
lookup-method可以实现这个
我们修改一下上面的案例
public class Service1{
}
public class Service2{
private Service1 service1;
public void printService1(){
this.service1 = this.getService1();
System.out.println("this:" + this + ",Service1:" + this.service1);
}
public Service1 getService1() {
return null;
}
}
注意上面的getService1(),这个方法中返回了一个null对象,我们通过spring来创建上面2个bean对象,然后让spring对上面的getService1方法进行拦截,返回指定的bean。
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="service1" class="com.chen.chat.chen.Service1" scope="prototype"/>
<bean id="service2" class="com.chen.chat.chen.Service2">
<lookup-method name="getService1" bean="service1"/>
</bean>
</beans>
<lookup-method name="getService1" bean="service1"/>
当我们调用service2中的getService1方法的时候,这个方法会拦截,然后会按照lookup-method元素中bean属性的值作为bean的名称去容器中查找对应bean,然后作为getService1的返回值返回,即调用getServiceA方法的时候,会从spring容器中查找id为service1的bean然后返回。
测试输出
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml");
//从容器中按照类型查找Service1对应的bean。
System.out.println(context.getBean(Service1.class));
System.out.println(context.getBean(Service1.class));
System.out.println("service2中的service1");
Service2 service2 = context.getBean(Service2.class);
//获取service2中的service1对象
service2.printService1();
service2.printService1();
}
}
com.chen.chat.chen.Service1@66d1af89
com.chen.chat.chen.Service1@8646db9
service2中的service1
this:com.chen.chat.chen.Service2$$EnhancerBySpringCGLIB$$4271b21e@37374a5e,Service1:com.chen.chat.chen.Service1@4671e53b
this:com.chen.chat.chen.Service2$$EnhancerBySpringCGLIB$$4271b21e@37374a5e,Service1:com.chen.chat.chen.Service1@2db7a79b
可以看到,service1是调用this.getService1()方法获取,源码中这个方法返回的是null,但是spring内部对这个方法进行了拦截,每次调用这个方法的时候,都会去容器中查找service1,然后返回,所以上面最后2行的输出中service1是有值的,并且是不同的service1实例。
小结:
lookup-method:所以我们就知道lookup-method的作用了:方法查找,调用name属性指定的方法的时候,spring会对这个方法进行拦截,然后去容器中查找lookup-method元素中bean属性的值作为名称去查找指定的bean,然后将找到的bean作为方法的返回值返回。
2,replaced-method (方法替换)
比如我们要调用service2中的getService1的时候,我们可以对service2这个bean中的getService1方法进行拦截,把这个调用请求转发到一个替换者处理。这就是replaced-method可以实现的功能,比lookup-method更强大更灵活。
我们来实现一下:
1,定义替换者,需要实现spring中的MethodReplacer接口
MethodReplacer接口
public interface MethodReplacer {
/**
*当调用目标对象需要被替换的方法的时候,这个调用请求会被转发到这里的替换者的reimplement方法进行处理。
* @param obj 被替换方法的目标对象
* @param method 目标对象的方法
* @param args 方法的参数
* @return return value for the method
*/
Object reimplement(Object var1, Method var2, Object[] var3) throws Throwable;
}
servie2的方法替换者
public class Service2MethodReplacer implements MethodReplacer, ApplicationContextAware {
private ApplicationContext context;
@Override
public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("我是Service2MethodReplacer");
return this.context.getBean(Service1.class);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
ApplicationContextAware
这个接口有一个方法setApplicationContext,这个接口给了自定义的bean中获取applicationContext的能力,当我们的类实现这个接口之后,spring容器创建bean对象的时候,如果bean实现了这个接口,那么容器会自动调用setApplicationContext方法,将容器对象applicationContext传入,此时在我们的bean对象中就可以使用容器的任何方法了。
2,定义替换者bean
<bean id="service2MethodReplacer" class="com.chen.chat.chen.Service2MethodReplacer"/>
3,通过replaced-method元素配置目标bean需要被替换的方法
<bean id="service2" class="com.chen.chat.chen.Service2">
<replaced-method name="getService1" replacer="service2MethodReplacer"/>
</bean>
总的bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="service2" class="com.chen.chat.chen.Service2">
<replaced-method name="getService1" replacer="service2MethodReplacer"/>
</bean>
<bean id="service2MethodReplacer" class="com.chen.chat.chen.Service2MethodReplacer"/>
</beans>
注意上面的replaced-method元素的2个属性:
name:用于指定当前bean需要被替换的方法
replacer:替换者,即实现了MethodReplacer接口的类对应的bean
上面配置中当调用service2的getService1的时候,会自动调用service2MethodReplacer这个bean中的reimplement方法进行处理
测试输出
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml");
//从容器中按照类型查找Service1对应的bean。
System.out.println(context.getBean(Service1.class));
System.out.println(context.getBean(Service1.class));
System.out.println("service2中的service1");
Service2 service2 = context.getBean(Service2.class);
//获取service2中的service1对象
service2.printService1();
service2.printService1();
}
}
com.chen.chat.chen.Service1@4671e53b
com.chen.chat.chen.Service1@2db7a79b
service2中的service1
我是Service2MethodReplacer
this:com.chen.chat.chen.Service2$$EnhancerBySpringCGLIB$$4271b21e@6950e31,Service1:com.chen.chat.chen.Service1@b7dd107
我是Service2MethodReplacer
this:com.chen.chat.chen.Service2$$EnhancerBySpringCGLIB$$4271b21e@6950e31,Service1:com.chen.chat.chen.Service1@42eca56e
从输出中可以看出结果和lookup-method案例效果差不多,实现了单例bean中使用多例bean的案例。
输出中都有CGLIB这样的字样,说明是通过cglib实现的。
总结
- lookup-method:方法查找,可以对指定的bean的方法进行拦截,然后从容器中查找指定的bean作为被拦截方法的返回值
- replaced-method:方法替换,可以实现bean方法替换的效果,整体来说比lookup-method更灵活一些