1使用Spring

来源:互联网 发布:linux 挂载目录 编辑:程序博客网 时间:2024/05/21 17:38
下载Spring2.5.6的地址:http://www.springframework.org站点。
将spring.jar,cglib,dom4j,jakarta-commons,log4j复制到项目的lib目录下,另外spring.jar还要放置到classpath环境变量中。


public class SpringTest{
public static void main(String[] args){
// 创建spring的ApplicationContext
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(ctx);
}
}
上面的代码仅仅创建了ApplicationContext实例,而ApplicationContext实例正式Spring容器,一旦获得Spring容器,就可通过该容器访问Spring容器中的Bean。


1 依赖注入
当某个java实例(调用者)需要另一个java实例(被调用者)时
,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但是在依赖注入的模式下,创建被被调用者的工作不在由调用者来完成,创建被调用者实例的工作通常由spring容器来完成,然后注入调用者,因此称为依赖注入。spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。
依赖注入通常有两种:
设值注入:IoC容器使用属性的setter方法来注入被依赖的实例。
构造注入:IoC容器使用构造器来注入被依赖的实例。


设值注入
设值注入是指IoC容器使用属性的setter方法来注入被依赖的实例,这种方式简单,直观因而在Spring的依赖注入里大量使用。
例如:
定义了一个Person接口:
public interface Person{
//定义使用父子的方法
public void userAxe();
}


下面是Axe接口的代码:
public interface Axe{
// Axe接口里有个砍的方法
public String chop();
}
下面是Person接口的实现类:
public class Chinese implements Person{
private Axe axe;
public Chinese(){


}
//需写setter方法以便spring向该属性注入
public void setAxe(Axe axe){
this.axe=axe;
}
//实现Person接口的useAxe方法
public void useAxe(){
//调用axe的chop()方法
//表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}
上面的Chinese类中,Chinese类并不知道要调要的axe实例在哪里,它只是需要调用一个Axe实例,这个Axe实例将由spring容器负责注入。下面是提供了Axe的实现类:
public class StoneAxe implements Axe{
public StoneAxe(){
}
public String chop(){
return "石斧砍柴";
}
}
实际上,Spring需要使用XML配置文件来指定实例之间的依赖关系。
Spring采用xml作为配置文件,从spring 2.0开始,spring即可采用DTD来定义配置文件的语义也可采用schema来定配置文件的语义。
<?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:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!--配置chinese实例,其实现类是lee.Chinese-->
<bean id="chinese" class="lee.Chinese">
<!--将stoneAxe注入给axe属性-->
<property name="axe" ref="stoneAxe"/>
</bean>
<!--配置stoneAxe实例,其实现类是lee.StoneAxe-->
<bean id="stoneAxe" class="lee.StoneAxe"/>
</beans>
在配置文件,Spring配置Bean实例通常会指定两个属性:
id,指定该Bean的唯一标识,程序通过id属性值来访问该Bean实例。
class,指定该Bean的实现类,此处不可在用接口,必须是实现类,Spring容器会使用xml解析器读取该属性值,并利用反射来创建该实现类的实例。
spring会自动接管每个<bean../>里定义的<property../>元素定义,Spring会在调用无参数的构造器后,创建默认的Bean实例后,调用对象的setter方法为程序注入属性值。
<property../>元素还用于为Bean对象的属性赋值。如:
<property name="driverClass" value="com.mysql.jdbc.Driver"/>


*********通过ApplicationContext 获取Bean********
下面主程序只是简单获取Person实例,并调用该实例的userAxe方法。如:
public class Bean Text{
public static void main(String[] args)throws Exception{
//创建spring容器
ApplicationContex ctx=new ClassPathXmlApplicationContext("bean.xml");
Person p=(Person)ctx.getBean("chinese");
p.useAxe();
}
可以看出Spring容器就是一个巨大的工厂,它可以生成出所有类型的Bean实例,获取Bean实例的方法是getBean()。


构造注入
public class Chinese implements Person{
private Axe axe;
//默认的构造器
public Chinese(){
}
//构造注入所需的带参数的构造器
public Chinese(Axe axe){
this.axe=axe;
}
//实现Person接口的useAxe方法
public void userAxe(){
//调用axe的chop()方法
//表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}
上面的Chinese类没有提供设置axe属性setter方法,仅仅提供了一个带Axe属性的构造器,Spring将通过构造器为chinese注入Bean实例。
构造注入的配置文件也需要做简单的修改,为了使用构造注入,使用<constructor-arg.../>元素指定构造器的参数。如:
<?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:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="chinese" class="lee.Chinese">
<!--使用构造注入,为chinese实例注入steelAxe实例-->
<constructor-arg ref="steelAxe"/>
</bean>
<bean id="steelAxe" class="lee.SteelAxe"/>
<bean id="stoneAxe" class="lee.StoneAxe"/>
</beans>
上面的配置文件使用<constructor-arg../>元素指定了一个构造器参数,该参数类型是
Axe,这指定Spring调用lee.Chinese类里带一个Axe参数的构造器来创建chinese实例。因为使用了有参数的构造器创建实例,所以当Bean实例被创建完成后,该Bean的依赖关系已经被设置完成。
配置<constructor-arg../>元素时可指定一个index属性,用于指定该构造参数值作为第几个构造参数值,例如index="0"表明该构造参数值将作为第一个构造参数。


2spring的容器
spring有两个核心接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口。它们都可以代表Spring容器,Spring是生成Bean实例的工厂,并管理容器中的Bean。Spring里的Bean是非常广泛的概念,任何的Java对象,Java组件都被当成Bean处理,甚至这些组件并不是标准的JavaBean。


Spring容器最基本的接口就是BeanFactory,BeanFactory负责配置,创建,管理Bean,它有一个子接口:ApplicationContext,因此也被称为Spring上下文。Spring容器还负责管理Bean和Bean之间的依赖关系。BeanFactory接口包含如下几个基本方法:
boolean containsBean(String name),判断Spring容器是否含有id为name的Bean实例,并且类型为requiredType的Bean。
Class getType(String name),返回容器中指定Bean实例的类型。


BeanFactory有很多实现类,通常使用org.springframework.beans.factory.xml.XmlBeanFactory类。但对大部分java EE应用而言,推荐使用ApplicationContext,ApplicationContext是BeanFactory的子接口,其常用的实现类是org.springframework.context.support.FileSystemXmlApplicationContext和org.springframwork.context.support.ClassPathXmlApplicationContext。


创建BeanFactory实例时,必须提供Spring容器管理的Bean的详细配置信息。Spring的配置信息通常采用XML配置文件来设置,因此创建BeanFactory实例时,应该提供XML配置文件作为参数。XML配置文件通常使用Resource对象传入。


**********  Spring容器ApplicationContext  ******************
获得Spring容器ApplicationContext,可以通过它的三个实现类:
FileSystemXmlApplicationContext,ClassPathXmlApplicationContext,WebApplicationContext
ApplicationContext实现类的实例方法:
Object getBean(String arg0),获得id为arg0的Bean的实例。
Class  getType(String arg0), 获得id为arg0的Bean的Class。


在启动Web应用中,采用ContextLoaderListener创建ApplicationContext
使用ContextLoaderListener创建ApplicationContext需要web服务器支持listener(ServletContextListener是从Servlet 2.3才出现的规范),Apache Tomcat是支持Servlet 2.3的,所以我们在Apache Tomcat的web.xml这样配置来加载ApplicationContext:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Spring根据bean定义创建WebApplicationContext对象,并将其保存在Web应用的ServletContext中。大部分情况下,应用中的bean无需感受到 ApplicationContext的存在,只要利用ApplicationContext的IoC即可。WebApplicationContext也是ApplicationContext的实现类。


在web应用中,如果你在控制器,JSP页面中想直接访问Spring容器ApplicationContext,您必须先获取WebApplicationContext对象。Spring容器在启动时将WebApplicationContext保存在ServletContext的属性列表中,通过WebApplicationContextUtils工具类可以方便地获取WebApplicationContext对象。


WebApplicationContextUtils
当Web应用集成Spring容器后,代表Spring容器的WebApplicationContext对象将以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为建存放在ServletContext属性列表中。所以你可以直接通过以下语句获取WebApplicationContext:
WebApplicationContext wac=(WebApplicationContext)servlet.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);


但通过org.springframework.web.context.support包中的WebApplicationContextUtils工具类获取WebApplicationContext更方便:
WebApplicationContext wac=WebApplicationContextUtils.getWebApplicationContext(ServletActionContext.getPageContext().getServletContext());

WebApplicationContext wac=WebApplicationContextUtils.getWebApplicationContext(
request.getServletContext());
getWebApplicationContext(servletContext)方法的参数是一个servletContext对象,我们通过pageContext对象的getServletContext()方法得到。
当ServletContext属性列表中不存在WebApplicationContext时,getWebApplicationContext()方法不会抛出异常,它简单地返回null。而WebApplicationContextUtils的另一个方法getRequiredWebApplicationContext(ServletContext sc)方法要求ServletContext属性列表中一定要包含一个有效的WebApplicationContext,否则会抛出一个IllegalStateException异常。


通过WebApplicationContext获取对应的bean。
context.getBean(spring中配置bean ID);




下面是一个BeanUtils类,我们可以再某个servlet或filter中调用
BeanUtils.setContext(WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
来初始化,然后就可以在程序调用getBean来获取对应的Bean的实例。
public Class BeanUtils{


private Static ThreadLocal<WebApplicationContext> context=new ThreadLocal<WebApplicationContext>();


public Static void setContext(WebApplicationContext context){
BeanUtils.context.set(context);
}




public static Object getBean(String beanId){
return context.get().getBean(beanId));
}


}




对于独立的应用程序可以通过如下方法来实例化BeanFactory。
//以当前文件路径下的beans.xml文件创建Resource对象
InputStreamResource isr=new FileSystemResource("bean.xml");
//以Resource对象作为参数,创建BeanFactory实例
xmlBeanFactory factory=new XmlBeanFactory(ist);
或采用如下方法:
ClassPathResource res=new ClassPathResource("beans.xml");
XmlBeanFactory factory=new XmlBeanFactory(res);


如果应用里有多个属性配置文件,则应该采用BeanFactory的子接口ApplicationContext来创建BeanFactory的实例。ApplicationContext通常使用如下两个实现类:
FileSystemXmlApplicationContext:以基本文件系统的XML配置文件创建ApplicationContext实例。
ClassPathXmlApplicationContext:以类加载路径下的XML配置文件创建ApplicationContext实例。
如果需要同时加载多个XML配置文件,则可以采用如下形式:
//搜索CLASSPATH路径,以CLASSPATH路径下的applicationContext.xml,service.xml文件创建ApplicationContext。
ClassPathXmlApplicationContext appContext=new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml","service.xml"});


//以指定路径下的applicationContext.xml,service.xml文件创建ApplicationContext。
FileSystemXmlApplicationContext appContext=new FileSystemXmlApplicationContext(new String[]{"applicationContext.xml","Service.xml"});
事实上,ApplicationContext是BeanFactory的子接口,可直接将其赋给BeanFactory变量
BeanFactory factory=appContext;




*****************************************
Spring配置文件的根元素是<beans../>,该元素可接受0个到多个<bean../>子元素,每个<bean.../>子元素配置一个Bean实例。如:
<?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:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
...
</beans>
大部分时候,我们不会使用BeanFactory实例作为Spring容器,而是使用ApplicationContext实例作为容器,


AppplicationContext的事件机制:
通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext的事件处理。如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。
Spring的事件框架有如下两个重要的成员:
ApplicationEvent,容器事件,必须由ApplicationContext发布。
ApplicationListener,监听器,可由容器中任何监听Bean担任。






3Bean的基本定义
<beans.../>是spring的根元素,<bean../>元素是<beans../>元素的子元素,<beans../>元素可以包含多个<bean.../>子元素,每个<bean.../>子元素定义一个Bean,每个Bean对应Spring容器里的一个Java实例。
定义Bean时,通常需要指定两个属性:
id,确定该Bean的唯一标识符,容器对Bean管理,访问,以及该Bean的依赖关系,都通过该属性完成。Bean的id属性在Spring容器中应该是唯一的。
class,指定该Bean的具体实现类,这里不能是接口。Spring容器必须知道创建Bean的实现类。而不能是接口。通常,Spring会直接使用new关键字创建该Bean的实例。因此必须提供Bean的实现类的类名。
id属性是容器中Bean的唯一标识,这个id属性必须遵循xml文档的id属性规则(不能以"/"等特殊符号作为属性值必须由字母和数字组合,字母开头)。但在某些特殊的时候,Bean的标识如果必须包含这些特殊符号,此时可以采用name属性,用于指定Bean的别名,通过访问Bean别名来访问Bean。如果需要被Bean实例指定多个别名,可以在name属性中使用逗号,冒号或空格来分隔多个别名,后面通过任一别名即可访问该Bean实例。在一些极端的情况下,程序无法在定义Bean时指定所有的别名,而是需要在其他地方为一个已经存在的Bean实例指定别名,则可以使用<alias../>元素来完成,该元素可指定2个属性:
name:该属性指定一个Bean实例的标识名,表明将该Bean实例指定别名。
alias:指定一个别名。
<alias name="person" alias="jack"/>
默认情况下,Spring会自动预初始化容器中所有的singleton实例,如果我们不想让Spring容器与初始化某个sington Bean,则可为该<bean../>元素增加lazy-init属性,指定该属性为true。
<bean id="bean2" class="lee.Test2" lazy-init="true"/>


Spring容器集中管理Bean的实例化,Bean实例通过BeanFactory的genBean(String beanid)方法得到,


primary,spring3.x中当多个候选受管Bean都符合协作者条件时,只有启用了primary属性,并且其取值为true的受管Bean才有资格注入到其他受管Bean中。


autowire-candidate,spring3.x中该属性不会让autowire-candidate="false"的受管Bean参与候选。




4容器中Bean的作用域
通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域,Spring支持5中作用域,这5中作用域如下所示:
singleton,单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。默认的方式是singleton。ApplicationContext默认预初始化所有的singleton Bean。仅被初始化一次。


prototype,原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。


request,对于每个HTTP请求中,使用request定义的Bean都将产生一个新实例。只有在Web应用中使用spring时,该作用域才真正有效。


global session,每个全局Http Session对应一个Bean的实例。
如果不指定Bean的作用域,Spring默认使用singleton作用域。设置Bean的基本行为,通过scope属性指定,该属性可以接受singleton,prototype,request,session和globalSession。如:
<bean id="p2" class="lee.Person" scope="prototype"/>


request和session作用域只在Web应用中才有效,并且在Web应用中增加而外配置才会生效。为此,我们需要有两种配置方式,采用Listener配置或采用Filter配置。当使用支持Servlet2.4及以上规范的Web容器时,我们可以在Web应用的web.xml文件中增加如下Listener配置,该Listener负责为request作用域生效。如:
<web-app>
...
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
</web-app>
如果只支持Servlet2.4以前规范的Web容器,则该容器不支持Listener规范,故无法使用这种配置方式,只能该改为使用Filter配置:
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
一旦web.xml中增加如上任意一种配置,就可以在Spring配置文件中使用request或session作用域。


5Spring的Bean和JavaBean的区别:
Spring容器对Bean没有特殊要求,甚至不要求该Bean像标准的JavaBean(必须为每个属性提供对应的getter和setter方法)。Spring的Bean是Java实例,java组件,而传统的Java应用中的JavaBean通常作为数据传输对象,用来封装值对象,在各层之间传递数据。当然传统的JavaBean也可以作为Spring的Bean,从而接受Spring管理。
虽然Spring对Bean没有特殊要求,但还是建议Spring中Bean应满足如下几个原则:
尽量为每个Bean实现类提供无参数构造器。
接受构造注入的Bean,则应提供对应的构造函数。
接受设置注入的Bean,则应提供对应的setter方法,并不强制要求提供对应的getter方法。


抽象Bean
所有所有抽象Bean,就是指定abstract属性为true的Bean,抽象Bean不能被实例化,抽象bean的价值在于被继承,抽象Bean通常作为父Bean被继承。Spring容器会忽略所有的抽象Bean定义,预初始化不初始化抽象Bean。抽象Bean的配置如下:
<?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:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">


<bean id="steelAxe" class="lee.SteelAxe"/>
<!--通过abstract属性定义该Bean是抽象Bean-->
<bean id="chineseTemplate" class="lee.Chinese" abstract="true">
<property name="axe" ref="steelAxe"/>
</bean>
</beans>
又因为抽象Bean无需实例化,因此可以没有class属性。即:
<bean id="chineseTemplate" abstract="true">
<property name="axe" ref="steelAxe"/>
</bean>


使用子Bean
子Bean定义可以从父Bean继承实现类,构造器参数,属性值等配置信息。除此之外,子Bean配置可以增加新的配置信息,并可以指定新的配置信息覆盖父Bean的定义,子Bean无法从父Bean继承如下属性:depends-on,autowire,dependency-check,singleto,scope,lazy-init,这些属性总是从子Bean定义中获得,或采用默认值。
通过为一个<bean.../>元素指定的parent属性即可指定该Bean的一个子Bean,parent属性指定该Bean所继承的父Bean的id。如:
<bean id="chinese" parent="chineseTemplate">
<!--覆盖父Bean中依赖关系的配置-->
<property name="axe" ref="stoneAxe"/>
</bean>
Spring中Bean的继承是实例之间的关系,因此主要表现在参数值的延续;而Java中的继承是lei之间的关系,主要表现为方法,属性的延续。
Spring中子Bean不可作为父Bean使用,不具备多态性。Java中的子类实例完全可当成父类实例使用。


依赖关系注入之后的行为
使用init-method属性
init-method属性指定某个方法在Bean的全部属性设置结束自动执行。如为一个Bean增加init-method="init"来指定init()方法,例如:
<bean id="chinese" class="lee.Chinese" init-method="init">
<property name="axe" ref="steelAxe"/>
</bean>
还可以通过实现InitialzingBean接口,该接口提供一个方法:
void afterPropertiesSet()throws Exception
实现该接口必须实现该方法,方法体就是依赖注入完成之后执行的方法。


Bean销毁之前的行为
Spring也提供两种方法定制Bean实例销毁之前的特定行为,这两种如下:
第一种方法,destroy-method属性指定某个方法在Bean销毁之前自动执行。例如:
<bean id="chinese" class="lee.chinese" destroy-method="close">
...
</bean>
第二种方法,就是让Chinese类实现DisposableBean接口,该接口提供一个方法:
void destroy() throws Exception
实现该接口必须实现该方法,该方法就是Bean实例被销毁之前应该执行的方法。


6协调作用域不同步的Bean
当Spring容器中作用域不同的Bean相互依赖时,可以会出现一些问题:当两个singleton作用域Bean存在依赖关系时,或当prototype作用域Bean赖以singleton作用于Bean时,通过属性定义依赖关系就足够了。
但是当singleton作用域Bean依赖prototype作用域Bean时,singlton作用域Bean只有一次初始化的机会,而所依赖的prototype作用域Bean则会不断产生新的Bean实例,这样导致singleton作用域的Bean得不到即时的更形,这样就会出现不同步的现象。
通常使用方法注入解决该问题:
方法注入通常使用lookup方法注入,利用lookup方法注入可以让spring容器重写容器中的Bean的抽象或具体方法,返回容器中其它Bean的结果,被查找的Bean通常是一个非singleton Bean。
如果一个singleton作用域的Bean依赖prototype作用域的Bean,通常情况在singleton作用域的Bean中增加一个抽象方法,spring框架将会负责为该方法提供实现体,这样这个方法就会变成具体方法了,程序也就可以调用该方法了。spring怎么实现该方法呢。通过使用<lookup-method../>元素来配置这个方法。<lookup-method/>元素需要指定如下两个属性:
name:指定需要让Spring实现的方法。
bean,指定Spring方法后,该方法返回的值。如:
<bean id="chinese" class="lee.Chinese">
<lookup-method name="createAxe" bean="steelAxe"/>
<property name="axe" ref="steelAxe"/>
</bean>
该例子中spring负责实现singleton Bean chinese的createAxe()方法,该方法的返回值是容器中的steelAxe Bean的实例。因为createAxe()方法由spring实现,spring保证每次调用createAxe()时都会返回最新的steelAxe实例。


7深入理解依赖关系配置
通常情况下,spring在实例化容器时,会校验BeanFactory中每一个Bean的配置,这些校验包括:
Bean引用的依赖Bean是否指向一个合法的Bean。
Bean的普通属性值是否获得了一个有效值。
对于singleton作用域的Bean,如果没有强行取消其预初始化行为,系统会在创建spring容器时预初始化所有singleton Bean。与此同时,该Bean所依赖Bean也被一起实例化。
BeanFactory与ApplicationContext实例化容器中Bean的时机不同:前者等到程序需要Bean实例时才创建Bean,而后者在容器创建ApplicationContext实例时,会预初始化容器中的全部Bean。
Spring的作用就是管理java EE组件,Spring把所有java对象都称为Bean,所以我们可以把任何java类部署在spring容器中,只要该java类具有相应的构造器即可。spring可以为任何java对象注入任何类型的属性-只要该java对象为该属性提高哦你了对应的setter方法即可。例如:
<bean id="id" class="lee.AClass">
<property name="aaa" value="aVal"/>
<property name="bbb" value="bVal"/>
...
</bean>
对于上面的程序,Spring将采用类似于如下的代码:
//获取lee.AClass类的Class对象
Class targetClass=Class.forName("lee.AClass");
//创建lee.AClas类的默认实例
Object bean=targetClass.new Instance();
<bean../>元素每包含一个<property../>子元素,Spring将为该bean实例调用一次setter方法。


<bean id="id" class="lee.AClass">
<!--property配置需要依赖注入的属性-->
<contructor index="1" value="aval"/>
<contructor index="0" value="bval"/>
</bean>
上面配置片段指定使用构造注入,则spring不会采用默认的构造器来创建Bean实例,而是使用特定构造器来创建该Bean的实例。对于上面的代码将采用如下方式来创建Bean实例:
Class targetClass=Class.forName("lee.AClass");
//获取第一个参数是bVal类型,第二参数是aVal类型的构造器
Constructor targetCtr=targetClass.getConstructor(bVal.getClass(),aVal.getClass());
//以指定的构造器创建Bean实例
Object bean=targetCtr.newInstance(bVal,aVal);


注入普通属性值
<value../>元素用于指定字符串类型,基本类型的属性值。例如:
<property name="integerProperty">
<value>1</value>
</property>
<value../>元素的值也可为空,表明该属性值为null,与使用<null/>元素指定属性值的效果基本相同。如:
<value></value>


如果需要为Bean设置的属性值是容器中另一个Bean实例,则应该使用<ref.../>元素,使用<ref.../>元素时可以指定两个属性:
bean,引用不在同一份XML配置文件中的其他Bean实例的id属性值。
local,引用同一份XML配置文件中的其他Bean实例的id属性值。
如:
<property name="axe">
<ref local="steelAxe"/>
</property>
或:<property name="axe" ref="steelAxe"/>
当在配置文件中为BEan属性指定值时,还可以使用组合属性名的方式。如<property name="person.name" value="孙悟空"/>表示为exampleBean里提供的person属性的name属性指定值。


注入集合值
如果Bean的属性是集合,则可以使用集合元素<list../>,<set.../>,<map.../>和<props.../>元素分别用来设置类型为List,Set,Map和Properties的集合属性值。
例如:
<property name="schools">
//为List属性配置
<list>
<value>小学</value>
<value>中学</value>
<value>大学</value>
</list>
</property>
<property name="scores">
//为Map属性配置
<map>
<entry>
<key><value>数学</value></key>
<value>87</value>
</entry>
</property>
<property name="scores">
// 为Properties属性配置
<props>
<prop key="血压">正常</prop>
<prop key="身高">175</prop>
</props>
</property>


Spring对List属性和数组属性处理一个都用<list../>属性来配置。


当使用<list../>,<set../>等配置集合属性时,由于集合元素又可以是基本类型值,引用容器中其他Bean,嵌套Bean和集合属性等。所以<list../>,<key.../>和<set.../>元素又可接收如下子元素:
<value>...</value>,指定集合元素是基本数据类型值或字符串类型值。
<ref local="stoneAxe"/>,指定集合元素是容器另一个Bean实例。
<ref bean="Axe"/>指定集合中元素是一个嵌套Bean
list,set,map及props,指定集合元素值又是集合。


注入方法返回值
通过MethodInvokingFactoryBean工厂Bean,可将指定方法返回值注入成目标Bean的属性值,MethodInvokingFactoryBean用来获得指定方法的返回值,该方法可以值静态方法也可以是实例方法。获得的方法返回值既然可以被注入到指定Bean实例的指定属性,也可以直接定义成Bean实例。例如:
<bean id="son4" class="lee.son">
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<!--targetObject确定目标Bean,指定调用哪个Bean-->
<property name="targetObject" ref="valueGnerator"/>
<!--targetMethod确定目标方法,指定调用目标Bean的哪个方法-->
<property name="targetMethod" value="getValue"/>
</bean>
</property>
</bean>
</bean>
targetObject,确定目标Bean,该Bean可以是容器中已有的bean。
targetMethod,确定目标方法,确定获得目标Bean哪个方法的返回值。
如果需要获取静态方法的返回值,则无需指定targetObject,但需要指定class,通过targetClass属性指定。如:
<property name="targetClass" value="lee.ValueGenerator"/>
<property name="targetMethod" value="getStaticValue"/>


注入Field值
通过FieldRetrievingFactoryBean类,可以完成field值的注入。FieldRetrievingFactoryBean用来获得目标Bean的Field值。例如:
<bean id="theAge3" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<!--targetClass指定Field所在的目标类-->
<property name="targetClass" value="java.sql.Connection"/>
<!--targetField指定Field名-->
<property name="targetField" value="TRACSACTION_SERIALIZABLE"/>
targetClass或targetObject,分别用于指定Field值所在的目标类或目标对象,如果需要获得的Field是静态Field,则使用targetClass指定目标类。否则使用targetObject指定目标对象。
targetField,用于指定目标Field的Field名。


强制初始化Bean
为了指定Bean在目标Bean之前初始化,可以使用depends-on属性,该属性可以在初始化主调Bean之前,强制初始化一个或多个Bean。如:
<bean id="beanOne" class="ExampleBean" depends-on="manager">
...
</bean>


8自动装配
Spring能够自动装配Bean与Bean之间的依赖关系,即无须使用ref显式指定依赖Bean,由BeanFactory检查XML配置文件内容,根据某种规则,为主调Bean注入依赖关系。


Spring的自动装配使用autowire属性值指定,每个<bean../>元素都可指定autowire属性。使用autowire属性配置自动装配,autowire属性可以接受如下值:
no,不使用自动装配。Bean依赖必须通过ref元素定义。Autowire默认的值。。


byName,byName:通过属性的名字的方式查找JavaBean依赖的对象并为其注入。比如说类Computer有个属性printer,指定其 autowire属性为byName后,Spring IoC容器会在配置文件中查找id/name属性为printer的bean,然后使用Setter方法为其注入。如果有多个这样的Bean,就抛出一个异常。如果没有匹配的Bean,就自动注入这个属性,属性不会被设置。如果属性的类型是一个接口,可以把接口的实现类注入到这个属性。


byType:通过属性的类型查找JavaBean依赖的对象并为其注入。比如类Computer有个属性printer,类型为Printer,那么,指定其autowire属性为byType后,Spring IoC容器会查找Class属性为Printer的bean,使用Seter方法为其注入。如果有多个这样的Bean,就抛出一个异常。如果没有匹配的Bean,就自动注入这个属性,属性不会被设置。如果属性的类型是一个接口,可以把接口的实现类注入到这个属性。


constructor:通byType一样,也是通过类型查找依赖对象。与byType的区别在于它不是使用Seter方法注入,而是使用构造子注入。


autodetect:在byType和constructor之间自动的选择注入方式。


default:由上级标签<beans>的default-autowire属性确定。
在配置bean时,<bean>标签中Autowire属性的优先级比其上级标签高,即是说,如果在上级标签中定义default- autowire属性为byName,而在<bean>中定义为byType时,Spring IoC容器会优先使用<bean>标签的配置。


byName规则,byName规则是通过名字注入依赖关系,加入Bean A的实现类包含setB()方法,而Spring的配置文件恰好包含id为b的Bean,则Spring容器会将b实例注入Bean A中。如果没有匹配的Bean,Spring不会做任何事情。
byType规则,指根据类型匹配来注入依赖关系。假如A实例有setB(B b)方法,而Spring配置文件中恰好有一个类型B的Bean实例,容器为A注入类型匹配的Bean实例,如果容器中没有一个类型为B的实例,则什么都不会发生。但如果容器中有多于一个的B实例则会抛出异常。按类型自动装配,只要求setter方法的采纳数类型与容器中Bean类型相同,就会被自动装配。例如:
<bean id="chinese" class="lee.Chinese" autowire="byType"/>
<bean id="gundog" class="lee.Gundog">
<property name="name" value="wangwang"/>
</bean>
上面的chinese使用byType自动装配,其类中有一个setDog(Dog dog)方法,而Gundog的类实现类Dog接口,那么id="gundog"的bean将被自动装配到setDog方法中。


当一个Bean既使用了自动装配依赖,又使用了ref显式指定依赖时,显式指定的依赖覆盖自动装配。


某些情况下,我们不想让某些Bean作为自动装配的候选者,可以考虑使用autowire-candidate属性,通过为<bean../>元素设置autowire-candidate="false"即可将该Bean限制在自动装配之外。


除此之外还可以通过<beans/>元素中指定default-autowire-candidates属性来对一批Bean进行限制,将这批Bean排除在自动装配之外。default-autowire-candidates还允许使用模式字符串。如:default-autowire-candidates="*abc",表示所有以"abc"结尾的Bean都将被排除在自动装配之外。


<beans/>元素中的default-autowire属性来对其中的<bean>配置默认的自动转配方式。如:default-autowire="byType"。


9<p/>命名空间
sping3.x 开发者只需要配置文件增加,如下内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/p">
<p/>命名空间便可以启用了。那么如何使用呢。
没有使用<p/>命名空间之前,
<bean id="bankSecurityService" class="com.openv.spring3x.ioc.BankSecurityServiceImpl">
<property name="bankSecurityDao" ref="bankSecurityDao"/>
<property name="level" value="10"/>
</bean>
借助<p/>命名空间,我们做出等效配置:
<bean id="bankSecurityService" class="com.openv.Spring3x.ioc.BankSecurityServiceImpl" p:bankSecurityDao-ref="bankSecurityDao" p:level="10"/>
<p/>命名空间的使用使得配置文件变得简洁,p:*-ref用于引用其它受管Bean,*表示属性名,比如:bankSecurityDao,对于简单属性(数据类型),开发者直接启用属性名即可,即"p:*"。


************************
applicationContext.xml文件中使用import的方式导入有模块配置文件,例如:
    <import resource="server/applicationContext-cxf-eip.xml"/>
    <import resource="server/applicationContext-cxf-management.xml"/>
    <import resource="server/applicationContext-cxf-portal.xml"/>
    <import resource="server/applicationContext-cxf-enterprise.xml"/>




************************
Spring的IoC(Inversion of Control控制反转)和DI(Dependency Injection依赖注入)
DI依赖注入可以称为IoC控制反转,但是IoC不等于就是DI,就像堆和堆栈一样。IoC包括了DI,但是IoC还包括了另一个叫做DL(Dependency Lookup依赖查找),DL很少用到。DI只是IoC的一种实现方式。


IoC控制反转:在java开发中,IoC意味着将你设计好的类交给系统/容器去控制,而不是在你的类内部控制,这称为控制反转。


IoC的原理
不创建对象个,但是描述创建它们的方式,在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器负责将这些连接在一起。组件处在一个容器当中,所有的组件初始化和调用都由容器负责。




********************
spring mvc+hibernate中,应该将事务控制放到xxx-servlet.xml中,而不是在applicationContext.xml中,否则没有效。而且会在使用sessFactory的getCurrentSession()时报出异常。目前在spring3.0.3发现这个问题,在spring3.0.6和spring3.1没有发现






***********************
在web.xml文件中加入Spring的utf-8过滤器
类 org.springframework.web.filter.CharacterEncodingFilter是Spring特有的,专门解决Web 的中文utf-8乱码问题。
<filter>  
     //过滤器使用spring类CharacterEncodingFilter  
         <filter-name>encodingFilter</filter-name>  
         <filter-class>  
             org.springframework.web.filter.CharacterEncodingFilter  
         </filter-class>  
         <init-param>  
             <param-name>encoding</param-name>  
     //过滤器过滤后的编码为utf-8  
             <param-value>utf-8</param-value>  
         </init-param>  
</filter>  
     <filter-mapping>  
     //过滤所有的路径:/*代表所有的路径  
         <filter-name>encodingFilter</filter-name>  
         <url-pattern>/*</url-pattern>  
     </filter-mapping>
*******************


使用Spring @Async异步执行方法的笔记
首先在applicationContext.xml中加入如下配置:
  xmlns:task="http://www.springframework.org/schema/task" 


  http://www.springframework.org/schema/task 
  http://www.springframework.org/schema/task/spring-task-3.0.xsd 


  <!-- 启动定时器 --> 
    <task:annotation-driven/>      


然后在代码中进行注解,例如:
    @Service  
    public class DaoService {  
        @Async  
        public void update() {  
            try {  
                Thread.sleep(2000);  
                // do something  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println("operation complete.");  
        }  
    }  
@Async注解对于异步方法调用的支持(相当于EJB 3.1里的 @Asynchronous) 






********************
SPRING官方网站改版后,建议都是通过 Maven和Gradle下载,对不使用Maven和Gradle开发项目的,下载就非常麻烦,下给出Spring Framework jar官方直接下载路径:
http://repo.spring.io/libs-release-local/org/springframework/spring/




spring 4.0.x(Spring Framework 4.0.1)下载
http://repo.springsource.org/libs-release-local/org/springframework/spring/4.0.0.RELEASE/spring-framework-4.0.0.RELEASE-dist.zip
http://repo.springsource.org/libs-release-local/org/springframework/spring/4.0.1.RELEASE/spring-framework-4.0.1.RELEASE-dist.zip


spring 3.2.x(Spring Framework 3.2.7)下载:
http://repo.springsource.org/libs-release-local/org/springframework/spring/3.2.4.RELEASE/spring-framework-3.2.4.RELEASE-dist.zip
http://repo.springsource.org/libs-release-local/org/springframework/spring/3.2.5.RELEASE/spring-framework-3.2.5.RELEASE-dist.zip
http://repo.springsource.org/libs-release-local/org/springframework/spring/3.2.6.RELEASE/spring-framework-3.2.6.RELEASE-dist.zip
http://repo.springsource.org/libs-release-local/org/springframework/spring/3.2.7.RELEASE/spring-framework-3.2.7.RELEASE-dist.zip




*************************
在eclipse通过SVN导入spring源代码:
File -> import -> SVN


Spring SVN url
Spring3.0  源码     https://github.com/SpringSource/spring-framework


Spring2.5 及以下版本   https://src.springframework.org/svn/spring-maintenance/ 


Spring Security  https://src.springframework.org/svn/spring-security/ 


Struts2 SVN : http://svn.apache.org/repos/asf/struts/struts2/






我下载编译是的v3.2.4RESEASE,地址为:https://github.com/SpringSource/spring-framework/tags/v3.2.4.RELEASE;
如果有错误提示则选择忽略。


首先通过SVN下去GitHub站点中CheckOut spring framework的源码,https://github.com/spring-projects/spring-framework/releases
下载3.2.X其中的一个版本到本地; 


安装Gradle软件,官网下载,解压即可,设置GRADLE_HOME,和PATH。


命令行中运行gradle -v,检查一下是否正确安装配置;


在命令行中进入到之前checkout的spring目录下执行:
gradlew cleanEclipse :spring-oxm:compileTestJava eclipse -x :eclipse
此命令执行需要很久,有时候会出错中断,我们继续执行这个命令,它会接着运行;有时候实在执行不下去还需要重启电脑再执行这个命令直到运行成功。




另外最好修改build.gradle如下:
用写字板打开build.gradle,定位到如下位置


testCompile("javax.inject:javax.inject-tck:1")这句话后面加上如下内容:


testCompile("commons-pool:commons-pool:1.5.3")


重新编译即可。




如果出现spring-webmvc-tiles3不能构建,爆出如下错误:
Execution failed for task ':spring-webmvc-tiles3:eclipseClasspath'.  
> Could not resolve all dependencies for configuration 'detachedConfiguration2'.  
  
则在build.gradle中找到
project("spring-webmvc-tiles3") { 
.....
}
在其中加入:
 eclipseClasspath {  
            downloadSources = false; // required for eclipseClasspath to work  
    }  


那么spring-webmvc-tiles3项目的脚本、就变成这样了:
project("spring-webmvc-tiles3") {  
    description = "Spring Framework Tiles3 Integration"  
    merge.into = project(":spring-webmvc")  
    dependencies {  
        provided(project(":spring-context"))  
        provided(project(":spring-web"))  
        provided("javax.el:el-api:1.0")  
        provided("javax.servlet:jstl:1.2")  
        provided("javax.servlet.jsp:jsp-api:2.1")  
        optional("org.apache.tiles:tiles-request-api:1.0.1")  
        optional("org.apache.tiles:tiles-api:3.0.1")  
        optional("org.apache.tiles:tiles-core:3.0.1") {  
            exclude group: "org.slf4j", module: "jcl-over-slf4j"  
        }  
        optional("org.apache.tiles:tiles-servlet:3.0.1") {  
            exclude group: "org.slf4j", module: "jcl-over-slf4j"  
        }  
        optional("org.apache.tiles:tiles-jsp:3.0.1") {  
            exclude group: "org.slf4j", module: "jcl-over-slf4j"  
        }  
        optional("org.apache.tiles:tiles-extras:3.0.1") {  
            exclude group: "org.slf4j", module: "jcl-over-slf4j"  
        }  
        optional("org.apache.tiles:tiles-el:3.0.1") {  
            exclude group: "org.slf4j", module: "jcl-over-slf4j"  
        }  
        provided("javax.servlet:javax.servlet-api:3.0.1")  
        testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")  
    }  
    eclipseClasspath {  
            downloadSources = false; // required for eclipseClasspath to work  
    }  
}


构建成功后,在eclipse中导入时选择这个spring目录目录就可以了,这样就完成spring源码导入到eclipse中。


***********************************




<mvc:default-servlet-handler/>  
这样spring会用默认的Servlet来响应静态文件,(DefaultServletHttpRequestHandler在容器启动是会使用主流web容器默认servlet的名称列表自动查找容器的默认servlet,包括Tomcat, Jetty, Glassfish, JBoss, Resin, WebLogic, and WebSphere。),如果为默认servlet配置了新的名称,或者这个容器servlet名字不在spring列表中是,必须显式配置默认servlet的名字




<mvc:annotation-driven/>标签作用
<mvc:annotation-driven/>标签,所以我们找到对应的实现类是org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser。
通过阅读类注释文档,我们发现这个类主要是用来向工厂中注册了下面这些类:
RequestMappingHandlerMapping 


BeanNameUrlHandlerMapping


RequestMappingHandlerAdapter


HttpRequestHandlerAdapter


SimpleControllerHandlerAdapter


ExceptionHandlerExceptionResolver 


ResponseStatusExceptionResolver 


DefaultHandlerExceptionResolver 


上面几个Bean实例。这几个类都是用来做什么的呢?
    前两个是HandlerMapping接口的实现类,用来处理请求映射的。其中第一个是处理@RequestMapping注解的。第二个会将controller类的名字映射为请求url。


    中间三个是用来处理请求的。具体点说就是确定调用哪个controller的哪个方法来处理当前请求。第一个处理@Controller注解的处理器,支持自定义方法参数和返回值(很酷)。第二个是处理继承HttpRequestHandler的处理器。第三个处理继承自Controller接口的处理器。


    后面三个是用来处理异常的解析器。




我们主要说明里面的两个,RequestMappingHandlerMapping和RequestMappingHandlerAdapter。


    第一个是HandlerMapping的实现类(),它会处理@RequestMapping 注解,并将其注册到请求映射表中。


    第二个是HandlerAdapter的实现类,它是处理请求的适配器,说白了,就是确定调用哪个类的哪个方法,并且构造方法参数,返回值。


    那么它跟<context:component-scan/>有什么区别呢?<context:component-scan/>标签是告诉Spring 来扫描指定包下的类,并注册被@Component,@Controller,@Service,@Repository等注解标记的组件。
而<mvc:annotation-scan/>是告知Spring,我们启用注解驱动。然后Spring会自动为我们注册上面说到的几个Bean到工厂中,来处理我们的请求。


在spring mvc 3.1中,对应变更为 
DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping 
AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter 
AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver 




**********************
Spring整合JPA改进办法
在标准JPA中,持久化单元默认被定义在META-INF/persistence.xml文件中,并且通过@Entity注解搜索获得,自动查找带有@Entity注释的类,不需要再配置文件指定要搜索的目录。但是大多数情况下,持久化单元不会仅仅存在一个,并且数据源也不会是一个,基于这个原因,Spring提供了一个可选方案,即通过LocalEntityManagerFactoryBean和LocalContainerEntityManagerFactoryBean进行管理。localEntityManagerFactoryBean扩展功能太少,这个不说,我们以LocalContainerEntityManagerFactoryBean为例。


创建persistence.xml文件
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="hibernatePersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
        </properties>
    </persistence-unit>
</persistence>


然后再在Spring的applicationContext.xml中引入persistence.xml配置文件并使用LocalContainerEntityManagerFactoryBean
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:persistenceXmlLocation="classpath*:META-INF/persistence.xml"
    p:persistenceUnitName="hibernatePersistenceUnit"
    p:dataSource-ref="jpaDataSource"(如果数据源在persistence.xml中已经配置则不需要这条)
    p:jpaVendorAdapter-ref="hibernateVendor"/>
  
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
    p:showSql="false"/>


我们希望移除persistence.xml文件,但是如何声明外部配置文件从而关联到ORM框架中呢?并且如何让EntityManagerFactory工厂类知道我们当初配置的实体在哪里。
可以这样:
删除META-INF/persistence.xml配置文件
声明packagesToScan属性
声明jpaPropertyMap属性

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:packagesToScan="org.krams.tutorial.domain"
    p:dataSource-ref="jpaDataSource"
    p:jpaVendorAdapter-ref="hibernateVendor"
    p:jpaPropertyMap-ref="jpaPropertyMap"/>
  
<util:map id="jpaPropertyMap">
    <entry key="hibernate.hbm2ddl.auto" value="update"/>
    <entry key="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
</util:map>
  
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
    p:showSql="false"/>
这样做的好处是,当真正需要调用相应的实体对象时再进行数据源的配置,让Spring自动管理工厂,这样即使数据源再多也不怕出现冲突。




LocalEntityManagerFactoryBean
LocalEntityManagerFactoryBean负责创建一个适合于仅使用JPA进行数据访问的环境的 EntityManager。 Factory bean将使用JPA PersistenceProvider 类的自动检测机制(根据JPA的 Java SE启动),而在绝大多数情况下,只需要指定persistence unit名称.
这种JPA部署方式最为简单,但却最受限制。例如,不能连接到现有的JDBCDataSource,并且不支持全局事务。甚至,持久化类的织入(字节码转换)也是特定于提供者的,经常需要在启动时指定一个特定的JVM代理。总之,这种方法实际上只适用于独立的应用程序和测试环境


LocalContainerEntityManagerFactoryBean
LocalContainerEntityManagerFactoryBean 提供了对JPA EntityManagerFactory 的全面控制,非常适合那种需要细粒度定制的环境。LocalContainerEntityManagerFactoryBean 将基于 persistence.xml 文件创建 PersistenceUnitInfo 类,并提供dataSourceLookup 策略和 loadTimeWeaver。 因此它可以在JNDI之外的用户定义的数据源之上工作,并控制织入流程。






*************************
spring 循环依赖错误
consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off
出现这种错误表示A类中依赖了B类,而B类中有依赖了A类,陷入了一个循环。







0 0