Spring逐点击破(一)—依赖(Bean)注入的几种方式(全)
最近在系统的学习Spring相关的知识点,准备在整理出一个Spring的知识架构体系,这是第一篇。
一、XML注入Bean
这种方式,相信绝大多数使用过Spring的朋友都轻车熟路。
- 首先创建一个我们需要注入到IOC容器的Bean测试类
package com.trinclesdar.beans; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Person { private String name; private Integer age; public Person(){ super(); } @Override public String toString() { return "Person \n [name=" + name + ", age=" + age + "]"; } }
- 在resources目录下创建beans.xml,通常在项目中,这个文件我们通常命名为applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="person" class="com.trinclesdar.bean.Person"> <property name="name" value="trinclesdar"/> <property name="age" value="18"/> </bean> </beans>
- 测试
package com.trinclesdar.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class XMLBeanInjectTest { public static void main(String args[]){ //把beans.xml的类加载到容器 ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml"); //从容器中获取bean Person person = (Person) app.getBean("person"); System.out.println(person); } }
- 结果
二、Spring注解注入Bean(@Configuration)
1. @Bean
- 首先,同样创建一个项目需要使用到的Bean类:
package com.trinclesdar.study.spring.bean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 定义需要注入到IOC容器的bean * * @author trinclesdar * @date 2018/11/4 17:22 */ @Data @AllArgsConstructor @NoArgsConstructor public class TestBean1 { private String property1; private Integer property2; }
- 通过@Bean导入组件
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.TestBean1; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; /** * bean注入IOC容器 * * @author trinclesdar * @date 2018/11/4 17:13 */ @Configuration public class BeanInjectByAnno { @Bean("testBean1") public TestBean1 injectBean() { return new TestBean1("testBean1", 1); } }
@Configuration注解的作用是声明该类是一个配置类,其效果等同于我们声明的applicationContext.xml文件,@Bean的作用是在Spring容器启动时,告诉容器这是一个SpringBean,将会注入到Spring的IOC容器,效果等同于在applicationContext.xml中声明的bean标签。
- 测试
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.TestBean1; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; /** * bean注入IOC容器 * * @author trinclesdar * @date 2018/11/4 17:13 */ @Configuration }, useDefaultFilters = false) public class BeanInjectByAnno { /** * 指定单例or多例模式 * 单实例的bean,在容器关闭时,会调用bean的destroy()方法销毁bean实例, * 而多实例的bean,容器只负责在调用bean的时候生成bean,但容器不会管理bean的生命周期, * 在容器关闭时,也不会调用destroy()方法销毁bean * * @return TestBean1 */ @Scope("prototype") @Bean("testBean1") // @Lazy public TestBean1 injectBean() { System.out.println("初始化容器标记"); return new TestBean1("testBean1", 1); } }
@Bean(“testBean1”)注解中的"testBean1",用于指定注入Bean的对象名,如果不配置,则Spring容器默认将方法名,即上述代码中的"injectBean",作为对象名,注入到容器中。@Bean通常用于导入第三方组件,如我们项目中数据库相关的Bean、各种模板组件等等。
- @Scope注解有两个属性,"prototype"和"singleton",用于指定该Bean在注入IOC容器时,是以单实例模式("singleton"),还是多实例模式("prototype")注入。
- 多实例:IOC容器启动时,并不会去调用初始化方法创建对象,而是每次获取的时候才会调用方法创建对象;
- 单实例(默认):IOC容器启动时,会调用初始化方法创建对象,并将对象放到IOC容器的"Big Map"中,以后每需要获取bean就直接从容器获取,不重复初始化。
如果不配置该注解,则Spring默认以单实例(“singleton”)模式,创建该Bean对象。
- @Lazy,该注解主要用于@Scope("singleton"),即单实例Bean下,当我们加上该注解时,Spring容器在启动过程中,并不会去创建和初始化对应的Bean,而是当且仅当我们调用了anno.getBean()方法,即当我们第一次需要使用(获取)Bean的时候,才会去创建和初始化对应的Bean。
- @Condition 在某些项目场景下,我们需要根据不同的条件,往容器中注入不同类型的Bean实例,比如,在Windows操作系统下,和Linux操作系统下,我们需要根据操作类型的不同,注入不同类型的Bean,这时候,我们就可以使用这个注解来实现。
新建两个实现org.springframework.context.annotation.Condition接口的类:
package com.enjoy.cap5.config; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; /** * 注入Bean的条件 * * @author trinclesdar * @date 2018/11/6 22:25 */ public class WinCondition implements Condition { /** * ConditionContext: 判断条件可以使用的上下文(环境) * AnnotatedTypeMetadata: 注解的信息 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 是否为WINDOW系统 //能获取到IOC容器正在使用的beanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //获取当前环境变量(包括我们操作系统是WIN还是LINUX??) Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); return osName.contains("Windows"); } }
package com.enjoy.cap5.config; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; /** * 注入Bean的条件 * * @author trinclesdar * @date 2018/11/6 22:26 */ public class LinCondition implements Condition { /** * ConditionContext: 判断条件可以使用的上下文(环境) * AnnotatedTypeMetadata: 注解的信息 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 是否为Linux系统 //能获取到IOC容器正在使用的beanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //获取当前环境变量(包括我们操作系统是WIN还是LINUX??) Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); return osName.contains("linux"); } }
新建配置类:
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.Person; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * 根据不同操作系统,注入不同类型bean * * @author trinclesdar * @date 2018/11/6 22:30 */ @Configuration public class BeanInjectByCondition { @Bean("person") public Person person() { System.out.println("给容器中添加person......."); return new Person("person", 20); } @Conditional(WinCondition.class) @Bean("trinclesdar") public Person trinclesdar() { System.out.println("给容器中添加trinclesdar......."); return new Person("trinclesdar", 18); } @Conditional(LinCondition.class) //bean在容器中的ID为jayChou, IOC容器MAP, map.put("id",value) @Bean("jayChou") public Person jayChou() { System.out.println("给容器中添加周杰伦......."); return new Person("周杰伦", 100); } }
如上,如果操作系统为Windows,则向容器注入一个trinclesdar的实例对象,如果系统为Linux,则为容器注入一个周杰伦对象。
测试
@Test public void test01(){ AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap5MainConfig.class); System.out.println("IOC容器创建完成........"); }
我当前操作系统是Windows,因此为Spring容器注入的Bean实例为trinclesdar。
2. @Component
我们常用的@Controller、@Service等注解,都是默认实现了@Component,该注解用于声明某个类可以作为Spring Bean,在容器初始化过程中,被扫描和自动注入至容器,供其它类调用,其用法与@Controller和@Service相似。
package org.springframework.stereotype; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.stereotype.Component; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { String value() default ""; }
3. @ComponentScan
新建几个我们熟悉的测试包和类,也就是我们项目中常用的controller、service…
package com.trinclesdar.study.spring.bean.controller; import com.trinclesdar.study.spring.bean.service.TestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller; /** * 测试Controller * * @author trinclesdar * @date 2018/11/4 18:00 */ @Controller public class TestController { @Autowired @Qualifier("service") private TestService testService; }
package com.trinclesdar.study.spring.beaninject; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; package com.trinclesdar.study.spring.bean.service; import org.springframework.stereotype.Service; /** * 测试Service * * @author trinclesdar * @date 2018/11/6 21:49 */ @Service public class TestService { } </code></pre> 通过@ComponentScan注解声明Bean扫描范围 <pre><code class="language-java line-numbers">/** * 通过@ComponentScan指定扫描范围 * 注:@ComponentScan注解会扫描指定范围的包下所有声明了@Component注解的Bean,@Controller、@Service等注解,其实都是引入了@Component注解的 * * @author LiaoDebin * @date 2018/11/4 17:58 */ @Configuration @ComponentScan(value = "com.trinclesdar.study.spring") public class BeanInjectByComponentScan { }
测试
@Test public void beanInjectByComponentScan() { // 声明容器,将BeanInjectByComponentScan类的配置信息加载至容器中 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInjectByComponentScan.class); // 获取容器中所有的bean名称 String[] beanNames = applicationContext.getBeanDefinitionNames(); System.out.println("beanNames:"); for (String beanName : beanNames) { System.out.println(beanName); } }可以看到,在容器初始化之后,已经将指定范围下("com.trinclesdar.study.spring")下的所有Bean注入到容器中,也包括了我们新加入的Controller和Service组件对象。
@ComponentScan用于指定在Spring容器启动时,扫描容器的包范围,默认只需要指定value的属性值即可,同时可以配合includeFilters和excludeFilters属性,进一步过滤容器扫描Bean的范围。
package com.trinclesdar.study.spring.beaninject; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; /** * 通过@ComponentScan指定扫描范围 * 注:@ComponentScan注解会扫描指定范围的包下所有声明了@Component注解的Bean,@Controller、@Service等注解,其实都是引入了@Component注解的 * * @author trinclesdar * @date 2018/11/4 17:58 */ @Configuration @ComponentScan(value = "com.trinclesdar.study.spring", includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {TestTypeFilter.class}) }, useDefaultFilters = false) public class BeanInjectByComponentScan { }
package com.trinclesdar.study.spring.beaninject; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; import java.io.IOException; /** * 包扫描过滤器 * * @author trinclesdar * @date 2018/11/4 17:13 */ public class TestTypeFilter implements TypeFilter { /** * MetadataReader:读取到当前正在扫描类的信息 * MetadataReaderFactory:可以获取到其他任何类信息 */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径) Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println("----->" + className); //当类包含er字符, 则匹配成功,返回true return className.contains("controller"); } }
通过以上代码,就可以在Spring容器启动时,将指定范围下,所有Controller组件注入到容器中。
- @ComponentScan.Filter:声明过滤器类型,type取值有5种类型:
ANNOTATION:通过注解过滤,如Controller.class、Service.class、Respository.class等;
ASSIGNABLE_TYPE:按照给定的类型过滤;比如TestService类型;
ASPECTJ:使用ASPECTJ表达式;
REGEX:通过正则过滤;
CUSTOM:自定义过滤器,示例代码中,使用的就是这种方式,使用该方式,需要自定义一个实现TypeFilter接口的过滤器类。
- useDefaultFilters:该属性表示,是否扫描value指定路径下所有的Bean,该属性默认为true,如果要使用自定义过滤规则,该值必须指定为false,否则过滤器不会生效。
使用场景:@ComponentScan使用与自定义组件(Bean)。
4. @Import
@Import适用于快速给容器导入一个组件,使用方便,但是其弊端在于,引入至容器中的是一个全类名。
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.TestBean1; import org.springframework.context.annotation.Import; /** * 通过Import注入bean至IOC容器 * * @author trinclesdar * @date 2018/11/4 18:23 */ @Import(value = {TestBean1.class}) public class BeanInjectByImport { }
@Test public void beanInjectByImport() { // 声明容器,将BeanInjectByComponentScan类的配置信息加载至容器中 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInjectByImport.class); // 获取容器中所有的bean名称 String[] beanNames = applicationContext.getBeanDefinitionNames(); System.out.println("beanNames:"); for (String beanName : beanNames) { System.out.println(beanName); } }
可以看到,注入到容器中的Bean,是一个类型为com.trinclesdar.study.spring.bean.TestBean1的实例。使用@Import注解,可以不用声明@Configuration注解。容器便会在初始化时,自动将@Import后声明的Bean,注入到容器。
- ImportSelector
声明两个空的Bean类TestBean2、TestBean3
package com.trinclesdar.study.spring.bean; /** *TestBean2 * * @author trinclesdar * @date 2018/11/6 22:17 */ public class TestBean2 { }
package com.trinclesdar.study.spring.bean; /** *TestBean3 * * @author trinclesdar * @date 2018/11/6 22:18 */ public class TestBean3 { }
新建一个BeanInjectByImportSelector类,实现org.springframework.context.annotation.ImportSelector接口,并实现selectImports()方法,该方法返回一个字符数组,数组中放的是我们需要注入至容器的Bean实例的全路径名。
package com.trinclesdar.study.spring.beaninject; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; /** * 通过ImportSelector引入bean * * @author trinclesdar * @date 2018/11/6 22:13 */ public class BeanInjectByImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.trinclesdar.study.spring.bean.TestBean2", "com.trinclesdar.study.spring.bean.TestBean3"}; } }
将BeanInjectByImportSelector.clas追加至@Import的value属性中,需要注意的是,value属性可以是一个字符串,也可以是一个数组,为数组时,可以声明多个组件,同时也不仅限于我们直接声明的Bean类,也支持ImportSelector和ImportBeanDefinitionRegistrar类型的组件。
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.TestBean1; import org.springframework.context.annotation.Import; /** * 通过Import注入bean至IOC容器 * * @author trinclesdar * @date 2018/11/4 18:23 */ @Import(value = {TestBean1.class, BeanInjectByImportSelector.class}) public class BeanInjectByImport { }
创建测试方法
@Test public void beanInjectByImport() { // 声明容器,将BeanInjectByComponentScan类的配置信息加载至容器中 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInjectByImport.class); // 获取容器中所有的bean名称 String[] beanNames = applicationContext.getBeanDefinitionNames(); System.out.println("beanNames:"); for (String beanName : beanNames) { System.out.println(beanName); } }
运行测试用例,通过结果可以看出,我们想要注入容器的TestBean2、TestBean3已经成功注入到容器中。
- FactoryBean
新建一个TestBean5,作为测试Bean
package com.trinclesdar.study.spring.bean; /** * TestBean5 * * @author LiaoDebin * @date 2018/11/6 22:25 */ public class TestBean5 { }
声明一个实现FactoryBean接口的TestFactoryBean类,并实现对应接口方法, getObject()、 getObjectType()、isSingleton(),方法的具体含义和目的,可以参见代码中的注释。
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.TestBean5; import org.springframework.beans.factory.FactoryBean; /** * 通过FactoryBean注入bean * * @author trinclesdar * @date 2018/11/6 22:30 */ public class TestFactoryBean implements FactoryBean { /** * 声明注入至容器的Bean实例对象 * * @return TestBean5 * @throws Exception 抛出异常 */ @Override public TestBean5 getObject() throws Exception { return new TestBean5(); } /** * 声明注入容器的Bean类型 * * @return TestBean5 */ @Override public Class getObjectType() { return TestBean5.class; } /** * 声明是否以单例模式注入容器 * * @return boolean */ @Override public boolean isSingleton() { return false; } }
创建测试用例:
@Test public void beanInjectByFactoryBean() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInjectByFactoryBean.class); Object testBean = applicationContext.getBean("testFactoryBean"); System.out.println("testBean Type:" + testBean.getClass()); applicationContext.close(); }
可以看到,类型为TestBean5的Bean实例已经成功注入容器。
- ImportBeanDefinitionRegistrar
同样,新建一个测试Bean类。
package com.trinclesdar.study.spring.bean; /** * TestBean4 * * @author trinclesdar * @date 2018/11/6 22:25 */ public class TestBean4 { }
新建一个实现org.springframework.context.annotation.ImportBeanDefinitionRegistrar接口的BeanInjectByImportBeanDefinitionRegistrar类。
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.TestBean4; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; /** * 通过ImportBeanDefinitionRegistrar注入Bean * * @author trinclesdar * @date 2018/11/6 22:24 */ public class BeanInjectByImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { boolean bean1 = beanDefinitionRegistry.containsBeanDefinition("com.trinclesdar.study.spring.bean.TestBean2"); boolean bean2 = beanDefinitionRegistry.containsBeanDefinition("com.trinclesdar.study.spring.bean.TestBean3"); //如果TestBean2和TestBean3同时存在于我们IOC容器中,那么创建TestBean4类, 加入到容器 //对于我们要注册的bean, 给bean进行封装, if (bean1 && bean2) { RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean4.class); beanDefinitionRegistry.registerBeanDefinition("testBean4", beanDefinition); } } }
最后,将BeanInjectByImportBeanDefinitionRegistrar.class类型追加至@Importor注解中。
package com.trinclesdar.study.spring.beaninject; import com.trinclesdar.study.spring.bean.TestBean1; import org.springframework.context.annotation.Import; /** * 通过Import注入bean至IOC容器 * * @author trinclesdar * @date 2018/11/4 18:23 */ @Import(value = {TestBean1.class, BeanInjectByImportSelector.class, BeanInjectByImportBeanDefinitionRegistrar.class}) public class BeanInjectByImport { }
测试
@Test public void beanInjectByImport() { // 声明容器,将BeanInjectByComponentScan类的配置信息加载至容器中 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInjectByImport.class); // 获取容器中所有的bean名称 String[] beanNames = applicationContext.getBeanDefinitionNames(); System.out.println("beanNames:"); for (String beanName : beanNames) { System.out.println(beanName); } }
效果如预料的一样,testBean4成功注入容器,我们在registerBeanDefinitions()方法中,声明的组件名称为testBean4,那么注入容器的名称则为testBean4,如果我们改为:
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { boolean bean1 = beanDefinitionRegistry.containsBeanDefinition("com.trinclesdar.study.spring.bean.TestBean2"); boolean bean2 = beanDefinitionRegistry.containsBeanDefinition("com.trinclesdar.study.spring.bean.TestBean3"); //如果TestBean2和TestBean3同时存在于我们IOC容器中,那么创建TestBean4类, 加入到容器 //对于我们要注册的bean, 给bean进行封装, if (bean1 && bean2) { RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean4.class); beanDefinitionRegistry.registerBeanDefinition("com.trinclesdar.study.spring.bean.TestBean4", beanDefinition); } }
那么注入容器的组件名称,将会对应变为全路径名。
这种方式,是Spring原生的方式,其底层实现的自带的Bean,就是通过这种方式将Bean注入Spring容器,由于registerBeanDefinitions()方法中,可以提供逻辑处理,因此通过这种方式来完成依赖注入,具备更高的灵活性。
5.总结
通常我们在做日常项目的过程中,最常用的注解为@Bean,@Component,基本上可以满足我们绝大多数的需求,其它几种Bean的注入方式,如ImportSelectorImportBeanDefinitionRegistrar、FacrotyBean等,出场率要低得多,本质上而言,其实最终的实现效果与@Bean实现的效果基本一样。之所以在这里花如此多的篇幅去写Spring的各种Bean的注入方式,一方面是为了更好的了解Sping,二来在工作中,偶尔用这些不太常见的方式来提升一下代码逼格,也是不错的。哈哈!最后,这些知识,在我们了解Spring底层源码实现的道路上,也是很重要的奠基。
最后的最后,附上一张我总结的关于Spring依赖注入的知识点思维导图:
- spring几种依赖注入方式以及ref-local/bean,factory-bean,factory-method区别联系
- spring的注入bean的几种方式
- Spring学习之(四)依赖注入的几种装配方式
- 关于Spring依赖注入的几种方式
- Spring Ioc-依赖注入的几种方式
- Spring 依赖注入的 几种方式
- Spring-2:bean的两种依赖注入方式
- Spring注入Bean的几种方式
- Spring Ioc-依赖注入的几种方式
- Spring依赖注入的几种方式
- Spring依赖注入的几种实现方式
- spring的依赖注入几种方式
- Spring学习----------Bean 的三种依赖注入方式介绍
- spring学习总结(三):IOC & DI 配置 Bean 之配置形式及依赖注入方式
- SpringIOC容器创建对象及依赖注入的几种方式
- Spring 依赖注入的几种方式详解
- spring 配置bean的方法及依赖注入发方式
- Spring学习笔记二(Bean注入的几种方式)
- Spring Ioc-依赖注入的几种方式
- Spring中依赖注入的几种方式