通过五大类注解可以更便捷的将对象存储到SpringIOC容器中,但是我们也同样可以使用注解将存储进来的对象取出来直接赋值到注册类所在的一个属性中,这个过程叫做对象的装配或者是叫做对象的注入,也叫做DI
1)之前获取SpringIOC容器中的Bean对象都是通过Spring的上下文对象来获取的比如说ApplicationContext对象或者是BeanFactory对象,但是现在期望通过注入的方式来获取到Bean对象
2)我们把获取到Bean对象也叫做Bean对象装配,在A类里面使用B对象,把B对象获取,也就是说从IOC容器中将对象读取出来,而不是用上下文;
2.1)属性字段注入
2.2)构造方法注入,注意构造方法只有一个的时候,@Autowried是不可以进行省略的,但是如果构造方法有多个,那么@Autowried就不可以省略了;
2.3)Setter注入
1)属性注入(属性注册)
比如说我要在UserController里面调用UserService对象的方法,我们最早的方法是创建一个UserService对象
2)Setter注入
//1将UserService注册到当前类里面(使用Setter注入的方式),首先先进行创建一个属性,定义一个属性 private UserService userService; //2.给这个属性设置一个Setter方法,并给Setter方法加上一个@Autowired的注解 @Autowired//如果说缺少了这个注解就会发生空指针异常 public void setUserService(UserService userService) { this.userService = userService; } //3新创建一个方法,在方法里面调用UserService的查询方法,并返回用户对象 public User FindUser(Integer id) { if(id==null) { return null; } else{ User user= userService.run(id); return user; } }
Setter注入里面需要进行注意:在Setter方法上面是不可以省略@Autowried,否则就会注入失败,为了进行验证属性注入的方式,做了以下几个工作:
1)先在UserService里面写了一个方法,根据不同的用户ID向数据库中查询用户并返回;
@Service public class UserService{ public User run(Integer id) {//前端传过来的参数可能是空,根据用户ID查询数据库; if (id == 1) { User user = new User(); user.setId(id); user.setName("周韵杠"); return user; }else{ User user=new User(); user.setName("胡子"); user.setId(id); return user; } } }
2)再从UserController中写一段代码,先获取到UserService对象,在调用UserService里面的方法获取到User对象
//将当前的类注册到Spring里面 @Controller public class UserController { public void start( String name) { System.out.println("当前的类已经启动,我是被Contraller注解修饰的"+name); } //1.属性注入,属性注册,从Spring里面进行获取一个对象,并注册到当前类,在属性上加上注解 @Autowired private UserService userService; public User findUser(Integer id){ if(id==null) { return null; } User user=userService.run(id); return user; } }
3)在启动类里面里面先获取到UserController对象
//1.先进行获取Spring的上下文对象 ApplicationContext context=new ClassPathXmlApplicationContext("web.xml"); //2.得到Controller对象 UserController userController=context.getBean("userController",UserController.class); //调用findUser方法进行查询用户,实际上的查询是在UserService里面进行查询的 User user=userController.findUser(1); System.out.println(user); /我们写这个方法是为了进行验证,咱们的UserService类是否成功的注入到了UserController里面
2)通过构造方法来进行注入Bean对象
都是通过@Autowired这个注解来实现注入的,使用@resource来实现对象的注入,它的用法是和@Autowired的用法是相同的;
//1.使用构造方法来进行注入对象,并先进行创建一个要注入的对象 private UserService userService; //2创建构造方法,给构造方法上面添加@Autowired注解 @Autowired public UserController(UserService userService) { this.userService = userService; } //UserService userservice;这是通过传统的方法来实现注入的 //3调用Service方法来返回User对象 public User FindUser(Integer id) { if(id==null) { return null;} else{ User user= userService.run(id); return user; } }
@Autowired和@Resource有什么区别?
@Resource和Autowried都可以实现将一个对象注入到当前类里面都是通过注入的方式从IOC容器中获取到Bean对象,在SpringBoot里面都是将依赖对象注入到当前对象的功能
1)@Resource是来源于JDK,但是@Autowired是来源于Spring框架提供的;
2)针对用法来说:我们通过不断的测试可以得知,我们使用@resource注解的方式只能通过属性注入和Setter注入的方式来进行获取对象,但是如果使用构造器的方式就会发生报错,但是@Autowired可以进行属性注入,Setter注入,构造器注入;
3)功能不同,@Resource本身可以配置更多的属性进行使用可以设置7个属性,可以同时设定name和type参数,我们就可以通过name和type来获取到Spring容器中的Bean对象
@Resource可以配合name属性和type来进行使用,从而完成对象的别名注入
但是@Autowired支持的属性比较只支持required()设置
@Autowired(request=“true”)
当@Autowired没有显示声明时默认为true
这个时候注入,该bean必须存在,否则就会注入失败,并报错
但是可以配合,@Qualifier(value = "u1")来进行使用
@Autowired(request=“true”)
当@Autowired没有显示声明时默认为true
4)Autowired是先根据类型进行查找,如果存在多个Bean就按照名字来进行查找
Resource是先根据名称进行查找,如果名称查询不到,那么就根据类型进行查找
如果注入的名称是不存在的,并且类型有多个那么注入就会失败
5)编译器提示不同,当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,设置quired属性即可,而使用 @Resource 注解则不会提示错误
总结:private修饰的对象名要么方法名字是相同的,要么和@bean中的name属性有一个相同
解决相同类型的多个Bean的报错
1)@Resource(name = "使用@Bean修饰的方法名或者是@Bean中的name属性")
@Component public class GetBean { @Bean(name ="u1")//将当前User对象存储到Spring里面 public User GetUser1(){ User user=new User(); user.setId(1); user.setName("李佳伟"); return user; } @Bean(name="u2") public User GetUser2(){ User user=new User(); user.setName("张某某"); user.setId(2); return user; } } @Controller public class Controller1 { @Resource(name ="u1") private User user;//这里面的user变量名既不和方法名相同,还不等于被@Bean修饰方法中的name属性 public User getUser() { return user; } }
@Autowried加上@Qualifier中的value属性来进行搭配使用
@Component public class GetBean { @Bean(name ="u1")//将当前User对象存储到Spring里面 public User GetUser1(){ User user=new User(); user.setId(1); user.setName("李佳伟"); return user; } @Bean(name="u2") public User GetUser2(){ User user=new User(); user.setName("张某某"); user.setId(2); return user; } } @Controller public class Controller1 { @Autowired @Qualifier(value = "u1") private User user; public User getUser() { return user; } }
3)使用上下文对象进行获取的时候,ID直接变成@Bean中的name属性或者是方法名字
3)@Bean命名规则:
当我们没有进行设置name属性的时候,那么Bean默认的就是方法名字,当我们设置了name属性之后,只能通过重命名的name属性对应的值来进行获取,也就是说重命名之后再次使用方法名就再也获取不到Bean对象了
解决相同类型的对象注入解决方法:
1)精准的描述被@bean修饰的名称,通过方法名或者方法名+返回值的类对象来进行获取Bean对象:Spring上下文对象.getBean(方法名,返回值类名.class)
或者是说通过设置的name属性
@Autowired private User GetUser;//把这个引用换成对应的方法名
2)使用@Resource设置name的方式来重命名注入对象
3)通过@Autowried+@Qualifier中的value属性来进行筛选Bean对象
1)依赖于变量名字:通过类型和名称精准匹配
下面的注入既可以通过@Resource也是可以通过@Autowired注解来进行注入的:
2)不依赖于变量名字:
1)通过@Resource的name属性:
2)通过@Autowried搭配使用的@Qualifier的value属性
注入方式是没有发生变化,但是构造方式所被修饰的注解发生了变化:
4)依赖注入方式的理解:
4.1)属性注入也叫作字段注入:
在UserController对象中想要使用UserService的方法
1)UserService里面的代码:
@Service public class UserService { public void start(){ System.out.println("我是UserService里面的方法"); } }
2)UserController里面的代码:
@Controller public class UserController { //1.将UserService注入到我的一个私有属性上面了,我们的原有的一个目的 是在UserController里面调用UserService里面的方法 //2.但是我们不想他通过new的方式来进行获取到UserSerice的实例 我们就可以通过属性注入的方式来进行获取 //3.告诉Spring在我们进行加载UserController类的时候 先将UserService对象注入到这个类里面 //4.这样获取到这个UserService对象就不依赖于Spring上下文对象 也不在需要GetBean方法来进行获取了,这样就加一个注解就可以了 (缺点是底层的类发生改变,整个想上调用链的类都要发生改变) @Autowired private UserService service; public void start(){ //传统的方式:UserService userservice=new UserService(); service.start(); } }
此时我们就可以在启动类中,使用上下文对象来获取到
UserController
对象,通过执行UserController
对象的sayHi
方法来进而调用到注入的UserService
对象中的start方法了,此时的UserService
对象就不是我们自己new
出来的了,上面说的是同类对象只有一个的情况,而如果存在多个同类对象,我们就得通过参数来告知容器我们要注入哪一个对象,不告知就会报错简单理解就是注入方式越简单,滥用的概率越大,就比如在数据持久层有一个针对用户操作的类,本来这个类就只是注入用户相关操作的依赖就行了,但由于属性注入使用起来成本不高,程序猿就多注了一些依赖去完成了一些和用户操作无关的内容,这就违背了单一设计原则了
4.2)构造方法注入:调用被注入的类的构造方法
对了,如果一个类中只有一个构造方法,@Autowired 是可以省略的
如果有多个构造方法,要注意此时是不能省略@Autowired 的,会导致会注入对象失败,直接注入的对象就是null,还要注意@Resource注解是不支持构造方法注入的;
1)UserService里面的代码:
@Service public class UserService { public void start(){ System.out.println("我是UserService里面start方法"); } }
2)UserController里面的代码:
@Controller public class UserController { public UserService userService; @Autowired public void setUserService(UserService userService) { this.userService = userService; } public void start(){ userService.start(); } }
@Controller public class UserController { private UserService service; @Autowired public UserController(UserService service){ this.service=service; 1这就表示我在启动UserController的时候,加载构造方法,就从Spring中 读取到一个UserService出来 再把构造方法的UserService赋值给这个属性 2也就说明在创建UserController实例的时候就 可以将UserService对象注入到UserController里面了 要比属性注入麻烦 3我们如果通过构造方法注入的方式来进行获取到Bean对象 如果说当前的UserController中只有一个构造方法,那么此时@Autowired注解是可以省略的 4此时如果UserController里面有多个构造方法,那么就不可以进行省略这个注解了 因为就不知道给哪一个构造方法注入了 } public void start(){ service.start(); } }
@Autowired 依赖注入流程首先是先根据类型从容器中获取对象,如果只能获取到一个,那么就直接将此对象注入到当前属性上;如果能获取到多个对象,此时会使用
BeanName
进行匹配,而我们添加到 Spring 中的对象是没有一个叫user
的,所以程序就报错了1)在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀ 个 User 对象,返回给 main ⽅法。Repository ⽆需连接数据库,使⽤伪代码即可;
2)首先要清楚的是在 main 方法中是不能使用依赖注入的,因为类的静态部分是在 Spring 注入之前的加载的,仔细想一下,在类加载时就要使用一个还没注入的对象这是不现实的。
所以我们要在 main 中执行的是将扫描路径中的类添加到 Spring 中,对象的注入要在 mian 方法所在类的外部去实现
解决方法:
1)将构造方法形参名字设置成beanname,变量名等于beanname的名字
2)@Autowried搭配@Qualfier中的value来使用,里面使用的是方法名字
构造方法只有一个构造方法不会报错:
有多个构造方法无法注入:调用会出现空指针异常
4.3)Setter注入:
三种注入方法有什么区比呢?对象注入有几种方式?有什么区别?
一个类中的属性(其他对象)不再需要手动new或者通过工厂方法进行创建,而是Spring容器在属性被使用的时候给予注入
4)对象注入方式区别:
3.1)之前不推荐构造方法注入是因为构造方法支持多个参数,参数过多,而Setter很单纯
3.2)后来Spring官方又说,如果你的构造方法里面所注入传递的参数过多,不是我Spring的问题,而是你写代码的这个人的问题,你要检查一下你当前的这个类的编写是否符合单一设计原则的规范
3.3)官方自己使用的时候,也是经常使用构造方法注入
3)Setter注解时Spring前期版本推荐的注入方式写法,但是通用性是不如构造方法的,有多个注册类的时候,只需要多写几个Set方法就可以了
3.1)不能注入不可变对象,他是符合单一职责的设计模式,因为每一个Setter注入只针对一个对象
3.2)况且他注入的对象可以被修改,Setter注入你提供了setXXX的方法,意味着你可以在任何时候,都可以调用setXXX方法来进行修改注入的对象,十分不安全
3.3)在不同的语言中,构造方法的写法都是一样的,但是Set方法在不同的语言中有不同的写法,在Java中后续几乎很少写Set方法,直接用lombok注解来进行代替,所有构造方法注入通用性更好
1)属性注入的优点是写法简洁,使用方便,其他的两种注入方式使用起来都是很麻烦的,但是他的缺点也是很明显的
1.1)它只能用于IOC容器,如果是非IOC容器那么是不可用的,况且在非IOC容器使用的时候使用的时候会出现空指针异常,通用性太差
1.2)在你进行使用的时候,连编译器都会告诉你不建议使用此注入方式
1.3)还不可以进行注入一个final修饰的对象,不可以注入不可变对象
当使用构造方法进行注入的时候
2.1)如果需要注入的对象比较多,那么咱们的构造方法比较臃肿,构造参数较多,这时Spring前期不推荐构造器进行注入的理由;
2.2)但是后期官方又说,如果出现臃肿的情况,构造方法注入参数比较多了,是应该进行考虑一下当前的类是否符合程序的单一职责的设计模式了,是否规范;
2.3)进行构造方法注入的时候,如果只有一个构造方法,那么注解可以省略,它是可以注入不可变对象的;
在我们进行使用之前,一定要保证使用之前一定能把保证注入的类不为空,简化设计模式很重要;
下面的这几个变量都指向了同一个UserService,况且这个Service是单例模式的 private UserService userService1; private UserService userService2; private UserService userService3; @Autowired public UserController(UserService userService1, UserService userService2, UserService userService3) { this.userService1 = userService1; this.userService2 = userService2; this.userService3 = userService3; }
使用这种方式优点:
1)可以注入不可变对象
2)注入对象不会被修改,这是相对于Setter注入来说的,因为构造方法只会在对象创建的时候执行一次,因此他是不存在Setter被随时调用或者修改的情况
3)完全初始化:因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全进行初始化
4)通用性更好,构造方法适用于IOC容器和非IOC容器,况且各个语言的构造方法的写法都是互通的