您的位置:首页 > 编程语言 > Java开发

Spring IoC 控制反转 DI依赖注入 以及常用注解

2015-08-13 20:10 716 查看
注解实现Bean配置主要用来进行如依赖注入、生命周期回调方法定义等,不能消除XML文件中的Bean元数据定义,且基于XML配置中的依赖注入的数据将覆盖基于注解配置中的依赖注入的数据。

生命周期中如果bean实现了InitializingBean,
DisposableBean或者是在配置文件中设置了init-method和destroy-method,那么就不会执行default-init-method和default-destroy-method。



注册注解处理器

• 方式一:bean

[html] view
plaincopy

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

• 方式二: 命名空间<context:annotation-config />

<context:annotationconfig /> 将隐式地向Spring 容器注册AutowiredAnnotationBeanPostProcessor 、CommonAnnotationBeanPostProcessor 、 PersistenceAnnotationBeanPostProcessor 以及RequiredAnnotationBeanPostProcessor 这4 个BeanPostProcessor 。

• 方式三: 命名空间<context:component-scan />

如果要使注解工作,则必须配置component-scan ,实际上不需要再配置annotation-config。

[html] view
plaincopy

<context:component-scan base-package="com.spring.ioc5">

<!-- annotation 通过注解来过滤 org.example.SomeAnnotation

assignable 通过指定名称过滤 org.example.SomeClass

regex 通过正则表达式过滤 org\.example\.Default.*

aspectj 通过aspectj表达式过滤 org.example..*Service+

-->

<context:include-filter type="regex" expression="com.spring.ioc5.*"/>

<context:exclude-filter type="annotation" expression="org.springframework.beans.factory.annotation.Autowired"/>

</context:component-scan>

• Spring 支持以下4 种类型的过滤方式:

• 注解 org.example.SomeAnnotation 将所有使用SomeAnnotation 注解的类过滤出来

• 类名指定 org.example.SomeClass 过滤指定的类

• 正则表达式 com.kedacom.spring.annotation.web..* 通过正则表达式过滤一些类

• AspectJ 表达式 org.example..*Service+ 通过AspectJ 表达式过滤一些类

Spring3的基于注解实现Bean依赖注入支持如下三种注解:

Spring自带依赖注入注解: Spring自带的一套依赖注入注解;
JSR-250注解:Java平台的公共注解,是Java EE 5规范之一,在JDK6中默认包含这些注解,从Spring2.5开始支持。
JSR-330注解:Java 依赖注入标准,Java EE 6规范之一,可能在加入到未来JDK版本,从Spring3开始支持;
JPA注解:用于注入持久化上下文和尸体管理器。

@Required

• 例如

[java] view
plaincopy

@required

public setName(String name){}

@ required 负责检查一个bean在初始化时其声明的 set方法是否被执行, 当某个被标注了
@Required 的 Setter 方法没有被调用,则 Spring 在解析的时候会抛出异常,以提醒开发者对相应属性进行设置。 @Required 注解只能标注在 Setter 方法之上。因为依赖注入的本质是检查 Setter 方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非 setXxxx() 类型的方法则被忽略。

@Autowired 采用byType的方式

[java] view
plaincopy

@Autowired

private ISoftPMService softPMService;

[java] view
plaincopy

@Autowired(required=false)

构造器、字段、方法

@Autowired 根据bean 类型从spring 上线文中进行查找,注册类型必须唯一,否则报异常。与@Resource 的区别在于,@Resource 允许通过bean 名称或bean 类型两种方式进行查找@Autowired(required=false) 表示,如果spring 上下文中没有找到该类型的bean 时,
才会使用new SoftPMServiceImpl();

@Autowired 标注作用于 Map 类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key。

@Autowired 还有一个作用就是,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作。

@Qualifier 和@AutoWired联合使用,自动装配的策略就变成byName

使用@Autowired 时,如果找到多个同一类型的bean,则会抛异常,此时可以使用 @Qualifier("beanName"),明确指定bean的名称进行注入,此时与 @Resource指定name属性作用相同。

[java] view
plaincopy

@Qualifier(value = "限定标识符")

字段、方法、参数

(1)根据基于XML配置中的<qualifier>标签指定的名字进行注入,使用如下方式指定名称

[java] view
plaincopy

<qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="限定标识符"/>

其中type属性可选,指定类型,默认就是Qualifier注解类,name就是给Bean候选者指定限定标识符,一个Bean定义中只允许指定类型不同的<qualifier>,如果有多个相同type后面指定的将覆盖前面的。

[java] view
plaincopy

@Autowired

//根据<qualifier>标签指定Bean限定标识符

public void initDataSource(@Qualifier("mysqlDataSource") DataSource dataSource) {

this.dataSource = dataSource;

}

使用@Qualifier("mysqlDataSource")来指定候选Bean的限定标识符,我们需要在配置文件中使用<qualifier>标签来指定候选Bean的限定标识符“mysqlDataSource”:

<bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<qualifier value="mysqlDataSource"/>

</bean>

(2)缺省的根据Bean名字注入:最基本方式,是在Bean上没有指定<qualifier>标签时一种容错机制,即缺省情况下使用Bean标识符注入,但如果你指定了<qualifier>标签将不会发生容错。

@Autowired

@Qualifier(value = "mysqlDataSource2") //指定Bean限定标识符

//@Qualifier(value = "mysqlDataSourceBean")

//是错误的注入,不会发生回退容错,因为你指定了<qualifier>

public void initDataSource(DataSource dataSource) {

this.dataSource = dataSource;

}

(3)扩展@Qualifier限定描述符注解:对@Qualifier的扩展来提供细粒度选择候选者;

如果我们有两个数据源,分别为Mysql和Oracle,因此注入两者相关资源时就牵扯到数据库相关,如在DAO层注入SessionFactory时,当然可以采用前边介绍的方式,但为了简单和直观我们希望采用自定义注解方式。

1. 扩展@Qualifier限定描述符注解来分别表示Mysql和Oracle数据源

/** 表示注入Mysql相关 */

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @interface Mysql {

}

/** 表示注入Oracle相关 */

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @interface Oracle {

}

@Autowired

public void initDataSource(@Mysql DataSource mysqlDataSource, @Oracle DataSource oracleDataSource) {

this.mysqlDataSource = mysqlDataSource;

this.oracleDataSource = oracleDataSource;

}

<bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<qualifier value="mysqlDataSource"/>

<qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>

</bean>

<bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<qualifier type="cn.javass.spring.chapter12.qualifier.Oracle"/>

</bean>

Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), testBean33.getMysqlDataSoruce());

Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean33.getOracleDataSoruce());

测试通过

2.扩展参数的注解

package cn.javass.spring.chapter12.qualifier;

public enum DataBase {

ORACLE, MYSQL;

}

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @interface DataSourceType {

String ip(); //指定ip,用于多数据源情况

DataBase database();//指定数据库类型

}

@Autowired

public void initDataSource(

@DataSourceType(ip="localhost", database=DataBase.MYSQL)

DataSource mysqlDataSource,

@DataSourceType(ip="localhost", database=DataBase.ORACLE)

DataSource oracleDataSource) {

this.mysqlDataSource = mysqlDataSource;

this.oracleDataSource = oracleDataSource;

}

<bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<qualifier value="mysqlDataSource"/>

<qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>

<qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">

<attribute key="ip" value="localhost"/>

<attribute key="database" value="MYSQL"/>

</qualifier>

</bean>

<bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<qualifier type="cn.javass.spring.chapter12.qualifier.Oracle"/>

<qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">

<attribute key="ip" value="localhost"/>

<attribute key="database" value="ORACLE"/>

</qualifier>

</bean>

Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), testBean34.getMysqlDataSource());

Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean34.getOracleDataSoruce());

测试通过

3. 自定义注解限定描述符:完全不使用@Qualifier,而是自己定义一个独立的限定注解;

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

public @interface CustomQualifier {

String value();

}

@Autowired

public TestBean35(@CustomQualifier("oracleDataSource") DataSource dataSource) {

this.dataSoruce = dataSource;

}

在Spring配置文件中注册CustomQualifier自定义注解限定描述符,只有注册了Spring才能识别:

<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">

<property name="customQualifierTypes">

<set>

<value>cn.javass.spring.chapter12.qualifier.CustomQualifier</value>

</set>

</property>

</bean>

@Value

用于注入SpEL表达式,可以放置在字段方法或参数上

[java] view
plaincopy

@Value(value = "SpEL表达式")

字段、方法、参数

1、可以在类字段上使用该注解:

@Value(value = "#{message}")

private String message;

2、可以放置在带@Autowired注解的方法的参数上

@Autowired

public void initMessage(@Value(value = "#{message}#{message}") String message) {

this.message = message;

}

3、还可以放置在带@Autowired注解的构造器的参数上:

@Autowired

private TestBean43(@Value(value = "#{message}#{message}") String message) {

this.message = message;

}



@Lazy

定义Bean将延迟初始化

@Component("component")

@Lazy(true)

public class TestCompoment {

……

}

@DependsOn

定义Bean初始化及销毁时的顺序

@Component("component")

@DependsOn({"managedBean"})

public class TestCompoment {

……

}

@Scope

定义Bean作用域,默认单例

@Component("component")

@Scope("singleton")

public class TestCompoment {

……

}

@Primary

自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常

@Component("component")

@Primary

public class TestCompoment {

……

}

@Configuration

注解需要作为配置的类,表示该类将定义Bean配置元数据,@Configuration注解的类本身也是一个Bean,因为@Configuration被@Component注解了,因此@Configuration注解可以指定value属性值,如“ctxConfig”就是该Bean的名字,如使用“ctx.getBean("ctxConfig")”将返回该Bean。

[java] view
plaincopy

<span style="font-size:10px;font-weight: normal;">@Configuration("ctxConfig")

public class ApplicationContextConfig {

//定义Bean配置元数据

} </span>

@Bean

注解配置类中的相应方法,则该方法名默认就是Bean名,该方法返回值就是Bean对象,并定义了Spring IoC容器如何实例化、自动装配、初始化Bean逻辑

[java] view
plaincopy

<span style="font-weight: normal;"><span style="font-size:10px;">@Bean(name={},

autowire=Autowire.NO,

initMethod="",

destroyMethod="") </span></span>

name:指定Bean的名字,可有多个,第一个作为Id,其他作为别名;
autowire:自动装配,默认no表示不自动装配该Bean,另外还有Autowire.BY_NAME表示根据名字自动装配,Autowire.BY_TYPE表示根据类型自动装配;
initMethod和destroyMethod:指定Bean的初始化和销毁方法。


@Import

类似于基于XML配置中的<import/>,基于Java的配置方式提供了@Import来组合模块化的配置类

[java] view
plaincopy

<span style="font-size:10px;font-weight: normal;">@Configuration("ctxConfig2")

@Import({ApplicationContextConfig.class})

public class ApplicationContextConfig2 {

@Bean(name = {"message2"})

public String message() {

return "hello";

}

} </span>

JSR-250注解@Resource


自动装配,默认根据类型装配,如果指定name属性将根据名字装配,可以使用如下方式来指定

[java] view
plaincopy

<span style="white-space:pre"> </span>@Resource(name = "message")

<span style="white-space:pre"> </span>private String message;

@Resource注解应该只用于setter方法注入,不能提供如@Autowired多参数方法注入;
@Resource在没有指定name属性的情况下首先将根据setter方法对于的字段名查找资源,如果找不到再根据类型查找;
@Resource首先将从JNDI环境中查找资源,如果没找到默认再到Spring容器中查找,因此如果JNDI环境中有和Spring容器同名的资源时需要注意。

@PostConstruct和PreDestroy:通过注解指定初始化和销毁方法定义;

类似于通过<bean>标签的init-method和destroy-method属性指定的初始化和销毁方法,但具有更高优先级,即注解方式的初始化和销毁方法将先执行。

• @PostConstruct在方法上加上注解@PostConstruct ,这个方法就会在Bean 初始化之后被Spring 容器执行(注:Bean 初始化包括,实例化Bean ,并装配Bean 的属性(依赖注入))。

• @PreDestroy 在方法上加上注解@PreDestroy ,这个方法就会在Bean 被销毁前被Spring 容器执行。


JSR-330注解

@Inject:等价于默认的@Autowired,只是没有required属性;

@Named:指定Bean名字,对应于Spring自带@Qualifier中的缺省的根据Bean名字注入情况;

@Qualifier:只对应于Spring自带@Qualifier中的扩展@Qualifier限定描述符注解,即只能扩展使用,没有value属性。

学习过Spring框架的人一定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring Ioc的理解。


一、分享Iteye的开涛对Ioc的精彩讲解

  首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846


1.1、IoC是什么

  Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  ●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  ●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

  用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:



图1-1 传统应用程序示意图

  当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:

图1-2有IoC/DI容器后程序结构示意图


1.2、IoC能做什么

  IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

  其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

  IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。


1.3、IoC和DI

  DI—Dependency Injection,即“依赖注入”组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

  理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  ●谁依赖于谁:当然是应用程序依赖于IoC容器

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

  IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin
Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

  看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。


二、分享Bromon的blog上对IoC与DI浅显易懂的讲解


2.1、IoC(控制反转)

  首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

  那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。


2.2、DI(依赖注入)

  IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java
1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

  理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。


三、我对IoC(控制反转)和DI(依赖注入)的理解

  在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。

  所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了
IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。

  这是我对Spring的IoC(控制反转)的理解。DI(依赖注入)其实就是IOC的另外一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式反转了。


四、小结

  对于Spring Ioc这个核心概念,我相信每一个学习Spring的人都会有自己的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。如果有理解不到位或者理解错的地方,欢迎广大园友指正! http://blog.csdn.net/liumm0000/article/details/7565027 http://www.cnblogs.com/xdp-gacl/p/4249939.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: