IoC详解
IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象.也就是bean的存储.
Bean的存储
共有两类注解类型可以实现Bean的存储:
1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
2. 方法注解:@Bean.
@Controller(控制器存储)
使用@Controller存储Bean的代码如下:
@Controller
public class UserController {
public void sayHi() {
System.out.println("hi,UserController");
}
}
在启动类中,从Spring IoC容器中获取对象
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
UserController bean = context.getBean(UserController.class);
bean.sayHi();
}

Spring容器中已包含这个Bean.
@Service(服务存储)
使用@Service存储Bean的代码如下所示:
@Service
public class UserService {
public void sayHi(){
System.out.println("hi,UserService");
}
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
UserService bean = context.getBean(UserService.class);
bean.sayHi();
}

@Repository(仓库存储)
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository~");
}
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
UserRepository bean = context.getBean(UserRepository.class);
bean.sayHi();
}

@Component(组件存储)
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent");
}
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
UserComponent bean = context.getBean(UserComponent.class);
bean.sayHi();
}

@Configuration(配置存储)
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi,UserConfiguration");
}
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
UserConfiguration bean = context.getBean(UserConfiguration.class);
bean.sayHi();
}

为什么要这么多注解?
从上面可以观察到,这些注解的用法看起来大差不差.但是这些注解与应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途.
@Controller 控制层,接受请求,对请求进行处理,并进行相应.
@Service 业务逻辑层,处理具体的业务逻辑.
@Repository 数据访问层,也称为持久层,负责数据访问操作.
@Configuration 配置层,处理项⽬中的⼀些配置信息.
查看 @Controller/@Service/@Repository/@Configuration等注解的源码发现:他们的注解都包含@Component,也就是这些注解都是@Component的"子类".@Component的功能,这些注解都包含.
@Controller,@Service和@Repository⽤于更具体的⽤例(分别在控制层,业务逻辑层,持久化层),在开发过程中,如果你要在业务逻辑层使⽤@Componen或@Service,显然@Service是更好的选择.
方法注解@Bean
类注解是注解在某个类上的,但是这样做有两个问题:
1.在使用外部包的类时,无法使用类注解对外部包的类进行注解.
2.一个类需要多个对象时,类注解只会产生一个对象.
在上述的两个场合,就需要使用方法注解@Bean.
观察@Bean如何使用:
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setName("zhangsan");
return userInfo;
}
}
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.springboot.ioc.model.UserInfo' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1148)
at com.springboot.ioc.IoCApplication.main(IoCApplication.java:18)
直接使用,并尝试获取对象时报错,NoSuchBeanDefinitionException根据后续信息判断出,这里@Bean注解没有成功添加Bean到容器中.
方法注解要配合类注解使用
在Spring框架的设计中,⽅法注解 @Bean要配合类注解才能将对象正常的存储到Spring容器中,如下代码所⽰:
@Component
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setName("zhangsan");
return userInfo;
}
}

成功获取到对象.
定义多个对象
@Component
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setName("zhangsan");
return userInfo;
}
@Bean
public UserInfo user(){
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setName("lisi");
return userInfo;
}
@Bean
public UserInfo userInfo1(){
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setName("wangwu");
return userInfo;
}
}
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.springboot.ioc.model.UserInfo' available: expected single matching bean but found 3: userInfo,user,userInfo1
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1287)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:483)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:338)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1148)
at com.springboot.ioc.IoCApplication.main(IoCApplication.java:18)
此时获取对象又报错了,NoUniqueBeanDefinitionException,根据报错信息判断出,Spring容器中包含多个Bean,但是Spring无法判断选择哪一个.此时我们只需要指定获取哪个Bean即可.
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
//指定获取名为user的对象
UserInfo bean = context.getBean("user",UserInfo.class);
System.out.println(bean);
}

在指定获取之后,程序成功允许并获得了正确的结果.
重命名Bean
可以通过name属性给Bean对象进行重命名操作.
//将user重命名为UUUUUUU
@Bean(name = {"UUUUUUU","user"})
public UserInfo user(){
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setName("lisi");
return userInfo;
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
UserInfo bean = context.getBean("UUUUUUU",UserInfo.class);
System.out.println(bean);
}

重命名并使用新的名称指定获取成功.
扫描路径
项目中的代码并不是所有代码都能被Spring扫描到的,程序被Spring扫描并管理需要满足以下条件:
1.被Spring扫描到,Spring默认扫描路径为启动类所在的目录包含子目录.可以通过@ComponentScan(basePackage="")进行修改.
2.类需要配合五大注解使用.
DI详解
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象.在之前程序案例中,使⽤了@Autowired 这个注解,完成了依赖注⼊的操作.
依赖注入,Spring也提供了三种方案:
1.属性注入
2.构造方法注入
3.Setter注入
属性注入
属性注⼊是使用@Autowired实现的,将Service类注⼊到Controller类中:
@Service
public class UserService {
public void sayHi(){
System.out.println("hi,UserService");
}
}
@Controller
public class UserController {
//属性注入
@Autowired
private UserService userService;
public void sayHi() {
System.out.println("hi,UserController");
userService.sayHi();
}
}

注入成功,代码正确执行.
构造方法注入
依旧将Service类注⼊到Controller类中:
@Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("hi,UserController");
userService.sayHi();
}
}

直接写出包含Service的构造方法,即可正确注入.
但是如果类中包含多个构造方法,那么就会出现错误.
@Controller
public class UserController {
private UserService userService;
public UserController() {
}
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("hi,UserController");
userService.sayHi();
}
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.springboot.ioc.service.UserService.sayHi()" because "this.userService" is null
at com.springboot.ioc.controller.UserController.sayHi(UserController.java:20)
at com.springboot.ioc.IoCApplication.main(IoCApplication.java:19)
抛出了空指针异常,说明Spring直接使用了无参数的构造方法,如果想让Spring使用我们提供的包含Serveice的构造方法,需要标上@Autowired注解.
@Controller
public class UserController {
private UserService userService;
public UserController() {
}
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("hi,UserController");
userService.sayHi();
}
}

包含多个构造方法时:
@Controller
public class UserController {
private UserService userService;
private UserConfiguration userConfiguration;
// public UserController() {
// }
public UserController(UserService userService) {
this.userService = userService;
}
public UserController(UserService userService, UserConfiguration userConfiguration) {
this.userService = userService;
this.userConfiguration = userConfiguration;
}
public void sayHi() {
System.out.println("hi,UserController");
userService.sayHi();
}
}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [E:\Java\IoC\target\classes\com\springboot\ioc\controller\UserController.class]: Failed to instantiate [com.springboot.ioc.controller.UserController]: No default constructor found
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1306) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1198) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:915) ~[spring-context-6.0.4.jar:6.0.4]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[spring-context-6.0.4.jar:6.0.4]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.0.2.jar:3.0.2]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[spring-boot-3.0.2.jar:3.0.2]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[spring-boot-3.0.2.jar:3.0.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-3.0.2.jar:3.0.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[spring-boot-3.0.2.jar:3.0.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[spring-boot-3.0.2.jar:3.0.2]
at com.springboot.ioc.IoCApplication.main(IoCApplication.java:17) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.springboot.ioc.controller.UserController]: No default constructor found
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83) ~[spring-beans-6.0.4.jar:6.0.4]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1300) ~[spring-beans-6.0.4.jar:6.0.4]
... 17 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.springboot.ioc.controller.UserController.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3585) ~[na:na]
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2754) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:79) ~[spring-beans-6.0.4.jar:6.0.4]
... 18 common frames omitted
分析报错信息,得出是因为没有默认的构造函数导致的.此时给我们需要的构造函数加上@Autowired注解即可.

Setter注入
Setter注⼊和属性的Setter⽅法实现类似,只不过在设置set⽅法的时候需要加上@Autowired注解,如下代码所⽰:
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("hi,UserController");
userService.sayHi();
}
}

三种注入的优缺点分析
属性注⼊
优点:简洁,使⽤⽅便
缺点:
1.只能⽤于IoC容器,如果是⾮IoC容器不可⽤,并且只有在使⽤的时候才会出现NPE(空指针异常)
2.不能注⼊⼀个Final修饰的属性
构造函数注⼊(Spring4.X推荐)
优点:
1.可以注⼊final修饰的属性
2.注⼊的对象不会被修改
3.依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法
是在类加载阶段就会执⾏的⽅法.
4.通⽤性好,构造⽅法是JDK⽀持的,所以更换任何框架,他都是适⽤的.
缺点:注⼊多个对象时,代码会⽐较繁琐
Setter注⼊(Spring3.X推荐)
优点:⽅便在类实例之后,重新对该对象进⾏配置或者注⼊
缺点:
1.不能注⼊⼀个Final修饰的属性.
2.注⼊对象可能会被改变,因为setter⽅法可能会被多次调⽤,就有被修改的⻛险.
@Autowired存在问题
Autowired会先尝试获取与所修饰的变量同名的Bean,当不存在同名的Bean时,尝试获取同类型的Bean.当同一类型存在多个Bean时,使用@Autowired就会存在问题:
@Controller
public class UserController {
@Autowired
private UserInfo userInfo2;
public void sayHi() {
System.out.println("hi,UserController");
System.out.println(userInfo2);
}
}

报错的原因是存在非唯一的bean对象.
解决方案:
使用@Primary
使用@Primary来标识默认的Bean对象.
@Primary
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setName("zhangsan");
return userInfo;
}

使用@Qualifier
使用@Qualifier注解:指定当前要注⼊的bean对象.在@Qualifier的value属性中,指定注⼊的bean的名称.
public class UserController {
//指定获取变量名为user的Bean
@Qualifier("user")
@Autowired
private UserInfo userInfo2;
public void sayHi() {
System.out.println("hi,UserController");
System.out.println(userInfo2);
}
}

使⽤@Resource
使⽤@Resource注解:是按照bean的名称进⾏注⼊.通过name属性指定要注⼊的bean的名称.
public class UserController {
//获取userInfo1
@Resource(name = "userInfo1")
private UserInfo userInfo2;
public void sayHi() {
System.out.println("hi,UserController");
System.out.println(userInfo2);
}
}

@Autowired与@Resource的区别:
@Autowired是spring框架提供的注解,⽽@Resource是JDK提供的注解.
@Autowired默认是按照类型注⼊,而@Resource是按照名称注⼊.相比于@Autowired来说,@Resource⽀持更多的参数设置,例如name设置,根据名称获取Bean.
6470

被折叠的 条评论
为什么被折叠?



