一、延迟初始化Bean
延迟初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用时才创建及初始化Bean。
配置方式很简单只需在<bean>标签上指定“lazy-init”属性值为“true”即可延迟初始化Bean。
Spring容器会在创建容器时提前初始化“singleton”作用域的Bean,“singleton”就是单例的意思即整个容器每个Bean只有一个实例,后边会详细介绍。Spring容器预先初始化Bean通常能帮助我们提前发现配置错误,所以如果没有什么情况建议开启,除非有某个Bean可能需要加载很大资源,而且很可能在整个应用程序生命周期中很可能使用不到,可以设置为延迟初始化。
延迟初始化的Bean通常会在第一次使用时被初始化;或者在被非延迟初始化Bean作为依赖对象注入时在会随着初始化该Bean时被初始化,因为在这时使用了延迟初始化Bean。
容器管理初始化Bean消除了编程实现延迟初始化,完全由容器控制,只需在需要延迟初始化的Bean定义上配置即可,比编程方式更简单,而且是无侵入代码的。
<bean id="circleA" class="com.iflytek.demo.Demo" lazy-init="true"/>
二、使用depends-on
depends-on是指指定Bean初始化及销毁时的顺序,使用depends-on属性指定的Bean要先初始化完毕后才初始化当前Bean,由于只有“singleton”Bean能被Spring管理销毁,所以当指定的Bean都是“singleton”时,使用depends-on属性指定的Bean要在指定的Bean之后销毁。
注:文档中说销毁Bean的顺序:Dependent beans that define a depends-on relationship with a given bean aredestroyed first, prior to the given bean itself being destroyed.
意思是:在depends-on属性中定义的“依赖Bean”要在定义该属性的Bean之前销毁。
但实际是错误的,定义“depends-on”属性的Bean会首先被销毁,然后才是“depends-on”指定的Bean被销毁。大家可以试验一下。
<bean id="helloApi" class="com.iflytek.demo.HelloImpl"/> <bean id="decorator" class="com.iflytek.demo.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”还保持着资源访问,造成资源不能释放或释放错误。
Demo
ResourceBean.java
package com.iflytek.demo; 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; } }
DependentBean.java
package com.iflytek.demo; 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; } }
demors.xml
<bean id="resourceBean" class="com.iflytek.demo.ResourceBean" init-method="init" destroy-method="destroy"> <!-- Spring容器能自动把字符串转换为java.io.File。 --> <property name="file" value="C:\Users\xdwang\Desktop\temp.txt" /> </bean> <!-- init-method="init" : 指定初始化方法,在构造器注入和setter注入完毕后执行。 --> <!-- destroy-method="destroy":指定销毁方法,只有“singleton”作用域能销毁,“prototype”作用域的一定不能,其他作用域不一定能; --> <bean id="dependentBean" class="com.iflytek.demo.DependentBean" init-method="init" destroy-method="destroy" depends-on="resourceBean"> <property name="resourceBean" ref="resourceBean" /> </bean>
Test
@Test public void testDependOn() throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "demors.xml"); // 一点要注册销毁回调,否则我们定义的销毁方法不执行 context.registerShutdownHook(); DependentBean dependentBean = context.getBean("dependentBean", DependentBean.class); dependentBean.write("aaa"); }
输出结果:
ResourceBean:========初始化 ResourceBean:========加载资源,执行一些预操作 DependentBean:=======初始化 DependentBean:=======写资源 DependentBean:=======销毁 ResourceBean:========销毁 ResourceBean:========释放资源,执行一些清理操作
三、自动装配
自动装配就是指由Spring来自动地注入依赖对象,无需人工参与。
目前Spring3.0支持“no”、“byName ”、“byType”、“constructor”四种自动装配,默认是“no”指不支持自动装配的,其中Spring3.0已不推荐使用之前版本的“autodetect”自动装配,推荐使用Java 5+支持的(@Autowired)注解方式代替;如果想支持“autodetect”自动装配,请将schema改为“spring-beans-2.5.xsd”或去掉。
自动装配的好处是减少构造器注入和setter注入配置,减少配置文件的长度。自动装配通过配置<bean>标签的“autowire”属性来改变自动装配方式。接下来让我们挨着看下配置的含义。
1、default:表示使用默认的自动装配,默认的自动装配需要在<beans>标签中使用default-autowire属性指定,其支持“no”、“byName ”、“byType”、“constructor”四种自动装配,如果需要覆盖默认自动装配,请继续往下看;
2、no:意思是不支持自动装配,必须明确指定依赖。
3、byName:通过设置Bean定义属性autowire="byName",意思是根据名字进行自动装配,只能用于setter注入。比如我们有方法“setHelloApi”,则“byName”方式Spring容器将查找名字为helloApi的Bean并注入,如果找不到指定的Bean,将什么也不注入。
例如如下Bean定义配置:
<bean id="helloApi" class="com.iflytek.demo.HelloImpl"/> <bean id="bean" class="com.iflytek.demo.HelloApiDecorator" autowire="byName"/>
@Test public void testAutowireByName() throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("autowire-byName.xml"); HelloApi helloApi = context.getBean("bean", HelloApi.class); helloApi.sayHello(); }
是不是不要配置<property>了,如果一个bean有很多setter注入,通过“byName”方式是不是能减少很多<property>配置。此处注意了,在根据名字注入时,将把当前Bean自己排除在外:比如“hello”Bean类定义了“setHello”方法,则hello是不能注入到“setHello”的。
4、“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="com.iflytek.demo.HelloImpl"/> <bean id="bean" class="com.iflytek.demo.HelloApiDecorator" autowire="byType"/>
2)根据类型找到多个Bean时,对于集合类型(如List、Set)将注入所有匹配的候选者,而对于其他类型遇到这种情况可能需要使用“autowire-candidate”属性为false来让指定的Bean放弃作为自动装配的候选者,或使用“primary”属性为true来指定某个Bean为首选Bean:
2.1)通过设置Bean定义的“autowire-candidate”属性为false来把指定Bean后自动装配候选者中移除:
<bean class="com.iflytek.demo.HelloImpl"/> <!-- 从自动装配候选者中去除 --> <bean class="com.iflytek.demo.HelloImpl" autowire-candidate="false"/> <bean id="bean1" class="com.iflytek.demo.HelloApiDecorator" autowire="byType"/>
2.2)通过设置Bean定义的“primary”属性为false来把指定自动装配时候选者中首选Bean:
<bean class="com.iflytek.demo.HelloImpl"/> <!-- 自动装配候选者中的首选Bean--> <bean class="com.iflytek.demo.HelloImpl" primary="true"/> <bean id="bean" class="com.iflytek.demo.HelloApiDecorator" autowire="byType"/>
5、“constructor”:通过设置Bean定义属性autowire="constructor",功能和“byType”功能一样,根据类型注入构造器参数,只是用于构造器注入方式,直接看例子吧:
<bean class="com.iflytek.demo.HelloImpl"/> <!-- 自动装配候选者中的首选Bean--> <bean class="com.iflytek.demo.HelloImpl" primary="true"/> <bean id="bean" class="com.iflytek.demo.HelloApiDecorator" autowire="constructor"/>
@Test public void testAutowireByConstructor() throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("autowire-byConstructor.xml"); HelloApi helloApi = context.getBean("bean", HelloApi.class); helloApi.sayHello(); }
6、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时什么也不做,这样在程序中总会莫名其妙的发生一些空指针异常,而且是在程序运行期间才能发现,有没有办法能在提前发现这些错误呢?接下来就让我来看下依赖检查吧。
四、依赖检查
上一节介绍的自动装配,很可能发生没有匹配的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="com.iflytek.demo.HelloImpl"/> <!-- 注意我们没有注入helloApi,所以测试时会报错 --> <bean id="bean" class="com.iflytek.demo.HelloApiDecorator" dependency-check="objects"> <property name="message" value="Haha"/> </bean>
注意由于我们没有注入bean需要的依赖“helloApi”,所以应该抛出异常UnsatisfiedDependencyException,表示没有发现满足的依赖:
@Test(expected = UnsatisfiedDependencyException.class) public void testDependencyCheckByObject() throws IOException { //将抛出异常 new ClassPathXmlApplicationContext("dependency-check-object.xml"); }
三、simple:对基本类型进行依赖检查,包括数组类型,其他依赖不报错;配置方式为:dependency-check="simple",以下配置中没有注入message属性,所以会抛出异常:
<bean id="helloApi" class="com.iflytek.demo.HelloImpl"/> <!-- 注意我们没有注入message属性,所以测试时会报错 --> <bean id="bean" class="com.iflytek.demo.HelloApiDecorator" dependency-check="simple"> <property name="helloApi" ref="helloApi"/> </bean>
四、all:对所以类型进行依赖检查,配置方式为:dependency-check="all",如下配置方式中如果两个属性其中一个没配置将报错。
<bean id="helloApi" class="com.iflytek.demo.HelloImpl"/> <bean id="bean" class="com.iflytek.demo.HelloApiDecorator" dependency-check="all"> <property name="helloApi" ref="helloApi"/> <property name="message" value="Haha"/> </bean>
依赖检查也可以通过“<beans>”标签中default-dependency-check属性来指定全局依赖检查配置。
五、方法注入
所谓方法注入其实就是通过配置方式覆盖或拦截指定的方法,通常通过代理模式实现。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 com.iflytek.demo; 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 com.iflytek.dmeo; import com.iflytek.demo.HelloApi; import com.iflytek.demo.Printer; public abstract class HelloImpl5 implements HelloApi { private Printer printer; public void sayHello() { printer.print("setter"); createPrototypePrinter().print("prototype"); } public abstract Printer createPrototypePrinter(); public Printer createSingletonPrinter() { System.out.println("该方法不会被执行,如果输出就错了"); return new Printer(); } public void setPrinter(Printer printer) { this.printer = printer; } }
2) 开始配置了,其中“prototypePrinter”是“prototype”Printer,“singletonPrinter”是“singleton”Printer,“helloApi1”是“singleton”Bean,而“helloApi2”注入了“prototype”Bean:
<bean id="prototypePrinter" class="com.iflytek.demo.Printer" scope="prototype" /> <bean id="singletonPrinter" class="com.iflytek.demo.Printer" scope="singleton" /> <bean id="helloApi1" class="com.iflytek.demo.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="com.iflytek.demo.HelloImpl5" scope="prototype"> <property name="printer" ref="prototypePrinter" /> <lookup-method name="createPrototypePrinter" bean="prototypePrinter" /> <lookup-method name="createSingletonPrinter" bean="singletonPrinter" /> </bean>
@Test public void testLookup() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("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 com.iflytek.demo.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="com.iflytek.demo.PrinterReplacer"/> <bean id="printer" class="com.iflytek.demo.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("methodReplacerInject.xml"); Printer printer = context.getBean("printer", Printer.class); printer.print("我将被替换"); }
相关推荐
白色宽屏风格的芭蕾舞蹈表演企业网站模板.rar
由一个精美的UI集成界面和5个小游戏组成(球球大作战,坦克大战,飞机大战,球球消消乐,贪吃蛇)
内容概要:本文介绍了如何使用Python和OpenCV库识别电梯开关门视频中的门位置。具体步骤包括将视频帧转换为灰度图像、应用高斯模糊减少噪声、使用Canny边缘检测算法检测图像边缘、查找和筛选轮廓、确定门的位置并在视频中绘制边界框。该方法适用于门的颜色或纹理与周围环境有明显区别的场景。 适合人群:计算机视觉领域的开发者和研究人员,尤其是对图像处理感兴趣的读者。 使用场景及目标:主要用于监控和安全系统中,对电梯开关门进行自动化检测,确保安全运行。 阅读建议:在理解和实践过程中,建议读者熟悉Python和OpenCV的基本操作,并尝试调整参数以适应不同的视频环境。
智慧工地,作为现代建筑施工管理的创新模式,以“智慧工地云平台”为核心,整合施工现场的“人机料法环”关键要素,实现了业务系统的协同共享,为施工企业提供了标准化、精益化的工程管理方案,同时也为政府监管提供了数据分析及决策支持。这一解决方案依托云网一体化产品及物联网资源,通过集成公司业务优势,面向政府监管部门和建筑施工企业,自主研发并整合加载了多种工地行业应用。这些应用不仅全面连接了施工现场的人员、机械、车辆和物料,实现了数据的智能采集、定位、监测、控制、分析及管理,还打造了物联网终端、网络层、平台层、应用层等全方位的安全能力,确保了整个系统的可靠、可用、可控和保密。 在整体解决方案中,智慧工地提供了政府监管级、建筑企业级和施工现场级三类解决方案。政府监管级解决方案以一体化监管平台为核心,通过GIS地图展示辖区内工程项目、人员、设备信息,实现了施工现场安全状况和参建各方行为的实时监控和事前预防。建筑企业级解决方案则通过综合管理平台,提供项目管理、进度管控、劳务实名制等一站式服务,帮助企业实现工程管理的标准化和精益化。施工现场级解决方案则以可视化平台为基础,集成多个业务应用子系统,借助物联网应用终端,实现了施工信息化、管理智能化、监测自动化和决策可视化。这些解决方案的应用,不仅提高了施工效率和工程质量,还降低了安全风险,为建筑行业的可持续发展提供了有力支持。 值得一提的是,智慧工地的应用系统还围绕着工地“人、机、材、环”四个重要因素,提供了各类信息化应用系统。这些系统通过配置同步用户的组织结构、智能权限,结合各类子系统应用,实现了信息的有效触达、问题的及时跟进和工地的有序管理。此外,智慧工地还结合了虚拟现实(VR)和建筑信息模型(BIM)等先进技术,为施工人员提供了更为直观、生动的培训和管理工具。这些创新技术的应用,不仅提升了施工人员的技能水平和安全意识,还为建筑行业的数字化转型和智能化升级注入了新的活力。总的来说,智慧工地解决方案以其创新性、实用性和高效性,正在逐步改变建筑施工行业的传统管理模式,引领着建筑行业向更加智能化、高效化和可持续化的方向发展。
基于stm32人体健康监测系统,包含pcb (心率,血氧,体温,语音播报,报警) 本设计采用STM32F103C8T6作为主控 使用MAX30102采集心率和血氧值 使用MLX90614测量体温 OLED显示当前信息 语音播报使用SYN6658芯片,外围自己搭建,播放当前温度、心率、血氧 两个按键一个蜂鸣器警报,当体温、心率、血氧异常发出警报 资料包括源码,原理图,pcb,bom清单,都是原始文件
白色简洁风格的流行音乐演奏整站网站源码下载.zip
白色简洁风格的透明登录界面整站网站源码下载.zip
随着学业负担的日益加重,越来越多的学生选择通过家教、自学或参加补习班来加强课外学习。然而,家教费用高昂,自学效率低下且难以及时解决疑难问题,而补习班则受限于时间和地点,灵活性不足。此外,国家政策也不鼓励校外补习。鉴于网络技术的成熟和各类在线平台的兴起,开发一个专业的在线辅助学习网站对于辅助学生的课外学习显得尤为重要。 本在线教育系统基于Vue.js构建,采用B/S架构设计,后端语言为Java,数据库使用MySQL。通过整合Vue.js技术,系统界面更加丰富和友好。系统主要面向课程购买用户,涉及的角色包括管理员、学生和教师。学生可以注册登录后浏览课程视频、收藏课程、留言并购买课程,同时实现订单管理。管理员负责管理学生信息、课程信息、发布班级和管理章节等。教师则可以管理课程订单、课程内容和章节。该系统允许学生利用碎片时间自主学习,具有很高的灵活性,对于难以理解的课程可以反复学习并在线提问,极大地促进了学生的学习。
GaAs限幅器芯片:LCLM0002P1,工作频段DC-3Ghz
基于simulink的12 8开关磁阻电机电流斩波、角度位置调速控制、模型预测电流、转矩控制仿真程序
白色简洁风格的时尚室内设计整站网站源码下载.zip
内容概要:本文详细解析了HTTP请求的整个流程,包括用户请求发起、请求报文构建、服务器处理请求、响应报文生成、网络传输响应和浏览器接收响应六个阶段。每个阶段的内容均涵盖了关键步骤和技术细节,如DNS解析、TCP连接、缓存策略、HTTP/2性能提升、HTTPS加密等。通过这些内容,读者可以全面理解HTTP请求的完整流程。 适合人群:具备一定网络基础知识的前端、后端开发人员及IT运维人员。 使用场景及目标:适用于希望深入了解HTTP协议及其优化技术的技术人员,有助于提升系统的性能和安全性,优化用户体验。 阅读建议:本文内容详尽且涉及多个关键技术点,建议读者结合实际案例进行学习,逐步理解和掌握各个阶段的技术细节和优化方法。
2023-04-06-项目笔记-第三百五十九阶段-课前小分享_小分享1.坚持提交gitee 小分享2.作业中提交代码 小分享3.写代码注意代码风格 4.3.1变量的使用 4.4变量的作用域与生命周期 4.4.1局部变量的作用域 4.4.2全局变量的作用域 4.4.2.1全局变量的作用域_1 4.4.2.357局变量的作用域_357- 2024-12-26
白色简洁风格的互联网推广企业网站源码下载.zip
内容概要:本文详细解析了HTTP协议的发展历程,从HTTP/1.0到HTTP/3.0的各个版本演进特点,以及HTTP请求与响应的基本概念。此外,还分析了HTTP报文的结构,包括请求报文和响应报文的具体组成部分,并介绍了HTTPS协议的安全机制。 适合人群:网络开发人员、Web开发者以及对HTTP协议有深入了解需求的技术人员。 使用场景及目标:①理解和掌握HTTP协议的基本概念和工作原理;②了解HTTP协议各版本的改进及应用场景;③学习HTTPS协议的加密机制及其重要性。 阅读建议:本文详细解析了HTTP协议的基础概念和各个版本的演进过程,适合希望深入理解HTTP协议的技术人员阅读。可以结合实际项目中遇到的问题来加深对协议的理解。
OA办公系统源码是开发企业级管理软件的重要组成部分,它基于C#编程语言,利用ASP.NET框架,并结合SQL数据库技术,为企业的日常运营提供高效、便捷的自动化办公环境。源码是开发者的心血结晶,包含了从需求分析到设计、编码、测试的全过程,体现了软件工程的实践与理论知识。下面我们将深入探讨这些关键知识点。 C#是微软公司推出的一种面向对象的编程语言,广泛应用于Windows平台的开发。在OA办公系统中,C#提供了丰富的类库和工具,支持事件驱动编程,使得代码更简洁,可读性更强。C#支持泛型、自动垃圾回收、异常处理等特性,提高了程序的稳定性和安全性。 ASP.NET是微软构建Web应用程序的开发框架,它基于.NET Framework,提供了丰富的控件和模板,简化了网页开发。在OA办公系统源码中,ASP.NET负责处理HTTP请求,呈现动态内容,实现用户交互。MVC(Model-View-Controller)模式是ASP.NET常用的开发模式,它将业务逻辑、数据模型和用户界面分离,便于维护和扩展。 SQL(Structured Query Language)是用于管理和处理关系数据库的标准语
永磁同步电机无位置传感器控制,采用的是龙贝格,基于模型的 定点开发,仿真效果和实际95%高度吻合,可以仿真学习,也可以直接移植到项目中。内容来源于网络分享,如有侵权请联系我删除。另外如果没有积分的同学需要下载,请私信我。
三相逆变 单相 三相逆变器 SPWM ---stm32主控(输入、输出具体可根据需要设定),本逆变器可以二次开发。 本内容只包括 逆变程序,实现变频(0~100Hz)、变压调节,均有外接按键控制(使用C语言实现)。
白色简洁风格的旅行记录整站网站源码下载.zip
白色简洁风格的金融会计行业企业网站模板.rar