当前位置: 首页 > 后端技术 > Java

你知道最全的Spring依赖注入方法吗?

时间:2023-04-01 18:19:05 Java

前言Spring,顾名思义,为开发者带来了春天。Spring是一个旨在解决企业级应用开发复杂性的框架。它的设计理念是:简化开发。Spring框架中的核心思想是:IOC(InversionofControl):即把创建对象的控制权转移,将创建对象的控制权从开发者转移到Spring框架。AOP(AspectProgramming):将公共行为(如日志记录、权限验证等)封装成可复用的模块,让原有模块只需要专注于自己的个性化行为。在这篇文章中,我将主要介绍Spring中IOC的依赖注入。InversionofControlIOC,就IOC本身而言,并不是什么新技术,只是一种想法。IOC的核心是创建一个对象。我们需要直接通过new来创建。IOC相当于有人给你创建一个对象。当你需要使用它时,你可以拿走它。IOC的实现方式主要有两种:DL(DependencyLookup):依赖查找。这意味着容器已经为我们创建了对象。当我们需要使用它的时候,我们会主动去容器中查找,比如:ApplicationContextapplicationContext=newClassPathXmlApplicationContext("/application-context.xml");Objectbean=applicationContext.getBean("object");DI(DependencyInject):依赖注入。依赖注入是相对于依赖查找的另一种优化,即我们不需要自己查找,只需要告诉容器需要注入的对象,容器会自动注入(赋值)创建的目的。依赖注入DI通过xml的注入方式我们就不讨论了。这里主要讨论基于注解的注入方式。基于注解的常规注入方式通常有三种:基于属性的注入、基于setter的注入、基于构造函数的注入,三种常规注入方式。下面分别介绍三种常规注入方式。属性注入通过属性注入是很常见的,这个方法大家应该也比较熟悉:@ServicepublicclassUserService{@AutowiredprivateWolf1Beanwolf1Bean;//通过属性注入}setter方法注入不仅是通过属性注入,还有通过setter方法注入可以实现:@ServicepublicclassUserService{privateWolf3Beanwolf3Bean;@Autowired//通过setter方法实现注入publicvoidsetWolf3Bean(Wolf3Beanwolf3Bean){this.wolf3Bean=wolf3Bean;我们也可以通过构造函数实现注入:@ServicepublicclassUserService{privateWolf2Beanwolf2Bean;@Autowired//通过构造函数注入publicUserService(Wolf2Beanwolf2Bean){this.wolf2Bean=wolf2Bean;}}interfaceinjectedintotheabovethree在常规的注入方式中,如果我们要注入一个接口,而当前接口有多个实现类,那么这时候就会报错,因为Spring无法知道应该是哪个实现类注入。比如上面三个类都实现了同一个接口IWolf,那么直接使用常规的注入方式,不带任何注解元数据注入接口IWolf。@AutowiredprivateIWolfiWolf;这时候启动服务会报错:意思是说应该注入了一个类,但是Spring找到了三个,所以无法确认应该使用哪一个。如何解决这个问题呢?主要有五种方案:通过配置文件和@ConditionalOnProperty注解,通过@ConditionalOnProperty注解可以结合配置文件实现唯一注入。下面的例子意思是如果在配置文件中配置了lonely.wolf=test1,那么Wolf1Bean会被初始化到容器中。这时因为其他实现类不满足条件,所以不会初始化到IOC容器中,所以可以正常注入接口。:@Component@ConditionalOnProperty(name="lonely.wolf",havingValue="test1")publicclassWolf1BeanimplementsIWolf{}当然,在这个配置中,编译器可能还是会提示有多个bean,但是只要我们保证每个实现类如果条件不一致,都可以正常使用。上面的配置文件条件是通过其他@Condition条件注解去掉的,也可以使用其他类似的条件注解,比如:@ConditionalOnBean:当某个Bean存在时,将这个类初始化到容器中。@ConditionalOnClass:当一个类存在时,初始化这个类的容器。@ConditionalOnMissingBean:当一个Bean不存在时,将这个类初始化到容器中。@ConditionalOnMissingClass:当某个类不存在时,将这个类初始化到容器中。...类似这种实现方式,也可以很灵活的实现动态配置。但是上面介绍的方法好像一次只注入一个实现类,那么如果我们想同时注入多个类,不同的场景可以动态切换,不需要重启,也不需要修改配置文件,如何实现呢??如果不想通过@Resource注解手动获取,我们也可以通过@Resource注解的形式动态指定BeanName:@ComponentpublicclassInterfaceInject{@Resource(name="wolf1Bean")privateIWolfiWolf;}如图上面,只会注入BeanName为wolf1Bean的实现类。除了通过集合指定bean来注入,我们还可以通过集合一次性注入接口的所有实现类:@AutowiredprivateMap地图;}以上两种形式会将IWolf中的所有实现类都注入到集合中。如果你使用的是List集合,那么我们可以把它取出来,使用instanceof关键字来判断类型;而如果通过Map集合注入,Spring会将Bean名(类名默认小写)作为key进行存储,这样我们就可以在需要的时候动态获取自己想要的实现类。@Primary注解实现默认注入除了上述方法外,我们还可以在其中一个实现类中添加@Primary注解,表示当多个bean满足条件时,优先注入当前带有@Primary注解的Bean:@Component@PrimarypublicclassWolf1BeanimplementsIWolf{}这样Spring会默认注入wolf1Bean,同时我们仍然可以通过context手动获取其他的实现类,因为其他的实现类也存在于容器中。手动获取bean的几种方式在Spring项目中,手动获取bean需要一个ApplicationContext对象。这时候可以通过以下五种方式获取:直接注入最简单的方式是直接注入获取ApplicationContext对象,然后通过ApplicationContext对象获取Bean:@ComponentpublicclassInterfaceInject{@AutowiredprivateApplicationContextapplicationContext;//注入publicObjectgetBean(){returnapplicationContext.getBean("wolf1Bean");ApplicationContextAware接口获取ApplicationContext对象获取Bean。需要注意的是,实现ApplicationContextAware接口的类也需要注解交给Spring统一管理(该方法也是项目中使用较多的方法):@ComponentpublicclassSpringContextUtilimplementsApplicationContextAware{privatestaticApplicationContextapplicationContext=空;@OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{this.applicationContext=applicationContext;}/***通过名称获取bean*/publicstaticTgetBeanByName(StringbeanName){return(T)applicationContext.getBean(beanName);}/***按类型获取beans*/publicstaticTgetBeanByType(Classclazz){return(T)applicationContext.getBean(clazz);}}封装之后,我们可以直接调用对应的方法获取Bean:Wolf2Beanwolf2Bean=SpringContextUtil.getBeanByName("wolf2Bean");Wolf3Beanwolf3Bean=SpringContextUtil.getBeanByType(Wolf3Bean.class);通过ApplicationObjectSupport和WebApplicationObjectSupport获取这两个对象,WebApplicationObjectSupportnObjectSupport继承了ApplicationObjectSupport,所以没有真正的区别同样,下面的工具类也需要注解为Spring管理:@ComponentpublicclassSpringUtilextends/*WebApplicationObjectSupport*/publicstaticTgetBean(StringbeanName){return(T)applicationContext.getBean(beanName);}@PostConstructpublicvoidinit(){applicationContext=super.getApplicationContext();}}有了工具类,直接在方法中调用即可:@RestController@RequestMapping("/hello")@QualifierpublicclassHelloController{@GetMapping("/bean3")publicObjectgetBean3(){Wolf1Beanwolf1Bean=SpringUtil.getBean("wolf1Bean");返回wolf1Bean.toString();}}通过HttpServletRequest获取通过HttpServletRequest对象,结合Spring自身提供的工具类WebApplicationContextUtils,同样可以获取ApplicationContext对象,HttpServletRequest对象可以主动获取(下面getBean2方法)也可以被动获取(下面getBean1方法):@RestController@RequestMapping("/你好")@QualifierpublicclassHelloController{@GetMapping("/bean1")publicObjectgetBean1(HttpServletRequestrequest){//直接通过方法中的HttpServletRequest对象ApplicationContextapplicationContextWeb=WebApplicationContextUtilsApplications.getRequired();Wolf1Beanwolf1Bean=(Wolf1Bean)applicationContext.getBean("wolf1Bean");返回wolf1Bean.toString();}@GetMapping("/bean2")publicObjectgetBean2(){HttpServletRequestrequest=((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();//手动获取请求对象ApplicationContextWebapplicationContext.RequireRequiredContext.RequiredContext.));Wolf2Beanwolf2Bean=(Wolf2Bean)applicationContext.getBean("wolf2Bean");返回wolf2Bean.toString();}}其他方式获得当然,除了上面提到的方法,我们也可以使用最开始提到的DL中代号示例去手动new一个ApplicationContext对象,但是这意味着重新初始化一次,所以不建议这样做,但是这种方法更适合写单元测试的时候。说说@Autowrite与@Resource和@Qualifier注解的区别。上面我们看到,注入一个Bean可以通过@Autowrite或者@Resource注解来注入。这两个注解有什么区别?@Autowrite:按类型注入,可用于构造函数和参数注入。当我们注入一个接口时,它的所有实现类都属于同一个类型,因此无法知道选择哪个实现类进行注入。@Resource:默认按名称注入,不能用于构造函数和参数注入。如果无法通过名称找到唯一的bean,则将通过类型查找。唯一的实现可以通过指定名称或类型来确定,如下所示:@Resource(name="wolf2Bean",type=Wolf2Bean.class)privateIWolfiWolf;而@Qualifier注解用于标识合格者,当@Autowrite和@Qualifier一起使用时,相当于通过名字来判断唯一性:@Qualifier("wolf1Bean")@AutowiredprivateIWolfiWolf;那么可能有人会说,我直接用@Resource就行了,何苦把两个注解组合起来,这样看来@Qualifier注解有点多余呢?@Qualifier注释是多余的吗?我们先来看下面声明一个bean的场景。这里通过一个方法声明了一个bean(MyElement),方法中的参数有Wolf1Bean对象。那么此时Spring会自动为我们注入Wolf1Bean:但是,如果我们稍微改动一下上面的代码,把参数改成一个接口,而这个接口有多个实现类,这时候就会报错:@ComponentpublicclassInterfaceInject2{@BeanpublicMyElementtest(IWolfiWolf){//此时由于IWolf接口有多个实现类,会报错returnnewMyElement();}}而@Resource注解是不能在参数中使用的,所以这时候就需要使用@Qualifier注解来确认唯一实现(例如@Qualifier注解经常在配置多个数据源时使用):@ComponentpublicclassInterfaceInject2{@BeanpublicMyElementtest(@Qualifier("wolf1Bean")IWolfiWolf){returnnewMyElement();}}小结本文主要讲述了如何在Spring中使用灵活的方法实现各种场景的注入方法,重点关注当一个接口有多个实现类时如何实现的问题inject,最后介绍几种常用的注入注解的区别,通过这篇文章,相信大家会更加熟悉如何在Spring中使用依赖注入