Spring IoCDI补充
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 {@Beanpublic 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' availableat 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 {@Beanpublic UserInfo userInfo(){UserInfo userInfo = new UserInfo();userInfo.setAge(18);userInfo.setName("zhangsan");return userInfo;}
}
成功获取到对象.
定义多个对象
@Component
public class BeanConfig {@Beanpublic UserInfo userInfo(){UserInfo userInfo = new UserInfo();userInfo.setAge(18);userInfo.setName("zhangsan");return userInfo;}@Beanpublic UserInfo user(){UserInfo userInfo = new UserInfo();userInfo.setAge(18);userInfo.setName("lisi");return userInfo;}@Beanpublic 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,userInfo1at 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 {//属性注入@Autowiredprivate 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 nullat 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() {}@Autowiredpublic 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 foundat 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 foundat 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;@Autowiredpublic 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 {@Autowiredprivate UserInfo userInfo2;public void sayHi() {System.out.println("hi,UserController");System.out.println(userInfo2);}
}
报错的原因是存在非唯一的bean对象.
解决方案:
使用@Primary
使用@Primary来标识默认的Bean对象.
@Primary@Beanpublic 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")@Autowiredprivate 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.