目录
1 loC控制反转思想
2 DI依赖注入
3 loC详解
3.1 存储Bean
(1)@Controller
(2)@Service
(3)@Repository
(4)@Component
(5)@Configuration
(6)@Bean
(7)Bean对象命名规范
(8)5大类注解之间的关系
3.2 获取Bean
(1)ApplicationContext
(2)三种常用的获取方式
(3)获取多个@Bean注解的Bean
(4)Bean重命名
3.3 Spring扫描路径
(1)默认路径
(2)@ComponentScan
3.4 ApplicationContext和BeanFactory区别
4 DI详解
4.1 属性注入
4.2 构造方法注入
4.3 Setter方法注入
4.4 三种方式优缺点
(1)属性注入
(2)构造方法注入
(3)Setter方法注入
4.5 @Autowired原理
(1)@Autowired原理与问题
(2)解决方法
(3)@Autowired与@Resource区别
Spring家族包含:Spring Framework、SpringMVC、SpringBoot、Spring Cloud等等框架,而我们常说的Spring框架就是指Spring Framework。
Spring框架(Spring Framework)的推出是为了简化Java程序的开发(快速、简单、安全)。它是包含众多工具方法的loC(控制反转)容器。即Spring是一个容器,其内部装了很多的对象,这个容器蕴含了loC思想。
1 loC控制反转思想
建房子首先需要用各种建材盖出毛坯房,然后再对毛坯房进行装修,得到成品房,成品房再交付到客户手中。用面向对象的程序设计思想就需要三个类:Ston类表示用建材盖毛坯房、Decoration类表示对毛坯房进行装修、House类表示把成品房交付给客户。
// 建房子用的建材public class Stone {public Stone() {System.out.println("使用建材数量: " + 20 + " 建成毛坯房");}}// 毛坯房装修public class Decoration {private Stone stone;public Decoration(){stone = new Stone();System.out.println("对毛坯房进行装修");}}//成品房交付public class House {private Decoration decoration;public House(){decoration = new Decoration();System.out.println("成功交付房子");}}
在这个流程中,House类的实例需要Decoration类的实例,Decoration类的实例需要Stone类的实例。我们称这种关系为:House实例依赖Decoration实例,Decoration实例依赖Stone实例。
如果现在想要修改Stone类,比如添加一些属性、修改一些方法的参数时,情况就如下述:
public class Stone {private int num;public String name;public Stone(int num,String name) {this.num = num;this.name = name;System.out.println("使用建材数量: " + this.num + " 建成毛坯房: " + this.name);}}public class Decoration {private Stone stone;public String name;public Decoration(int num,String name){stone = new Stone(num,name);this.name = name;System.out.println("对毛坯房:" + this.name + "进行装修");}}public class House {private Decoration decoration;public House(int num,String name){decoration = new Decoration(num,name);System.out.println("成功交付房子:" + decoration.name);}}
可以发现上述方案有一个很大的问题:调用链最底层的代码需要修改,整个调用链的类都需要进行修改,程序耦合度很高。
如果改变实例创建顺序,让每个实例的创建都不由其他类控制,而通过依赖对象注入的方式,即把实例传递给每个类的构造方法,这样每个类的修改就不需要修改其他类的代码了,实现了对象的解耦。
public class Stone {private int num;public String name;public Stone(int num,String name) {this.num = num;this.name = name;System.out.println("使用建材数量: " + this.num + " 建成毛坯房: " + this.name);}}public class Decoration {private Stone stone;public String name;public Decoration(Stone stone){name = stone.name;System.out.println("对毛坯房:" + name + "进行装修");}}public class House {private Decoration decoration;public House(Decoration decoration){System.out.println("成功交付房子:" + decoration.name);}}public class Test {public static void main(String[] args) {Stone stone = new Stone(20,"花园3单元301");Decoration decoration = new Decoration(stone);House house = new House(decoration);}}
原先对象的创建顺序是:House对象=>Decoration对象=>Stone对象,对象的创建依赖其他对象。而采用依赖对象注入的方式后,对象的创建顺序就变为:Stone对象=>Decoration对象=>House对象,对象的创建不由其他对象负责,而采用注入的方式,即每个对象的创建控制权发生了反转。这就是loC控制反转的思想。
而Spring是loC容器,loC容器体现在上述Test类的代码,即由Spring负责创建对象并管理对象等资源。因此loC容器具有以下优点:
1.资源集中管理:loC容器帮助我们管理一些资源,包括对象等。需要使用这些资源时,直接从容器取。
2.耦合度降低:降低了资源之间的依赖程度,最典型的就是上述对象间的创建不再依赖其他对象的实现细节,某个对象修改时,由于是依赖对象注入的方式,不需要修改其他对象。
2 DI依赖注入
DI依赖注入:容器运行期间,动态地为应用程序提供依赖的资源的过程,就是依赖注入。
DI和loC是从两个角度描述同一件事,loC是思想,DI是其具体的实现,loC强调要将对象等资源交给容器来管理,那么该如何管理呢?DI就是其实现管理的方式,即依赖注入,需要时把对象从容器中取出来注入到需要使用的地方。
Spring把对象称为Bean,Bean存放在loC容器中,因此loC容器主要提供两个功能:存和取。存就是loC容器体现的方面,取就是DI体现的方面。
3 loC详解
如何把Bean存储到loC容器中,这就牵扯到5大类注解:@Controller、@Service、@Repository、@Component、@Configuration,1个方法注解:@Bean。
3.1 存储Bean
(1)@Controller
@Controllerpublic class UserController {public void hello(){System.out.println("hello userController");}}
直接在要交给Spring管理的对象的类前加上@Controller,其他的5大注解类似。Spring在启动时就会扫描该路径并创建对象放到loC容器中。
(2)@Service
@Servicepublic class UserService {public void hello(){System.out.println("hello userService");}}
(3)@Repository
@Data@Repositorypublic class UserInfo {private Integer id;private String name;private Integer age;public UserInfo() {}public UserInfo(Integer id, String name, Integer age) {this.id = id;this.name = name;this.age = age;}}
(4)@Component
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}}
(5)@Configuration
@Configurationpublic class UserConfiguration {public void hello(){System.out.println("hello userConfiguration");}}
(6)@Bean
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}@Beanpublic UserInfo getUser1(){UserInfo user1 = new UserInfo(100,"zhangsan",20);return user1;}@Beanpublic UserInfo getUser2(){UserInfo user2 = new UserInfo(101,"lisi",21);return user2;}}
@Bean注解必须搭配类注解使用,如果要定义多个对象(类是同一个,但对象不是同一个),就需要使用多次@Bean注解。
为什么要用到@Bean注解?上述类注解存在两个问题:1.如果外部类的对象我们也想交给Spring来管理,那么没有办法在外部类加类注解(总不能改别人的源代码吧)2.如果一个类需要注册多个对象,多个对象的属性的值不同,通过类注解没有办法实现(类注解使用了单例模式,对象全局只有一个,这点在后面获取Bean会得到印证)。而@Bean注解就可以解决上述问题。
(7)Bean对象命名规范
在java.beans.Introspector类中的decapitalize定义了Bean的命名规范:
public static String decapitalize(String name) {if (name == null || name.length() == 0) {return name;}if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&Character.isUpperCase(name.charAt(0))){return name;}char[] chars = name.toCharArray();chars[0] = Character.toLowerCase(chars[0]);return new String(chars);}
默认如果类的名称前两个字母是大写的,则Bean对象名称和类名一致;如果类的名称不符合这种特殊情况,则首字母小写(小驼峰)。而方法注解@Bean所创建的对象的名称,就是方法名。
(8)5大类注解之间的关系
观察@Controller、@Service、@Repository、@Configuration四个注解的源码发现,都含有@Component注解,说明那4个注解是@Component注解的衍生类(子类),而@Component是元注解。
那有了@Component就可以把对象创建出来交给Spring管理,为什么还需要其他注解?这是开发模式的需要:常用的开发模式是后端分为Controller层、Service层和Dao层,Controller层用于处理请求和响应,Service层用于业务逻辑,Dao层用于和数据库交互。对Controller层使用@Controller,对Service层使用@Service,对Dao层使用@Repository,便于项目结构的清晰,这已经成为了一种开发规范。
注意:@RestController也具有创建Bean对象并存储到loC容器中的功能,这是由于@RestController=@Controller+@ResponseBody。但是如果只对Controller层使用@Component注解和@ResponseBody,就会导致请求url分级的一些问题。
3.2 获取Bean
(1)ApplicationContext
因为Bean对象都交给Spring管理了,所以获取Bean就需要用到Spring的上下文(它可以看做一个容器,存储了Spring运行时需要用到的环境和对象)。
而在启动类(@SpringBootApplication)中,SpringApplication.run()方法会返回一个对象,类型是ConfigurableApplicationContext,它的父类是ApplicationContext,我们获取Bean就是通过该对象获取的。
同时,也可以直接在在其他类中通过DI(依赖注入)注入ApplicationContext对象,使用@Autowired注解注入ApplicationContext对象。这里的注解在后面会讲。
(2)三种常用的获取方式
@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);// 获取Bean对象方式1:通过类型获取UserController userController1 = context.getBean(UserController.class);userController1.hello();System.out.println(userController1);// 获取Bean对象方式2:通过对象名称获取UserController userController2 = (UserController) context.getBean("userController");userController2.hello();System.out.println(userController2);// 获取Bean对象方式3:通过类型+对象名称获取UserController userController3 = context.getBean("userController", UserController.class);userController3.hello();System.out.println(userController3);}}
观察结果可以发现,我们使用三种方式获得的Bean对象是同一个,这说明UserController的对象全局中只有一个,这是由于Spring对于Bean的创建遵循单例模式。
方式1不适合获取多个Bean对象,而方式2和3都适合。
(3)获取多个@Bean注解的Bean
@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);UserInfo user1 = context.getBean("getUser1", UserInfo.class);UserInfo user2 = context.getBean("getUser2", UserInfo.class);System.out.println(user1);System.out.println(user2);}}
注意到,获取@Bean注解的对象,需要使用带对象名称的方式(2或3),而对象的名称就是方法名。
(4)Bean重命名
要重命名时,直接在注解中填入要重命名的名称(类注解和方法注解都适用)。比如:
@Controller("UserController")public class UserController {public void hello(){System.out.println("hello userController");}}@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);// 默认名称是userController(类的首字母小写), 重命名为UserControllerUserController userController1 = (UserController) context.getBean("UserController");userController1.hello();System.out.println(userController1);}}
注意:Java对类名的大小写敏感,不允许通过首字母大写和小写区分不同的类,比如UserController和userController,不能表示为两个不同的类,必须是同一个类,这也是命名规范。
3.3 Spring扫描路径
(1)默认路径
Spring默认的扫描路径是启动类所在的路径,在该同级目录下,Spring启动时都可以扫描到其中的注解,然后依次创建对象,存储在loC容器中。
(2)@ComponentScan
如果想要Spring也扫描启动类所在路径外的路径,就需要使用@ComponentScan(“要扫描的路径(包的全名)”),将该注解放在启动类前(@SpringBootApplication所在的位置)。如果要额外扫描多个路径,就用{}把所有的路径都括起来。
@SpringBootApplication包含@ComponentScan,其默认扫描路径就是启动类所在的路径。
3.4 ApplicationContext和BeanFactory区别
注意到,getBean()方法实际上是BeanFactory接口提供的方法,而ApplicationContext接口继承了ListableBeanFactory和HierarchicalBeanFactory,这两个接口的父类就是BeanFactory。因此:
1.从继承角度来讲,BeanFactory提供了基础的访问容器的能力,而ApplicationContext属于BeanFactory的子类,它除了继承了BeanFactory的所有功能之外,还拥有独特的特性,还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持。
2.从性能角度来讲,ApplicationContext是一次性加载并初始化所有的Bean对象(空间换时间)。而BeanFactory是需要哪个才去加载哪个,更加轻量。
4 DI详解
DI是依赖注入,体现在把loC容器中管理的对象动态的注入到需要的地方。有3种注入的方式:属性注入、构造方法注入和Setter方法注入。
4.1 属性注入
@Controllerpublic class UserController {@Autowiredpublic UserService userService;public void hello(){System.out.println("hello userController");userService.hello();}}@SpringBootApplicationpublic class SpringmvcApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringmvcApplication.class, args);// 依赖注入1UserController userController1 = context.getBean(UserController.class);userController1.hello();}}
属性注入是通过在需要注入对象的属性前加上@Autowired注解,运行结果可以看到,成功的把UserService的对象注入到UserController中。
4.2 构造方法注入
如果只有一个构造方法,只使用构造方法即可注入:
@Controllerpublic class UserController {public UserService userService;public UserController(UserService userService) {this.userService = userService;}public void hello(){System.out.println("hello userController");userService.hello();}}
如果有多个构造方法,就必须使用@Autowired注解+构造方法指明应该使用哪个构造方法来注入属性:
@Controllerpublic class UserController {public UserService userService;public UserConfiguration userConfiguration;public UserController(UserService userService) {this.userService = userService;}@Autowiredpublic UserController(UserService userService, UserConfiguration userConfiguration) {this.userService = userService;this.userConfiguration = userConfiguration;}public void hello(){System.out.println("hello userController");userService.hello();userConfiguration.hello();}}
4.3 Setter方法注入
@Controllerpublic class UserController {public UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void hello(){System.out.println("hello userController");userService.hello();}}
Setter方法注入也需要搭配上@Autowired注解。
4.4 三种方式优缺点
(1)属性注入
优点:使用简单。
缺点:1.只能用于loC容器(Spring框架),不能用在非loC容器。2.并且只有使用时才能知道Bean是否为Null。3.不能注入final类型的属性,因为final类型的属性需要声明时就初始化/声明时不初始化但是需要在构造方法初始化(那还不如用构造方法注入)。
(2)构造方法注入
优点:1.可以注入final类型的属性。2.一旦注入对象后不会被修改。3.依赖的对象在使用前就可以被初始化完成,因为依赖注入是在构造方法中完成的,而构造方法在类加载时期就会执行。4.通用性好,构造方法属于JDK的,更换其他框架也适用。
缺点:注入多个对象,代码会繁琐,因为可能需要写多种构造方法。
(3)Setter方法注入
优点:方便在类实例后,重新对对象进行配置或注入。
缺点:1.不能注入final类型的属性。2.注入的对象有被修改的风险,因为Setter方法在运行期间可以被多次调用。
4.5 @Autowired原理
(1)@Autowired原理与问题
@Autowired的原理是先根据对象类型获取对象,如果存在一个就注入。如果匹配到多个,再根据对象名称获取对象,此时如果获取到对象就注入,否则就报错有多个同一类型的类型。
因此,如果通过对象类型获取对象的时候,同一个类型匹配到多个Bean,就会报错。
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}@Beanpublic UserInfo getUser1(){UserInfo user1 = new UserInfo(100,"zhangsan",20);return user1;}@Beanpublic UserInfo getUser2(){UserInfo user2 = new UserInfo(101,"lisi",21);return user2;}}
(2)解决方法
修改属性名为对象名称:不建议使用,因为通常认为变量名与程序的业务逻辑无关,即修改变量名不影响程序的运行。但是这里如果修改变量名就会报错。
@Primary:在多个Bean的某一个前加上@Primary,表示该Bean是优先被注入的对象。
@Componentpublic class UserCompeonent {public void hello(){System.out.println("hello userCompeonent");}@Primary@Beanpublic UserInfo getUser1(){UserInfo user1 = new UserInfo(100,"zhangsan",20);return user1;}@Beanpublic UserInfo getUser2(){UserInfo user2 = new UserInfo(101,"lisi",21);return user2;}}@Controllerpublic class UserController {@Autowiredpublic UserInfo user1;public void hello(){System.out.println("hello userController");System.out.println(user1);}}
@Qualifier:搭配@Autowired注解使用,在要注入的属性前加上@Qualifier(“对象名称”),指定要注入的对象。
@Controllerpublic class UserController {@Qualifier("getUser2")@Autowiredpublic UserInfo user;public void hello(){System.out.println("hello userController");System.out.println(user);}}
@Resource:不使用@Autowired注解进行注入了,直接使用@Resource(name=“对象名称”)进行注入。
@Controllerpublic class UserController {@Resource(name = "getUser1")public UserInfo user;public void hello(){System.out.println("hello userController");System.out.println(user);}}
(3)@Autowired与@Resource区别
1.从通用性角度来讲:@Autowired是Spring框架提供的注解,只适用Spring框架搭建的程序中。@Resource是JDK提供的注解,使用与Java的所有范围。
2.从注入方式来讲:@Autowired初始按对象类型注入,没有找到对象就按对象名称注入。@Resource是对象名称注入,并且支持更多参数,最简单的就是按name参数进行按对象名称注入。
下篇文章:
Spring-AOPhttps://blog.csdn.net/sniper_fandc/article/details/148960179?fromshare=blogdetail&sharetype=blogdetail&sharerId=148960179&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link