依赖注入(依赖装配)
第一种方式:属性注入
说白了这个标题有点拗口,之前我们都是获取Bean对象就完事了,现在我们不但要从spring容器中去获取对象,同时还要将这个取出来的对象发到某个类里面,那么这个过程就叫做依赖注入,也称作依赖装配.
那么首先我们创建一个LoginService类,这个类的路径为org.example.service.LoginService:我们使用@Service注解将这个类注入到spring容器当中,代码如下所示:
package org.example.controller.service; import org.springframework.stereotype.Service; @Service public class LoginService { public void sayHi(){ System.out.println("LoginService say hi"); } }
然后此时我们想在LoginController类下面来取LoginService这个对象中的sayHi方法,就需要使用到@Autowired注解,代码如下所示:
首先是在对象上使用@Autowired注解(一般这种写法用的最多)
package org.example.controller; import org.example.controller.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { //第一种注入的方式,叫做属性注入,以下岩石的是属性注入的第一种写法:@Autowired //注意这种取名字的方式根Bean对象存到spring容器中类似,为loginService //取名方式前面有,我就不再做过多赘述了 @Autowired private LoginService loginService; public void sayHi() { //取到后直接调用即可 loginService.sayHi(); } }
同时我们还可以在setter方法上使用@Autowired注解
package org.example.controller; import org.example.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { //第一种注入的方式:属性注入的第二种写法setter注入+@Autowired private LoginService loginService; //这样写的好处是传参的名字随便命名,之前还必须是loginService,现在写ls也可以 @Autowired public void setLoginService(LoginService ls) { this.loginService = ls; } public void sayHi() { //取到后直接调用即可 loginService.sayHi(); } }
然后我们在App这个类中进行下测试,看我们从spring容器中获取到LoginController这个对象后调用sayHi()方法,可不可以输出LoginService里面的sayHi方法的结果.
package org.example; import org.example.controller.LoginController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); LoginController controller = applicationContext.getBean(LoginController.class); //结果为LoginService say hi controller.sayHi(); //一定要记得释放资源 ((ClassPathXmlApplicationContext) applicationContext).close(); } }
发现结果输出成功
第二种方式:构造方法注入
第二种注入方式为构造方法的写法,这种方法其实也非常简单:下面来看写法:
那么首先我们创建一个LoginService类,这个类的路径为org.example.service.LoginService:我们使用@Service注解将这个类注入到spring容器当中,代码如下所示:
package org.example.controller.service; import org.springframework.stereotype.Service; @Service public class LoginService { public void sayHi(){ System.out.println("LoginService say hi"); } }
然后此时我们想在LoginController类下面来取LoginService这个对象中的sayHi方法,之前我们使用的是属性注入的方法,现在我们使用的是构造方法注入的方法,来看代码:
package org.example.controller; import org.example.service.LoginService; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { //第二种注入的方式:构造方法的注入 private LoginService loginService; //注意此处就不需要加入@Autowired注解 public LoginController(LoginService loginService) { this.loginService = loginService; } public void sayHi() { //取到后直接调用即可 loginService.sayHi(); } }
然后我们在App这个类中进行下测试,看我们从spring容器中获取到LoginController这个对象后调用sayHi()方法,可不可以输出LoginService里面的sayHi方法的结果.
package org.example; import org.example.controller.LoginController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); LoginController controller = applicationContext.getBean(LoginController.class); //结果为LoginService say hi controller.sayHi(); //一定要记得释放资源 ((ClassPathXmlApplicationContext) applicationContext).close(); } }
补充
注入指定的Bean:@Qualifier
在spring容器中同类型的Bean有多个时,注入该类型Bean需要指定Bean的名称:注入有两种方法:
属性名或方法参数名设置为Bean的名称
属性名或方法参数设置 @Qualifier("名称")注解,注解内的字符串是Bean对象的名称
以下LoginController类中定义了2个用户对象,且在方法参数中注入了Bean对象:
package org.example.controller; import org.example.model.User; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { //注意此时我们往spring容器中注入了两个Bean对象 @Bean public User user1() { User user = new User(); user.setName("abc"); user.setPassword("123"); return user; } @Bean public User user2() { User user = new User(); user.setName("我不是汤神"); user.setPassword("tang"); return user; } }
我们先来展示错误获取某个Bean对象的名字的代码写法:
package org.example.controller; import org.example.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { //如果按照底下这样写我们会报错 @Autowired private User user; //注意此时我们往spring容器中注入了两个Bean对象 @Bean public User user1() { User user = new User(); user.setName("abc"); user.setPassword("123"); return user; } @Bean public User user2() { User user = new User(); user.setName("我不是汤神"); user.setPassword("tang"); return user; } public void sayHi() { //在这里会报错,原因是我们此时并不知道user这个引用到底指向user1方法返回的对象还是user2方法返回的对象 System.out.println(user.getName()); } }
在App类内部进行输出打印:
package org.example; import org.example.controller.LoginController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); LoginController controller = applicationContext.getBean(LoginController.class); //直接报错 controller.sayHi(); //一定要记得释放资源 ((ClassPathXmlApplicationContext) applicationContext).close(); } }
以下是错误信息:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘org.example.model.User’ available: expected single matching bean but found 2: user1,user2
很明显系统并不知道获取哪个对象的name,所以报错了,此时我们提供两种解决方案
解决方案1:属性名或方法参数名设置为Bean的名称
LoginController类代码:
package org.example.controller; import org.example.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { //属性名或方法参数名设置为Bean的名称 此时设置名称为方法名user1 @Autowired private User user1; //注意此时我们往spring容器中注入了两个Bean对象 @Bean public User user1() { User user = new User(); user.setName("abc"); user.setPassword("123"); return user; } @Bean public User user2() { User user = new User(); user.setName("我不是汤神"); user.setPassword("tang"); return user; } public void sayHi() { System.out.println(user1.getName()); } }
在App类内部打印结果,结果应为abc,我们来看结果是否正确:
package org.example; import org.example.controller.LoginController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); LoginController controller = applicationContext.getBean(LoginController.class); //结果为abc,正确 controller.sayHi(); //一定要记得释放资源 ((ClassPathXmlApplicationContext) applicationContext).close(); } }
解决方案2:属性名或方法参数设置 @Qualifier("名称")注解,注解内的字符串是Bean对象的名称
LoginController类代码:
package org.example.controller; import org.example.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { //属性名或方法参数设置 `@Qualifier("名称")`注解,注解内的字符串是Bean对象的名称 @Autowired @Qualifier("user1") private User user; //注意此时我们往spring容器中注入了两个Bean对象 @Bean public User user1() { User user = new User(); user.setName("abc"); user.setPassword("123"); return user; } @Bean public User user2() { User user = new User(); user.setName("我不是汤神"); user.setPassword("tang"); return user; } public void sayHi() { System.out.println(user.getName()); } }
在App类内部打印结果,结果应为abc,我们来看结果是否正确:
package org.example; import org.example.controller.LoginController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); LoginController controller = applicationContext.getBean(LoginController.class); //结果为abc,正确 controller.sayHi(); //一定要记得释放资源 ((ClassPathXmlApplicationContext) applicationContext).close(); } }
Bean的作用域(Bean的类型)(常见面试题)
其实在这里作用域指的就是类型,也就是在spring中Bean都有哪些类型?常见的一共有六种
singleton(单例模式)
官方说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
描述:该作用域下的Bean在IoC容器中只存在一个实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是同一个对 象。
场景:通常无状态的Bean使用该作用域。无状态表示Bean对象的属性状态不需要更新
备注:Spring默认选择该作用域
怎样验证其是同一个对象呢?来看代码示例:
代码示例
还是我们的LoginController类:
package org.example.controller; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller public class LoginController { }
然后来到我们的APP类获取我们的Bean对象,假设遵循单例模式,那么最终获取到的Bean对象使用==判断时应该为true,因为都是同一对象,来看代码:
package org.example; import org.example.controller.LoginController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); LoginController controller = applicationContext.getBean(LoginController.class); LoginController controller1 = applicationContext.getBean(LoginController.class); //答案为true System.out.println(controller == controller1); //一定要记得释放资源 ((ClassPathXmlApplicationContext) applicationContext).close(); } }
prototype(原型模式)(每个请求创建一个新对象)
官方说明:Scopes a single bean definition to any number of object instances.
描述:每次对该作用域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是新的对象 实例。
场景:通常有状态的Bean使用该作用域
代码示例
还是我们的LoginController类:只不过这次要在LoginController类上加上@Scope("prototype")
package org.example.controller; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; //加上controller注解后,spring就能扫描到啦. @Controller @Scope("prototype") public class LoginController { }
然后来到我们的APP类获取我们的Bean对象,假设此时遵循prototype,那么最终获取到的Bean对象使用==判断时应该为false,因为创建了新的实例,来看代码:
package org.example; import org.example.controller.LoginController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); LoginController controller = applicationContext.getBean(LoginController.class); LoginController controller1 = applicationContext.getBean(LoginController.class); //答案为false System.out.println(controller == controller1); //一定要记得释放资源 ((ClassPathXmlApplicationContext) applicationContext).close(); } }
request
官方说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean
definition. Only valid in the context of a web-aware Spring ApplicationContext.
描述:每次http请求会创建新的Bean实例,类似于prototype
场景:一次http的请求和响应的共享Bean
备注:限定SpringMVC中使用
session
官方说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
描述:在一个http session中,定义一个Bean实例
场景:用户回话的共享Bean, 比如:记录一个用户的登陆信息
备注:限定SpringMVC中使用
application(了解)
官方说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
描述:在一个http servlet Context中,定义一个Bean实例
场景:Web应用的上下文信息,比如:记录一个应用的共享信息
备注:限定SpringMVC中使用
websocket(了解)
官方说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
描述:在一个HTTP WebSocket的生命周期中,定义一个Bean实例
场景:WebSocket的每次会话中,保存了一个Map结构的头信息,将用来包裹客户端消息头。第一 次初始化后,直到WebSocket结束都是同一个Bean。
备注:限定Spring WebSocket中使用
面试的时候记住前五种即可
Bean的生命周期(面试常考)
生命周期概览
乍一看卧槽这个图可真多,如果上面的图记不住的话,至少也要记住底下的这几步:
1:实例化:将二进制字节流转换成内存对象的过程,此时属性还未注入
2:属性设置:进行依赖注入
3:进行初始化:开始执行我的业务代码
4:销毁
如果能记下来,就记住下面的总结
总结
对于Bean的生命周期,主要步骤为:
1.实例化Bean:通过反射调用构造方法实例化对象。
2.依赖注入:装配Bean的属性
3.实现了Aware接口的Bean,执行接口方法:如顺序执行BeanNameAware、BeanFactoryAware、
ApplicationContextAware的接口方法。
4.Bean对象初始化前,循环调用实现了BeanPostProcessor接口的预初始化方法
(postProcessBeforeInitialization)
5.Bean对象初始化:顺序执行@PostConstruct注解方法、InitializingBean接口方法、init-method
方法
6.Bean对象初始化后,循环调用实现了BeanPostProcessor接口的后初始化方法
(postProcessAfterInitialization)
7.容器关闭时,执行Bean对象的销毁方法,顺序是:@PreDestroy注解方法、DisposableBean接口方法、destroy-method
补充说明:第一步的实例化是指new对象,Spring的语义中说初始化Bean包含Bean生命周期中的初始 化步骤。

