3.3.1 延迟初始化Bean
延迟初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用时才创建及初始化Bean。
配置方式很简单只需在<bean>标签上指定 “lazy-init” 属性值为“true”即可延迟初始化Bean。
Spring容器会在创建容器时提前初始化“singleton”作用域的Bean,“singleton”就是单例的意思即整个容器每个Bean只有一个实例,后边会详细介绍。Spring容器预先初始化Bean通常能帮助我们提前发现配置错误,所以如果没有什么情况建议开启,除非有某个Bean可能需要加载很大资源,而且很可能在整个应用程序生命周期中很可能使用不到,可以设置为延迟初始化。
延迟初始化的Bean通常会在第一次使用时被初始化;或者在被非延迟初始化Bean作为依赖对象注入时在会随着初始化该Bean时被初始化,因为在这时使用了延迟初始化Bean。
容器管理初始化Bean消除了编程实现延迟初始化,完全由容器控制,只需在需要延迟初始化的Bean定义上配置即可,比编程方式更简单,而且是无侵入代码的。
具体配置如下:
- <bean id="helloApi"
- class="cn.javass.spring.chapter2.helloworld.HelloImpl"
- lazy-init="true"/>
3.3.2 使用depends-on
depends-on是指指定Bean初始化及销毁时的顺序,使用depends-on属性指定的Bean要先初始化完毕后才初始化当前Bean,由于只有“singleton”Bean能被Spring管理销毁,所以当指定的Bean都是“singleton”时,使用depends-on属性指定的Bean要在指定的Bean之后销毁。
配置方式如下:
- <bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <bean id="decorator"
- class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- depends-on="helloApi">
- <property name="helloApi"><ref bean="helloApi"/></property>
- </bean>
“decorator”指定了“depends-on”属性为“helloApi”,所以在“decorator”Bean初始化之前要先初始化“helloApi”,而在销毁“helloApi”之前先要销毁“decorator”,大家注意一下销毁顺序,与文档上的不符。
“depends-on”属性可以指定多个Bean,若指定多个Bean可以用“;”、“,”、空格分割。
那“depends-on”有什么好处呢?主要是给出明确的初始化及销毁顺序,比如要初始化“decorator”时要确保“helloApi”Bean的资源准备好了,否则使用“decorator”时会看不到准备的资源;而在销毁时要先在“decorator”Bean的把对“helloApi”资源的引用释放掉才能销毁“helloApi”,否则可能销毁 “helloApi”时而“decorator”还保持着资源访问,造成资源不能释放或释放错误。
让我们看个例子吧,在平常开发中我们可能需要访问文件系统,而文件打开、关闭是必须配对的,不能打开后不关闭,从而造成其他程序不能访问该文件。让我们来看具体配置吧:
1)准备测试类:
ResourceBean从配置文件中配置文件位置,然后定义初始化方法init中打开指定的文件,然后获取文件流;最后定义销毁方法destroy用于在应用程序关闭时调用该方法关闭掉文件流。
DependentBean中会注入ResourceBean,并从ResourceBean中获取文件流写入内容;定义初始化方法init用来定义一些初始化操作并向文件中输出文件头信息;最后定义销毁方法用于在关闭应用程序时想文件中输出文件尾信息。
具体代码如下:
- package cn.javass.spring.chapter3.bean;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- public class ResourceBean {
- private FileOutputStream fos;
- private File file;
- //初始化方法
- public void init() {
- System.out.println("ResourceBean:========初始化");
- //加载资源,在此只是演示
- System.out.println("ResourceBean:========加载资源,执行一些预操作");
- try {
- this.fos = new FileOutputStream(file);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- }
- }
- //销毁资源方法
- public void destroy() {
- System.out.println("ResourceBean:========销毁");
- //释放资源
- System.out.println("ResourceBean:========释放资源,执行一些清理操作");
- try {
- fos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public FileOutputStream getFos() {
- return fos;
- }
- public void setFile(File file) {
- this.file = file;
- }
- }
- package cn.javass.spring.chapter3.bean;
- import java.io.IOException;
- public class DependentBean {
- ResourceBean resourceBean;
- public void write(String ss) throws IOException {
- System.out.println("DependentBean:=======写资源");
- resourceBean.getFos().write(ss.getBytes());
- }
- //初始化方法
- public void init() throws IOException {
- System.out.println("DependentBean:=======初始化");
- resourceBean.getFos().write("DependentBean:=======初始化=====".getBytes());
- }
- //销毁方法
- public void destroy() throws IOException {
- System.out.println("DependentBean:=======销毁");
- //在销毁之前需要往文件中写销毁内容
- resourceBean.getFos().write("DependentBean:=======销毁=====".getBytes());
- }
- public void setResourceBean(ResourceBean resourceBean) {
- this.resourceBean = resourceBean;
- }
- }
2)类定义好了,让我们来进行Bean定义吧,具体配置文件如下:
- <bean id="resourceBean"
- class="cn.javass.spring.chapter3.bean.ResourceBean"
- init-method="init" destroy-method="destroy">
- <property name="file" value="D:/test.txt"/>
- </bean>
- <bean id="dependentBean"
- class="cn.javass.spring.chapter3.bean.DependentBean"
- init-method="init" destroy-method="destroy" depends-on="resourceBean">
- <property name="resourceBean" ref="resourceBean"/>
- </bean>
<property name="file" value="D:/test.txt"/>配置:Spring容器能自动把字符串转换为java.io.File。
init-method="init" :指定初始化方法,在构造器注入和setter注入完毕后执行。
destroy-method="destroy":指定销毁方法,只有“singleton”作用域能销毁,“prototype”作用域的一定不能,其他作用域不一定能;后边再介绍。
在此配置中,resourceBean初始化在dependentBean之前被初始化,resourceBean销毁会在dependentBean销毁之后执行。
3)配置完毕,测试一下吧:
- package cn.javass.spring.chapter3;
- import java.io.IOException;
- import org.junit.Test;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import cn.javass.spring.chapter3.bean.DependentBean;
- public class MoreDependencyInjectTest {
- @Test
- public void testDependOn() throws IOException {
- ClassPathXmlApplicationContext context =
- new ClassPathXmlApplicationContext("chapter3/depends-on.xml");
- //一点要注册销毁回调,否则我们定义的销毁方法不执行
- context.registerShutdownHook();
- DependentBean dependentBean =
- context.getBean("dependentBean", DependentBean.class);
- dependentBean.write("aaa");
- }
- }
测试跟其他测试完全一样,只是在此我们一定要注册销毁方法回调,否则销毁方法不会执行。
如果配置没问题会有如下输出:
- ResourceBean:========初始化
- ResourceBean:========加载资源,执行一些预操作
- DependentBean:=========初始化
- DependentBean:=========写资源
- DependentBean:=========销毁
- ResourceBean:========销毁
- ResourceBean:========释放资源,执行一些清理操作
3.3.3 自动装配
自动装配就是指由Spring来自动地注入依赖对象,无需人工参与。
目前Spring3.0支持“no”、“byName ”、“byType”、“constructor”四种自动装配,默认是“no”指不支持自动装配的,其中Spring3.0已不推荐使用之前版本的“autodetect”自动装配,推荐使用Java 5+支持的(@Autowired)注解方式代替;如果想支持“autodetect”自动装配,请将schema改为“spring-beans-2.5.xsd”或去掉。
自动装配的好处是减少构造器注入和setter注入配置,减少配置文件的长度。自动装配通过配置<bean>标签的“autowire”属性来改变自动装配方式。接下来让我们挨着看下配置的含义。
一、default:表示使用默认的自动装配,默认的自动装配需要在<beans>标签中使用default-autowire属性指定,其支持“no”、“byName ”、“byType”、“constructor”四种自动装配,如果需要覆盖默认自动装配,请继续往下看;
二、no:意思是不支持自动装配,必须明确指定依赖。
三、byName:通过设置Bean定义属性autowire="byName",意思是根据名字进行自动装配,只能用于setter注入。比如我们有方法“setHelloApi”,则“byName”方式Spring容器将查找名字为helloApi的Bean并注入,如果找不到指定的Bean,将什么也不注入。
例如如下Bean定义配置:
- <bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- autowire="byName"/>
测试代码如下:
- package cn.javass.spring.chapter3;
- import java.io.IOException;
- import org.junit.Test;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import cn.javass.spring.chapter2.helloworld.HelloApi;
- public class AutowireBeanTest {
- @Test
- public void testAutowireByName() throws IOException {
- ClassPathXmlApplicationContext context =
- new ClassPathXmlApplicationContext("chapter3/autowire-byName.xml");
- HelloApi helloApi = context.getBean("bean", HelloApi.class);
- helloApi.sayHello();
- }
- }
是不是不要配置<property>了,如果一个bean有很多setter注入,通过“byName”方式是不是能减少很多<property>配置。此处注意了,在根据名字注入时,将把当前Bean自己排除在外:比如“hello”Bean类定义了“setHello”方法,则hello是不能注入到“setHello”的。
四、“byType”:通过设置Bean定义属性autowire="byType",意思是指根据类型注入,用于setter注入,比如如果指定自动装配方式为“byType”,而“setHelloApi”方法需要注入HelloApi类型数据,则Spring容器将查找HelloApi类型数据,如果找到一个则注入该Bean,如果找不到将什么也不注入,如果找到多个Bean将优先注入<bean>标签“primary”属性为true的Bean,否则抛出异常来表明有个多个Bean发现但不知道使用哪个。让我们用例子来讲解一下这几种情况吧。
1)根据类型只找到一个Bean,此处注意了,在根据类型注入时,将把当前Bean自己排除在外,即如下配置中helloApi和bean都是HelloApi接口的实现,而“bean”通过类型进行注入“HelloApi”类型数据时自己是排除在外的,配置如下(具体测试请参考AutowireBeanTest.testAutowireByType1方法):
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- autowire="byType"/>
2)根据类型找到多个Bean时,对于集合类型(如List、Set)将注入所有匹配的候选者,而对于其他类型遇到这种情况可能需要使用“autowire-candidate”属性为false来让指定的Bean放弃作为自动装配的候选者,或使用“primary”属性为true来指定某个Bean为首选Bean:
2.1)通过设置Bean定义的“autowire-candidate”属性为false来把指定Bean后自动装配候选者中移除:
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <!-- 从自动装配候选者中去除 -->
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"
- autowire-candidate="false"/>
- <bean id="bean1" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- autowire="byType"/>
2.2)通过设置Bean定义的“primary”属性为true来把指定自动装配时候选者中首选Bean:
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <!-- 自动装配候选者中的首选Bean-->
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
- <bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- autowire="byType"/>
具体测试请参考AutowireBeanTest类的testAutowireByType***方法。
五、“constructor”:通过设置Bean定义属性autowire="constructor",功能和“byType”功能一样,根据类型注入构造器参数,只是用于构造器注入方式,直接看例子吧:
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <!-- 自动装配候选者中的首选Bean-->
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
- <bean id="bean"
- class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- autowire="constructor"/>
测试代码如下:
- @Test
- public void testAutowireByConstructor() throws IOException {
- ClassPathXmlApplicationContext context =
- new ClassPathXmlApplicationContext("chapter3/autowire-byConstructor.xml");
- HelloApi helloApi = context.getBean("bean", HelloApi.class);
- helloApi.sayHello();
- }
六、autodetect:自动检测是使用“constructor”还是“byType”自动装配方式,已不推荐使用。如果Bean有空构造器那么将采用“byType”自动装配方式,否则使用“constructor”自动装配方式。此处要把3.0的xsd替换为2.5的xsd,否则会报错。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-2.5.xsd">
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <!-- 自动装配候选者中的首选Bean-->
- <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
- <bean id="bean"
- class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- autowire="autodetect"/>
- </beans>
可以采用在“<beans>”标签中通过“default-autowire”属性指定全局的自动装配方式,即如果default-autowire=”byName”,将对所有Bean进行根据名字进行自动装配。
不是所有类型都能自动装配:
- 不能自动装配的数据类型:Object、基本数据类型(Date、CharSequence、Number、URI、URL、Class、int)等;
- 通过“<beans>”标签default-autowire-candidates属性指定的匹配模式,不匹配的将不能作为自动装配的候选者,例如指定“*Service,*Dao”,将只把匹配这些模式的Bean作为候选者,而不匹配的不会作为候选者;
- 通过将“<bean>”标签的autowire-candidate属性可被设为false,从而该Bean将不会作为依赖注入的候选者。
数组、集合、字典类型的根据类型自动装配和普通类型的自动装配是有区别的:
- 数组类型、集合(Set、Collection、List)接口类型:将根据泛型获取匹配的所有候选者并注入到数组或集合中,如“List<HelloApi> list”将选择所有的HelloApi类型Bean并注入到list中,而对于集合的具体类型将只选择一个候选者,“如 ArrayList<HelloApi> list”将选择一个类型为ArrayList的Bean注入,而不是选择所有的HelloApi类型Bean进行注入;
- 字典(Map)接口类型:同样根据泛型信息注入,键必须为String类型的Bean名字,值根据泛型信息获取,如“Map<String, HelloApi> map” 将选择所有的HelloApi类型Bean并注入到map中,而对于具体字典类型如“HashMap<String, HelloApi> map”将只选择类型为HashMap的Bean注入,而不是选择所有的HelloApi类型Bean进行注入。
自动装配我们已经介绍完了,自动装配能带给我们什么好处呢?首先,自动装配确实减少了配置文件的量;其次, “byType”自动装配能在相应的Bean更改了字段类型时自动更新,即修改Bean类不需要修改配置,确实简单了。
自动装配也是有缺点的,最重要的缺点就是没有了配置,在查找注入错误时非常麻烦,还有比如基本类型没法完成自动装配,所以可能经常发生一些莫名其妙的错误,在此我推荐大家不要使用该方式,最好是指定明确的注入方式,或者采用最新的Java5+注解注入方式。所以大家在使用自动装配时应该考虑自己负责项目的复杂度来进行衡量是否选择自动装配方式。
自动装配注入方式能和配置注入方式一同工作吗?当然可以,大家只需记住配置注入的数据会覆盖自动装配注入的数据。
大家是否注意到对于采用自动装配方式时如果没找到合适的的Bean时什么也不做,这样在程序中总会莫名其妙的发生一些空指针异常,而且是在程序运行期间才能发现,有没有办法能在提前发现这些错误呢?接下来就让我来看下依赖检查吧。
3.3.4 依赖检查
上一节介绍的自动装配,很可能发生没有匹配的Bean进行自动装配,如果此种情况发生,只有在程序运行过程中发生了空指针异常才能发现错误,如果能提前发现该多好啊,这就是依赖检查的作用。
依赖检查:用于检查Bean定义的属性都注入数据了,不管是自动装配的还是配置方式注入的都能检查,如果没有注入数据将报错,从而提前发现注入错误,只检查具有setter方法的属性。
Spring3+也不推荐配置方式依赖检查了,建议采用Java5+ @Required注解方式,测试时请将XML schema降低为2.5版本的,和自动装配中“autodetect”配置方式的xsd一样。
- <?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-2.5.xsd
- </beans>
依赖检查有none、simple、object、all四种方式,接下来让我们详细介绍一下:
一、none:默认方式,表示不检查;
二、objects:检查除基本类型外的依赖对象,配置方式为:dependency-check="objects",此处我们为HelloApiDecorator添加一个String类型属性“message”,来测试如果有简单数据类型的属性为null,也不报错;
- <bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <!-- 注意我们没有注入helloApi,所以测试时会报错 -->
- <bean id="bean"
- class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- dependency-check="objects">
- <property name="message" value="Haha"/>
- </bean>
注意由于我们没有注入bean需要的依赖“helloApi”,所以应该抛出异常UnsatisfiedDependencyException,表示没有发现满足的依赖:
- package cn.javass.spring.chapter3;
- import java.io.IOException;
- import org.junit.Test;
- import org.springframework.beans.factory.UnsatisfiedDependencyException;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class DependencyCheckTest {
- @Test(expected = UnsatisfiedDependencyException.class)
- public void testDependencyCheckByObject() throws IOException {
- //将抛出异常
- new ClassPathXmlApplicationContext("chapter3/dependency-check-object.xml");
- }
- }
三、simple:对基本类型进行依赖检查,包括数组类型,其他依赖不报错;配置方式为:dependency-check="simple",以下配置中没有注入message属性,所以会抛出异常:
- <bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <!-- 注意我们没有注入message属性,所以测试时会报错 -->
- <bean id="bean"
- class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- dependency-check="simple">
- <property name="helloApi" ref="helloApi"/>
- </bean>
四、all:对所以类型进行依赖检查,配置方式为:dependency-check="all",如下配置方式中如果两个属性其中一个没配置将报错。
- <bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
- <bean id="bean"
- class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
- dependency-check="all">
- <property name="helloApi" ref="helloApi"/>
- <property name="message" value="Haha"/>
- </bean>
依赖检查也可以通过“<beans>”标签中default-dependency-check属性来指定全局依赖检查配置。
3.3.5 方法注入
所谓方法注入其实就是通过配置方式覆盖或拦截指定的方法,通常通过代理模式实现。Spring提供两种方法注入:查找方法注入和方法替换注入。
因为Spring是通过CGLIB动态代理方式实现方法注入,也就是通过动态修改类的字节码来实现的,本质就是生成需方法注入的类的子类方式实现。
在进行测试之前,我们需要确保将“com.springsource.cn.sf.cglib-2.2.0.jar”放到lib里并添加到“Java Build Path”中的Libararies中。否则报错,异常中包含“nested exception is java.lang.NoClassDefFoundError: cn/sf/cglib/proxy/CallbackFilter”。
传统方式和Spring容器管理方式唯一不同的是不需要我们手动生成子类,而是通过配置方式来实现;其中如果要替换createPrinter()方法的返回值就使用查找方法注入;如果想完全替换sayHello()方法体就使用方法替换注入。 接下来让我们看看具体实现吧。
一、查找方法注入:又称为Lookup方法注入,用于注入方法返回结果,也就是说能通过配置方式替换方法返回结果。使用<lookup-method name="方法名" bean="bean名字"/>配置;其中name属性指定方法名,bean属性指定方法需返回的Bean。
方法定义格式:访问级别必须是public或protected,保证能被子类重载,可以是抽象方法,必须有返回值,必须是无参数方法,查找方法的类和被重载的方法必须为非final:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
因为“singleton”Bean在容器中只有一个实例,而“prototype”Bean是每次获取容器都返回一个全新的实例,所以如果“singleton”Bean在使用“prototype” Bean情况时,那么“prototype”Bean由于是“singleton”Bean的一个字段属性,所以获取的这个“prototype”Bean就和它所在的“singleton”Bean具有同样的生命周期,所以不是我们所期待的结果。因此查找方法注入就是用于解决这个问题。
1) 首先定义我们需要的类,Printer类是一个有状态的类,counter字段记录访问次数:
- package cn.javass.spring.chapter3.bean;
- public class Printer {
- private int counter = 0;
- public void print(String type) {
- System.out.println(type + " printer: " + counter++);
- }
- }
HelloImpl5类用于打印欢迎信息,其中包括setter注入和方法注入,此处特别需要注意的是该类是抽象的,充分说明了需要容器对其进行子类化处理,还定义了一个抽象方法createPrototypePrinter用于创建“prototype”Bean,createSingletonPrinter方法用于创建“singleton”Bean,此处注意方法会被Spring拦截,不会执行方法体代码:
- package cn.javass.spring.chapter3;
- import cn.javass.spring.chapter2.helloworld.HelloApi;
- import cn.javass.spring.chapter3.bean.Printer;
- public abstract class HelloImpl5 implements HelloApi {
- private Printer printer;
- public void sayHello() {
- printer.print("setter");
- createPrototypePrinter().print("prototype");
- createSingletonPrinter().print("singleton");
- }
- public abstract Printer createPrototypePrinter();
- public Printer createSingletonPrinter() {
- System.out.println("该方法不会被执行,如果输出就错了");
- return new Printer();
- }
- public void setPrinter(Printer printer) {
- this.printer = printer;
- }
- }
2) 开始配置了,配置文件在(resources/chapter3/lookupMethodInject.xml),其中“prototypePrinter”是“prototype”Printer,“singletonPrinter”是“singleton”Printer,“helloApi1”是“singleton”Bean,而“helloApi2”注入了“prototype”Bean:
- <bean id="prototypePrinter"
- class="cn.javass.spring.chapter3.bean.Printer" scope="prototype"/>
- <bean id="singletonPrinter"
- class="cn.javass.spring.chapter3.bean.Printer" scope="singleton"/>
- <bean id="helloApi1" class="cn.javass.spring.chapter3.HelloImpl5" scope="singleton">
- <property name="printer" ref="prototypePrinter"/>
- <lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>
- <lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>
- </bean>
- <bean id="helloApi2" class="cn.javass.spring.chapter3.HelloImpl5" scope="prototype">
- <property name="printer" ref="prototypePrinter"/>
- <lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>
- <lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>
- </bean>
3)测试代码如下:
- package cn.javass.spring.chapter3;
- import org.junit.Test;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import cn.javass.spring.chapter2.helloworld.HelloApi;
- public class MethodInjectTest {
- @Test
- public void testLookup() {
- ClassPathXmlApplicationContext context =
- new ClassPathXmlApplicationContext("chapter3/lookupMethodInject.xml");
- System.out.println("=======singleton sayHello======");
- HelloApi helloApi1 = context.getBean("helloApi1", HelloApi.class);
- helloApi1.sayHello();
- helloApi1 = context.getBean("helloApi1", HelloApi.class);
- helloApi1.sayHello();
- System.out.println("=======prototype sayHello======");
- HelloApi helloApi2 = context.getBean("helloApi2", HelloApi.class);
- helloApi2.sayHello();
- helloApi2 = context.getBean("helloApi2", HelloApi.class);
- helloApi2.sayHello();
- }}
其中“helloApi1”测试中,其输出结果如下:
- =======singleton sayHello======
- setter printer: 0
- prototype printer: 0
- singleton printer: 0
- setter printer: 1
- prototype printer: 0
- singleton printer: 1
首先“helloApi1”是“singleton”,通过setter注入的“printer”是“prototypePrinter”,所以它应该输出“setter printer:0”和“setter printer:1”;而“createPrototypePrinter”方法注入了“prototypePrinter”,所以应该输出两次“prototype printer:0”;而“createSingletonPrinter”注入了“singletonPrinter”,所以应该输出“singleton printer:0”和“singleton printer:1”。
而“helloApi2”测试中,其输出结果如下:
- =======prototype sayHello======
- setter printer: 0
- prototype printer: 0
- singleton printer: 2
- setter printer: 0
- prototype printer: 0
- singleton printer: 3
首先“helloApi2”是“prototype”,通过setter注入的“printer”是“prototypePrinter”,所以它应该输出两次“setter printer:0”;而“createPrototypePrinter”方法注入了“prototypePrinter”,所以应该输出两次“prototype printer:0”;而“createSingletonPrinter”注入了“singletonPrinter”,所以应该输出“singleton printer:2”和“singleton printer:3”。
大家是否注意到“createSingletonPrinter”方法应该输出“该方法不会被执行,如果输出就错了”,而实际是没输出的,这说明Spring拦截了该方法并使用注入的Bean替换了返回结果。
方法注入主要用于处理“singleton”作用域的Bean需要其他作用域的Bean时,采用Spring查找方法注入方式无需修改任何代码即能获取需要的其他作用域的Bean。
二、替换方法注入:也叫“MethodReplacer”注入,和查找注入方法不一样的是,他主要用来替换方法体。通过首先定义一个MethodReplacer接口实现,然后如下配置来实现:
- <replaced-method name="方法名" replacer="MethodReplacer实现">
- <arg-type>参数类型</arg-type>
- </replaced-method>”
1)首先定义MethodReplacer实现,完全替换掉被替换方法的方法体及返回值,其中reimplement方法重定义方法 功能,参数obj为被替换方法的对象,method为被替换方法,args为方法参数;最需要注意的是不能再 通过“method.invoke(obj, new String[]{"hehe"});” 反射形式再去调用原来方法,这样会产生循环调用;如果返回值类型为Void,请在实现中返回null:
- package cn.javass.spring.chapter3.bean;
- import java.lang.reflect.Method;
- import org.springframework.beans.factory.support.MethodReplacer;
- public class PrinterReplacer implements MethodReplacer {
- @Override
- public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
- System.out.println("Print Replacer");
- //注意此处不能再通过反射调用了,否则会产生循环调用,知道内存溢出
- //method.invoke(obj, new String[]{"hehe"});
- return null;
- }
- }
2)配置如下,首先定义MethodReplacer实现,使用< replaced-method >标签来指定要进行替换方法,属性name指定替换的方法名字,replacer指定该方法的重新实现者,子标签< arg-type >用来指定原来方法参数的类型,必须指定否则找不到原方法:
- <bean id="replacer" class="cn.javass.spring.chapter3.bean.PrinterReplacer"/>
- <bean id="printer" class="cn.javass.spring.chapter3.bean.Printer">
- <replaced-method name="print" replacer="replacer">
- <arg-type>java.lang.String</arg-type>
- </replaced-method>
- </bean>
3)测试代码将输出“Print Replacer ”,说明方法体确实被替换了:
- @Test
- public void testMethodReplacer() {
- ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/methodReplacerInject.xml");
- Printer printer = context.getBean("printer", Printer.class);
- printer.print("我将被替换");
- }
相关推荐
内容概要:本文详细介绍了基于FPGA的四相八拍步进电机控制系统的开发过程。主要内容包括:1. 使用VHDL和Verilog编写LED显示屏驱动代码,用于显示角度、学号和姓名等信息;2. 实现步进电机的正反转控制,通过状态机管理相序变化;3. 开发加速减速控制模块,确保电机启动和停止时的平稳性;4. 设计调速功能,通过调节脉冲频率实现速度控制。此外,文中还讨论了调试过程中遇到的问题及其解决方案。 适合人群:对FPGA开发和步进电机控制感兴趣的电子工程师、嵌入式系统开发者以及相关专业的学生。 使用场景及目标:适用于需要高精度运动控制的应用场合,如工业自动化、机器人技术和精密仪器等领域。目标是帮助读者掌握FPGA控制步进电机的基本原理和技术细节。 其他说明:文中提供了详细的代码片段和调试经验分享,有助于读者更好地理解和应用所学知识。同时,作者还提到了一些实用技巧,如通过PWM调节实现多级变速,以及如何避免步进电机的共振问题。
内容概要:本文详细介绍了基于Android Studio开发的日历备忘录记事本项目,涵盖日历查看、添加备忘录、闹钟提醒和删除备忘录等功能。项目使用SQLite数据库进行数据存储,通过CalendarView、EditText、Button等控件实现用户交互,并利用AlarmManager和PendingIntent实现闹钟提醒功能。此外,项目还包括数据库的设计与管理,如创建DatabaseHelper类来管理数据库操作,确保数据的安全性和完整性。文章还探讨了一些常见的开发技巧和注意事项,如时间戳的使用、手势监听的实现等。 适用人群:适用于初学者和有一定经验的Android开发者,尤其是希望深入了解Android开发基础知识和技术细节的人群。 使用场景及目标:该项目旨在帮助开发者掌握Android开发的基本技能,包括UI设计、数据库操作、闹钟提醒机制等。通过实际项目练习,开发者能够更好地理解和应用这些技术,提升自己的开发能力。 其他说明:文中提到一些进阶任务,如用Room替换SQLite、增加分类标签、实现云端同步等,鼓励开发者进一步扩展和优化项目。同时,项目源码公开,便于学习和参考。
内容概要:本文档详细介绍了一个基于SVM(支持向量机)和Adaboost集成学习的时间序列预测项目。该项目旨在通过结合这两种强大算法,提升时间序列预测的准确性和稳定性。文档涵盖了项目的背景、目标、挑战及其解决方案,重点介绍了模型架构、数据预处理、特征选择、SVM训练、Adaboost集成、预测与误差修正等环节。此外,文档还探讨了模型在金融市场、气象、能源需求、交通流量和医疗健康等多个领域的应用潜力,并提出了未来改进的方向,如引入深度学习、多任务学习、联邦学习等先进技术。 适合人群:具备一定机器学习基础的研究人员和工程师,特别是那些从事时间序列预测工作的专业人士。 使用场景及目标:①用于金融市场、气象、能源需求、交通流量和医疗健康等领域的复杂时间序列数据预测;②通过结合SVM和Adaboost,提升预测模型的准确性和稳定性;③处理噪声数据,降低计算复杂度,提高模型的泛化能力和实时预测能力。 其他说明:文档不仅提供了详细的理论解释,还附有完整的Matlab代码示例和GUI设计指导,帮助读者理解和实践。此外,文档还讨论了模型的部署与应用,包括系统架构设计、实时数据流处理、可视化界面、GPU加速推理等方面的技术细节。
#游戏之追逐奶酪123
内容概要:本文详细介绍了威纶通触摸屏配方管理系统的实现方法及其应用场景。首先,文章讲解了配方管理的基本概念和技术背景,强调了配方管理在工业自动化中的重要性。接着,通过具体的宏程序代码示例,展示了如何实现配方的保存、加载以及安全校验等功能。文中还提到配方数据结构的设计,如使用寄存器地址偏移来确保数据不冲突,并通过CSV文件格式方便地管理和维护配方数据。此外,文章深入探讨了UI设计方面的内容,包括动态图层技术和按钮交互效果的应用,使得用户界面更加友好和直观。最后,作者分享了一些实际项目中的经验和技巧,如文件操作的异常处理和宏指令调试方法。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是对触摸屏配方管理系统感兴趣的读者。 使用场景及目标:适用于需要频繁切换设备参数的生产环境,如食品加工、注塑成型等行业。通过使用威纶通触摸屏配方管理系统,可以提高工作效率,减少人为错误,同时简化设备调试和维护流程。 其他说明:附带的工具包提供了完整的宏指令注释版、图库资源和调试工具,帮助用户更好地理解和应用该系统。
张彩明-图形学简明教程 PPT课件
计算机术语.pdf
内容概要:本文详细介绍了利用改进粒子群算法(IPSO)进行微电网多目标优化调度的方法和技术。首先指出了传统粒子群算法(PSO)存在的局限性,如初始化随机性和易陷入局部最优等问题。接着提出了多种改进措施,包括混沌映射初始化、动态权重调整、自适应变异以及引入帕累托前沿机制等。文中通过具体的代码实例展示了这些改进的具体实现,并通过实验验证了改进后的算法在处理微电网优化调度问题时的有效性,尤其是在应对风光发电不确定性方面表现突出。此外,文章还讨论了实际应用场景中的约束处理方法,如功率平衡约束的修复策略,确保理论与实践相结合。 适合人群:对智能优化算法及其在电力系统特别是微电网中的应用感兴趣的科研人员、工程师及研究生。 使用场景及目标:适用于需要对微电网进行多目标优化调度的研究和工程项目,旨在提高微电网运行效率,降低成本并减少环境污染。通过学习本文提供的改进算法和技术手段,能够更好地理解和掌握如何针对特定业务场景定制化地改进经典优化算法。 其他说明:文章不仅提供了详细的理论分析和算法改进思路,还包括了大量的代码片段和实验结果,有助于读者深入理解并快速应用于实际项目中。
内容概要:本文详细介绍了基于西门子S7-1200 PLC和组态王的7车位3x3升降横移立体车库控制系统的设计与实现。主要内容涵盖IO分配、梯形图程序、接线图、组态画面设计以及安全防护逻辑等方面。文中强调了硬件互锁、软件互锁、模块化编程、精确控制和平移控制等关键技术点,并分享了一些调试经验和注意事项。此外,还讨论了光电传感器误触发、急停按钮处理、故障记录等实际应用中的挑战及其解决方案。 适合人群:从事工业自动化领域的工程师和技术人员,特别是熟悉PLC编程和组态软件使用的专业人员。 使用场景及目标:适用于需要设计和实施立体车库控制系统的工程项目。目标是帮助读者掌握S7-1200 PLC与组态王的具体应用方法,提高系统可靠性和安全性。 其他说明:文中提供了详细的代码片段和配置示例,有助于读者更好地理解和实践相关技术。同时,作者分享了许多宝贵的实战经验,对于初学者和有一定经验的技术人员都非常有价值。
内容概要:本文详细介绍了线性表及其顺序表示的概念、原理和操作。线性表作为一种基础数据结构,通过顺序表示将元素按顺序存储在连续的内存空间中。文中解释了顺序表示的定义与原理,探讨了顺序表与数组的关系,并详细描述了顺序表的基本操作,包括初始化、插入、删除和查找。此外,文章分析了顺序表的优点和局限性,并讨论了其在数据库索引、图像处理和嵌入式系统中的实际应用。最后,对比了顺序表和链表的性能特点,帮助读者根据具体需求选择合适的数据结构。 适合人群:计算机科学专业的学生、软件开发人员以及对数据结构感兴趣的自学者。 使用场景及目标:①理解线性表顺序表示的原理和实现;②掌握顺序表的基本操作及其时间复杂度;③了解顺序表在实际应用中的优势和局限性;④学会根据应用场景选择合适的数据结构。 其他说明:本文不仅提供了理论知识,还附带了具体的代码实现,有助于读者更好地理解和实践线性表的相关概念和技术。
计算机数学1 -5 重言式与蕴含式.pdf
内容概要:本文详细介绍了风电永磁直驱发电并网系统的构成及其关键控制部分。首先探讨了真实的风速模型构建方法,利用MATLAB生成带有随机扰动和突风成分的风速曲线,用于模拟自然界的风况。接着深入解析了永磁电机的转速控制机制,特别是最大功率点跟踪(MPPT)算法的具体实现方式,以及如何通过PI控制器调节电磁转矩。随后讨论了并网过程中LCL滤波器的设计要点,确保谐波失真小于3%的同时保持系统稳定性。此外,还涉及到了网侧变流器的锁相环(PLL)设计,增强了其在电网电压跌落情况下的快速跟踪能力。最后讲述了整套系统联调时遇到的问题及解决方案,如协同惯量控制策略应对电网扰动等。 适合人群:从事风力发电研究的技术人员、高校相关专业师生、对新能源发电感兴趣的工程爱好者。 使用场景及目标:适用于希望深入了解永磁直驱风力发电系统的工作原理和技术细节的人群。目标是掌握从风速建模到最终并网控制的完整流程,能够独立进行系统仿真和优化。 其他说明:文中提供了大量具体的代码示例,涵盖MATLAB、Python、C等多种编程语言,有助于读者更好地理解和实践所介绍的内容。
资源内项目源码是均来自个人的课程设计、毕业设计或者具体项目,代码都测试ok,包含核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,答辩评审绝对信服的,拿来就能用。放心下载使用!源码、数据集、部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.dataset.txt文件,仅供学习参考, 切勿用于商业用途。
本程序使用于:思迅软件、科脉软件、百威软件、泰格软件、嬴通软件等。 安装配置完连接参数后,用默认管理员账号:辞郁,密码:ciyu登录,主界面左上角,双击输入管理员辞郁密码:ciyu 进入设计模式。下载内容中有详细示例截图。 辞郁POP打印工具是一款专业的打印解决方案,主要针对零售行业的商品POP促销单。它支持多种零售软件系统,包括但不限于思迅软件、科脉软件、百威软件、泰格软件和嬴通软件。这种工具的出现极大地便利了零售业者在商品推广和营销方面的操作,通过快速生成并打印商品促销单,帮助商家更好地吸引顾客、提升销售业绩。
内容概要:本文详细介绍了利用蒙特卡洛法对电动汽车负荷进行预测的方法。首先解释了基本原理,即通过建立电动汽车出行时间、行驶里程和充电时间的概率模型,采用蒙特卡洛法进行抽样并累加每辆车的充电负荷,从而得出负荷预测结果。随后展示了具体的MATLAB代码实现,包括初始化参数设置、蒙特卡洛仿真循环、结果处理和可视化。代码中涉及到随机数生成、概率分布、数组操作等关键技术点。通过对不同类型的电动汽车(如私家车和出租车)进行建模,模拟了它们的充电行为,并分析了充电负荷的时间分布特点。最后讨论了模型的可扩展性和改进方向,如引入智能充电策略等。 适合人群:对电力系统、电动汽车技术和蒙特卡洛仿真方法感兴趣的科研人员、工程师和技术爱好者。 使用场景及目标:适用于研究和评估电动汽车对电网的影响,帮助规划和设计充电基础设施,确保电网稳定运行。同时,也为进一步优化充电策略提供了理论支持。 其他说明:文中提供的MATLAB代码可以作为学习和研究的基础,用户可以根据具体情况进行修改和完善。此外,还提到了一些常见的编程技巧和注意事项,有助于提高代码质量和效率。
内容概要:本文详细介绍了如何利用Python进行电网故障仿真,重点在于不同类型故障(单相接地、相间短路、相间短路接地)下的序分量分析。文中首先准备了必要的工具包,定义了系统参数,并通过具体的代码实例展示了如何计算和可视化各种故障状态下的正序、负序和零序分量。此外,还讨论了不同类型的故障对序分量的具体影响及其在继电保护中的应用。通过这些仿真,能够更好地理解和预测保护装置的动作特性。 适合人群:从事电力系统分析、继电保护设计以及相关领域的工程师和技术人员。 使用场景及目标:适用于研究和开发电力系统的故障检测和保护机制,帮助工程师们优化继电保护装置的参数设置,提高电力系统的稳定性和可靠性。 其他说明:文章强调了仿真过程中需要注意的关键点,如接地电阻设置、变压器接线方式、线路参数单位等,确保仿真结果的准确性。同时,提供了多个代码片段作为参考,便于读者快速上手实践。
6G中基于量子计算的路由 该代码使用量子退火来优化6G网络中的路径选择 基于图的网络,在考虑干扰和拥塞的同时,根据最短路径优化路由路径。
内容概要:本文详细介绍了基于西门子S7-1200 PLC系统的三个核心技术实现:Modbus RTU轮询、PLC间数据交互以及流量PID控制。对于Modbus RTU轮询,作者通过构建设备地址池并利用数组索引作为指针来高效管理39个不同类型设备的通信,确保了稳定的轮询机制。PLC间的S7通讯则通过精心规划DB块映射,实现了高效可靠的数据交换。而在流量PID控制方面,作者不仅解决了流量计信号毛刺的问题,还引入了前馈补偿以应对阀门间的耦合效应,最终达到了精确的流量控制。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些正在使用或计划使用S7-1200 PLC进行复杂项目开发的人士。 使用场景及目标:适用于需要处理大量Modbus设备轮询、实现PLC间高效数据交互以及精准流量控制的工业自动化项目。目标是在提高系统稳定性的同时,优化各个功能模块的工作效率。 其他说明:文中提供了丰富的代码片段和实践经验分享,帮助读者更好地理解和应用相关技术。同时强调了一些容易忽视的关键细节,如设备地址池的设计、DB块的正确配置以及PID参数调整等。
内容概要:本文详细介绍了基于三菱PLC的注塑机控制系统的设计方法,涵盖接线图与IO分配、梯形图程序设计以及触摸屏组态设计。首先明确了注塑机的基本控制需求如温度、压力和时间控制,然后具体讲解了PLC与注塑机各部件之间的连接方式,包括温度传感器、加热器等设备的接口配置。接着深入探讨了梯形图编程的具体实现,提供了多个实用的例子,如急停控制、温度控制等。对于触摸屏组态部分,则强调了如何利用三菱专用软件创建直观的操作界面,确保操作员可以方便地监控和调整各项参数。最后讨论了系统集成与测试的方法,确保所有组件协同工作无误。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是对PLC编程和触摸屏应用有一定了解的人群。 使用场景及目标:适用于希望深入了解三菱PLC在注塑机控制中的应用,掌握从硬件选型、程序编写到最终调试全过程的专业人士。目标是帮助读者构建一个高效稳定的注塑机控制系统。 其他说明:文中提到许多实际操作经验和常见错误避免措施,有助于初学者快速入门并减少开发过程中遇到的问题。此外,还涉及到了一些高级特性,如通过Modbus TCP协议接入MES系统,为后续扩展提供了思路。
内容概要:本文详细介绍了如何利用Python脚本将GeoStudio的SEEP/W模块计算得到的非饱和渗流场数据导入FLAC3D进行耦合分析。主要内容涵盖从SEEP/W导出节点孔隙水压力文本文件,通过Python脚本处理并生成FLAC3D可识别的输入文件,以及在FLAC3D中调用生成的FISH文件完成孔隙水压力场的初始化。文中强调了单位制统一、网格匹配等常见问题,并提供了完整的案例文件和转换脚本。此外,还讨论了非饱和区渗透系数设置、土水特征曲线参数调整等细节。 适合人群:从事岩土工程分析的技术人员,尤其是熟悉GeoStudio和FLAC3D软件的工程师。 使用场景及目标:适用于需要进行渗流场与应力场耦合分析的项目,如矿山排土场稳定性分析。目标是提高分析精度,减少重复建模的工作量,优化计算效率。 其他说明:建议初学者从简化模型开始练习,逐步掌握数据转换技巧。案例文件可在GitHub上获取,便于实践操作。