名词解释
今天我们来介绍Spring框架的最重要的part之一 SpringIOC 和 DI
这里的SpringIOC 其实是容器的意思,Spring是一个包含了很多工具方法的IOC容器
什么是IOC呢?
IOC其实是Spring的核心思想
Inversion of Control (控制反转)
可能这里你还是不理解这个是啥意思 其实就是我们一般创建对象的方式是使用new等方式创建,但是这里我们让Spring帮助我们创建对象,程序中只需要进行DI(对象注入)即可
Spring是一个IOC容器,所以有时候Spring也称之为Spring容器
这里举一个生活上的例子来解释一下SpringIOC的含义
我们招聘的时候,其实企业员工的入职和解雇等操作都得老板自己来,但是这里我们老板转给了HR来处理
案例对比
传统方式
假如我们需要实现这么一个依赖关系的思路
我们第一版本先使用传统方式进行操作一下
//轮胎 public class Tire { private int size; private String color; public Tire(int size) { System.out.println("tire size:"+size); } } //底盘 public class Bottom { private Tire tire; public Bottom(int size) { tire = new Tire(size); System.out.println("tire init..."); } } //框架 public class Framework { private Bottom bottom; public Framework(int size) { bottom = new Bottom(size); System.out.println("bottom init...."); } } //汽车 public class Car { private Framework framework; public Car(int size) { framework = new Framework(size); System.out.println("framework init..."); } public void run() { System.out.println("car run..."); } } //启动类 public class Main { public static void main(String[] args) { Car car = new Car(10); car.run(); } }
这里我们发现,我们如果对车万一需要进行一些操作,就会导致牵一发而动全身的效果
比如,如果我们为车的轮胎指定轮胎的厂家,这样我们就需要从轮胎类逐个的向上进行修改
这样程序的耦合度就太高了,于是我们现在介绍一下SpringIOC的方式来管理对象
注:这里只是模拟SpringIOC的方式来创建对象,这里SpringIOC其实我们也可以理解为一种思想,本质上要求交给Spring管理的对象是无状态的
SpringIOC版本
这里我们可以将对象的创建放到一块儿,Spring框架的想法是将对象的创建交给Spring来,我们向使用的时候只需要使用DI注入即可
这里我们先模拟一下程序的解耦,避免这种牵一发而动全身的操作存在
//bottom public class Bottom { private Tire tire; public Bottom(Tire tire) { this.tire = tire; System.out.println("tire init..."); } } //Car public class Car { private Framework framework; public Car(Framework framework) { this.framework = framework; System.out.println("framework init..."); } public void run() { System.out.println("car run..."); } } //Framework public class Framework { private Bottom bottom; public Framework(Bottom bottom) { this.bottom = bottom; System.out.println("bottom init...."); } } //Tire public class Tire { private int size; private String color; public Tire(int size, String color) { System.out.println("tire size:"+size+",color:"+color); } } //Main public class Main { public static void main(String[] args) { Tire tire = new Tire(17, "red"); Bottom bottom = new Bottom(tire); Framework framework = new Framework(bottom); Car car = new Car(framework); car.run(); } }
这里我们发现了对象创建的思路是这样的
传统方式是创建Car再去找需求的依赖,是一个从下到上的结构
而IOC的方式是将对象依赖注入,是改进之后的控制权反转
这里我们可以看出IOC思维的优势
1.减少了类之间的耦合度
2.便于对象的集中管理
DI
解释就是依赖注入的意思,我们刚刚这种传对象的操作其实理论依据就是DI
IOC详解
下面我们进行SpringIOC的详解
首先我们提出一个Bean的概念 Spring中的对象称之为Bean
我们如果想把某些对象交给Spring管理,我们可以用以下注解来修饰
1.类注解
@Repository @Service @Component @Configuration @Controller
2.方法注解
@Bean
注:Spring也不一定需要在web网页上显示,我们也可以在控制台打印
首先我们来尝试一下这五大类注解
@Component public class UserComponent { public void doComponent() { System.out.println("UserComponent"); } } @Configuration public class UserConfig { public void doConfig() { System.out.println("UserConfig doConfig"); } } @Repository public class UserRepo { public void doRepo() { System.out.println("UserRepo"); } } @Service public class UserService { public void doService() { System.out.println("UserService doService"); } } @Controller public class helloTest { public void hello() { System.out.println("hello"); } } //启动类 @SpringBootApplication public class SpringIocApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringIocApplication.class, args); helloTest bean = context.getBean(helloTest.class); bean.hello(); } }
这里我们只需要用相同的模板就可以实现这里的IOC操作
这里我们介绍一下getBean的几个参数
我们可以根据类型找到类,可以根据类型+方法名找到类,也可以只根据方法名寻找类
注意这里Bean的名字是以小驼峰的方式去拿的
比如HelloTest 这里的Bean名称就是 helloTest
注意这里获取到的三个对象都是同一个对象
Bean的命名规则:如果只有开头字母是大写的,那么我们就将第一个字母变成小写的,如果前两个都是大写字母,那么我们就直接返回这个名字
假设这里我们使用的类名是HElloTest,我们使用首字母小写小驼峰的方式就会直接失败
这里我们可以看到日志显示找不到这个Bean 实际上开头两个字母都大写Bean就不会修改名称,直接使用原名即可获取
使用的就是这个方法
注:idea中查询类 使用 ctrl + shift + alt + n
再查询方法可以使用ctrl + f查询
Application和BeanFactory之间的关系?
1.父子关系 BeanFactory有的功能Application都有
2.除此之外Application有很多其他的功能
3.BeanFactory是一个懒加载的方式
Application是一个预加载的方式,效率更高
五大注解的对比
我们看到除了component其他的四大注解基本上都是一样的
可以混用嘛??
理论上可以,但是工作中一般不允许混用,但是其中的界限分割的不明确,有的时候还是混用的
@Controller @Conponent 主键
@Service 业务逻辑层 @Repository 数据层 @Configuration 表现层
注:这里的Controller有特殊用处,其余三个可以在理论上混用(事实上也可以)
其余四个注解都包含了Component
注:@Controller如果进行替换不一定能实现路由映射
注:这里不加上注解时无法将对象交给Spring管理的
这里我将@Controller注解删除
Spring就会获取不到这个Bean
下面开始使用Bean的方式书写
注意:@Bean的使用也需要配合五大注解一起使用
Spring不可能扫描所有的项目文件,这里我们就使用五大注解来标识一下需要扫描的文件
@Bean的命名方式就是方法名
Spring扫描的默认是哪些文件呢?
我们先做一个测试,将启动类放到component包下
这时候去获取component的类就是可以的,获取HelloTest的类就是失败的
Spring扫描的三大条件
1.使用五大注解标记类
2.使用Bean注解标记方法
3.默认扫描路径是启动类所在的目录以及子目录
如果我们向让其扫描其他的路径,就可以使用@ComponentScan(basePackages = "")
我们这样就可以获取User对象了
@Configuration public class UserConfig { @Bean public User doConfig() { User u1 = new User(); u1.setName("zhangsan"); u1.setId(1001); return u1; } }
DI注入
这里有三种方式注入
1.依赖注入/属性注入
2.构造器注入
3.setter注入
首先是依赖注入 我们只需要给需要交给Spring的对象加上五大注解,给需要注入的属性加上Autowired让他去Spring里面获取对应的对象即可
我们将注解干掉,使用构造器的方式也可以获取到
但是如果加上一个空参构造器,就会产生异常
这里出现空指针了,因为这里的Spring是调用构造函数来创建对象的,是使用反射的方式来操作的,这里有两个对应的构造函数了,默认就直接使用无参的构造函数了
如果我们再加一个属性和一个两个参数的构造函数
就会产生没有默认构造器的错误,这里我们可以使用@Autowired修饰默认构造方法解决
这样就可以正常运行啦
总结:
对于构造函数注入
1.只有一个构造函数的时候,无需操作
2.多个构造函数的时候需要@Autowired指定对应的构造函数
注:规范的写法是任何时候都把无参的构造函数加上,然后把想用的构造函数加上@Autowired注解即可
set方法+@autowired注解仍然可以完成任务
三种属性注入的优缺点
1.属性注入
优点是简洁并且书写方便
缺点也明显 只能用于IOC容器,使用的时候肯呢个出现空指针问题
不能注入final修饰的属性
2.构造器注入
优点:可以注入final修饰的属性
通用性好,JDK支持,所以任何框架都行
依赖对象使用前一定会被完全初始化
3.set方法注入
优点是方便在类实例之后重新堆起对象进行配置和注入
缺点是不能注入final修饰的属性
也可能被其他人修改
我们使用已有的bean来命名是可以正常执行内容的
但是只要我们改一下名字,立马就找不到对应的bean了
我们可以理解这里的查找方式是先根据类型和名称查询,查不到就根据类型查找,在没有就报错异常
这里可以使用三种方式去拿到对应的Bean对象
1.使用Primary注解给对应的Bean标记上,指定使用这个Bean
2.使用Qualifier注解,选出使用Bean的方法名
3.使用resourse注解加上方法名的方式也可以解决问题