Ioc 详解
五大注解只能加在类上,并且只能加在自己的代码上,如果引入了一个第三方Jar包,也希望交给Spring管理,是没有办法加五大类注解的,此时我们可以使用@Bean
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring 上下文
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
// 从context中获取bean
UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);
userConfiguration.doConfiguration();
System.out.println("userConfiguration :" + userConfiguration);
UserConfiguration userConfiguration2 = context.getBean(UserConfiguration.class);
userConfiguration2.doConfiguration();
System.out.println("userConfigurationw2 :" + userConfiguration2);
System.out.println("======================");
System.out.println("userConfiguration == userConfigurationw2" + (userConfiguration == userConfiguration2));
}
}
他们两个的地址相同,也就是说它俩指向同一个对象。我们使用五大注解的方式,从容器中取多少次取到的都是同一个对象。
所以还有这一种场景:对于一个类,定义多个对象时,比如数据库操作,定义多个数据源,我们需要多个对象,就可以使用@Bean
方法注解 @Bean
@Data
public class UserInfo {
private Integer id;
private String name;
private Integer age;
}
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
@Bean
public UserInfo userInfo2(){
UserInfo userInfo = new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(19);
return userInfo;
}
}
使用
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring 上下文
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
// 从context中获取bean
//使用@Bean
UserInfo userInfo = context.getBean(UserInfo.class);
System.out.println(userInfo);
}
}
此代码发现报错,
bean没有定义,但是我们不是加了注解吗?
Spring通过扫描项目来确定是否加注解, 但是把整个项目去扫描是不合理的,如果我们需要Spring帮我们做一些事情的时候,给他个提醒,然后让他去做,这样不就更高效吗?
我们告诉Spring,BeanConfig这个类里面有需要它帮我们管理的对象,告诉的方式就是通过五大类注解,加了只后,Spring才会去扫描下面的方法。
改完后运行,我们发现又报错了!!!
Exception in thread “main” org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.example.demo.ioc.config.UserInfo’ available: expected single matching bean but found 2: userInfo,userInfo2
期望是一个bean名称,结果有两个bean名称。
使用@Bean注解时,bean的名称是方法名。
当前我们使用类型去获取bean,但是当前有两个:
我们先使用bean的名称来获取:
//使用@Bean
UserInfo userInfo = (UserInfo) context.getBean("userInfo");
System.out.println(userInfo);
如果想获取userInfo2,名称改一下就可以了:
//使用@Bean
UserInfo userInfo = (UserInfo) context.getBean("userInfo2");
System.out.println(userInfo);
这个方式也可以,就不用强制类型转换了:
UserInfo userInfo2 = context.getBean("userInfo",UserInfo.class);
System.out.println(userInfo2);
@Bean传递参数
定义了一个叫name2的String类型的对象,定义了一个叫name的String类型的对象。先根据类型拿,如果类型有多个,就根据名称去拿。
现在把名为name的注掉
它此时拿的就是name2了
结论:如果需要的Bean的类型,对应的对象只有一个时,就直接赋值,如果有多个时,通过名称去匹配。
扫描路径
SpringBoot特点:约定大于配置
我们将DemoApplication
移动到controller
路径下。
Spring默认扫描package com.example.demo.ioc.controller;
路径下的内容,所以运行DemoApplication
会报错。
解决方法就是通过注解:
指定扫描的路径
@ComponentScan("com.example.demo")
此时就运行成功了
一开始我们并没有使用@ComponentScan("com.example.demo")
这个注解,为什么就默认扫描的com.example.demo
,原因在于这个注解:@SpringBootApplication
它里面有一个@ComponentScan
,如下:
它就表示扫描当前类的目录以及它的子目录
总结:
DI详解
依赖注⼊(DI)是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象. 就是把对象取出来放到某个类的属性中.
依赖注⼊ Spring提供了三种⽅式:
- 属性注⼊(Field Injection)
- 构造⽅法注⼊(Constructor Injection)
- Setter 注⼊(Setter Injection)
属性注入
Service 类的实现代码
Controller 类的实现代码:
@Controller
public class UserController {
@Autowired
private UserService userService;
public void doController(){
userService.doService();
System.out.println("do Controller...");
}
}
获取 Controller 中的 doController⽅法:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring 上下文
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
// 从context中获取bean
UserController bean = context.getBean(UserController.class);
bean.doController();
}
}
结果:
属性注入以类型进行匹配,与注入的属性名称无关。但是如果一个类型存在多个对象时,优先名称匹配,如果名称都匹配不上,那就报错。
构造方法注入
@Controller
public class UserController {
//构造方法注入
private UserService userService;
private UserInfo userInfo;
public UserController(UserService userService, UserInfo userInfo) {
this.userService = userService;
this.userInfo = userInfo;
}
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void doController(){
userService.doService();
System.out.println("do Controller...");
}
}
如果存在多个构造函数,需要加上@AutoWired
注明使用哪个构造函数,如果只有一个构造函数@AutoWired
可以省略掉。
Setter注入
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上@Autowired 注解
@Controller
public class UserController {
// Setter 方法注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void doController(){
userService.doService();
System.out.println("do Controller...");
}
}
三种注入优缺点分析
属性注入:
- 优点:
简洁,使用方便。 - 缺点:
1.只能用于IoC容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常)
2.不能注⼊⼀个Final修饰的属性。
构造函数注入:
- 优点:
1.可以注入final修饰的属性
2.注⼊的对象不会被修改
3.依赖对象在使用前⼀定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法.
4.通用性好, 构造⽅法是JDK支持的, 所以更换任何框架,他都是适用的 - 缺点
注入多个对象时, 代码会⽐较繁琐。
Setter注入:
- 优点:
- 方便在类实例之后, 重新对该对象进行配置或者注入。
- 缺点:
1.不能注入⼀个Final修饰的属性。
2.注入对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险。
@Autowired存在问题
当同⼀类型存在多个bean时, 使用@Autowired会存在问题
@Configuration
public class BeanConfig {
@Bean
public String name(){
return "zhangsan";
}
// @Bean
// public String name2(){
// return "wangwu";
// }
@Bean
public UserInfo userInfo(String name){
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName(name);
userInfo.setAge(18);
return userInfo;
}
@Bean
public UserInfo userInfo2(){
UserInfo userInfo = new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(19);
return userInfo;
}
}
@Controller
public class UserController {
//属性注入
@Autowired
private UserService userService;
@Autowired
private UserInfo user;
public void doController(){
userService.doService();
System.out.println(user);
System.out.println("do Controller...");
}
}
报错的原因是,⾮唯⼀的 Bean 对象
解决方法:
- 属性名和需要使用的对象名保持一致。
- 使用@Primary注解标识默认的对象。
- 使用
@Qualifie
r注解
- 使用
@Resource
@Autowird
与@Resource
的区别
@Autowired
是spring框架提供的注解,而@Resource
是JDK提供的注解
@Autowired
默认是按照类型注入,而@Resource
是按照名称注入. 相比于@Autowired
来说,@Resource
支持更多的参数设置,例如name
设置,根据名称获取Bean
。
@Resource
⽀持更多的参数设置下面源码就体现了:name
lookup
type
等等
Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
/**
* The JNDI name of the resource. For field annotations,
* the default is the field name. For method annotations,
* the default is the JavaBeans property name corresponding
* to the method. For class annotations, there is no default
* and this must be specified.
*/
String name() default "";
/**
* The name of the resource that the reference points to. It can
* link to any compatible resource using the global JNDI names.
*
* @since Common Annotations 1.1
*/
String lookup() default "";
/**
* The Java type of the resource. For field annotations,
* the default is the type of the field. For method annotations,
* the default is the type of the JavaBeans property.
* For class annotations, there is no default and this must be
* specified.
*/
Class<?> type() default java.lang.Object.class;
/**
* The two possible authentication types for a resource.
*/
enum AuthenticationType {
CONTAINER,
APPLICATION
}
/**
* The authentication type to use for this resource.
* This may be specified for resources representing a
* connection factory of any supported type, and must
* not be specified for resources of other types.
*/
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
/**
* Indicates whether this resource can be shared between
* this component and other components.
* This may be specified for resources representing a
* connection factory of any supported type, and must
* not be specified for resources of other types.
*/
boolean shareable() default true;
/**
* A product specific name that this resource should be mapped to.
* The name of this resource, as defined by the <code>name</code>
* element or defaulted, is a name that is local to the application
* component using the resource. (It's a name in the JNDI
* <code>java:comp/env</code> namespace.) Many application servers
* provide a way to map these local names to names of resources
* known to the application server. This mapped name is often a
* <i>global</i> JNDI name, but may be a name of any form. <p>
*
* Application servers are not required to support any particular
* form or type of mapped name, nor the ability to use mapped names.
* The mapped name is product-dependent and often installation-dependent.
* No use of a mapped name is portable.
*/
String mappedName() default "";
/**
* Description of this resource. The description is expected
* to be in the default language of the system on which the
* application is deployed. The description can be presented
* to the Deployer to help in choosing the correct resource.
*/
String description() default "";
}
@AutoWired
默认是按照类型注入的,如果同一个类型存在多个对象,按名称匹配,如果名称匹配不上就会报错。