SpringBoot 开发总结思考(一)
1、Spring、SpringMVC与SpringBoot的关系与区别
Spring 全称:Spring Framework,当谈论及Spring 的时候,实际上是使用Spring Framework + Mybatis 进行编程,而SpringMvc 是Spring Framework 中专门用于Web的模块
Spring 有两层意义:
.Spring 家族下所有的产品,是一个泛称
.特指 Spring Framework
2、Spring Framework 与 SpringBoot 之间的关系
SpringBoot 是新的产品,但它里面用的很多东西还是 Spring Framework,例如 @Component等
Spring 是 SpringBoot 的基础/应用
两者之间的区别肯定是有的,但更多的是上下的关系,SpringBoot是利用SpringFrameWork抽象出来的更好用的一个上层的框架
3、SpringBoot 相对于 SpringFramework 最核心的优势是什么?(自动配置)
自动配置(http://blog.didispace.com/spring-boot-disable-autoconfig/)
4、SpringBoot最基础的编程模式@Component+@Autowired
把对象注入到容器的方式有N多种,大体上分为两种
.XML
.注解(Stereotype annotations)
@Component(最基础、用的最多的):在一个类上加此注解,就会被SpringBoot扫描之后,加入到容器当中并负责类的实例化,然后在代码中需要的时候就可以提取出了
.编程方式(但需要结合注解,所以更多时候主要归结到注解)
@Component 示例
新建Diana 类
@Component public class Diana { public void q() { System.out.println("Camille 释放了 Q 技能"); } public void w() { System.out.println("Camille 释放了 W 技能"); } public void e() { System.out.println("Camille 释放了 E 技能"); } public void r() { System.out.println("Camille 释放了 R 技能"); } }
需要的时候使用属性注入
@Autowired private Diana diana;
@RestController @RequestMapping("banner") public class BannerController { @Autowired private Diana diana; @GetMapping("test") public String test(){ diana.e(); return ""; } }
使用构造函数注入
@RestController @RequestMapping("banner") public class BannerController { //@Autowired(required = false) //private Diana diana; public BannerController(Diana diana){ this.diana = diana; } @GetMapping("test") public String test(){ diana.e(); return ""; } }
使用Setter注入
@RestController @RequestMapping("banner") public class BannerController { //@Autowired private Diana diana; @Autowired public void setDiana(Diana diana) { this.diana = diana; } @GetMapping("test") public String test(){ diana.e(); return ""; } }
5、Stereotype Annotations 模式注解(以@Component为基础)
@Component:把一个组件/类/bean 加入到 IOC 中
.@Service
.@Controller
.@Repository
.以上三个注解其实在功能上,目前版本与@Component没什么区别
.如果不是一个服务可以使用@Component,如果是则
@Configuration:把一个组件/类/bean加入到容器的方式与上面的有所区别(未完待续)
6、Spring的实例化和依赖注入时机与延迟实例化
1.使用@AutoWired可以注入一个Bean,但是如果容器中没有这个Bean,程序会报错
.在默认情况下,Spring 无法在容器中找到要注入的Bean,则会报错
.在注入时允许为空,@Autowired(required = false),调用Bean时则会报错
1.IOC 容器的注入时机(IOC是什么时候实例化对象,并且把对象给注入到代码片段中的)
.应用启动时实例化(立即/提前实例化)
.不仅是启动时实例化,并且还会将对象注入到代码片段中(默认机制)
@Lazy 延迟实例化
但在启动时依然会实例化对象,因为Bean不是一个单独的对象,在其他类(例如Controller)中被引用了,也即Controller 依赖于该Bean
所以如果不在依赖方(Controller)加@Lazy ,Bean依然会是默认的实例化流程
7、@Autowired按类型注入,被动推断注入与主动选择注入
Diana
@Component public class Diana implements ISkill{ public Diana() { System.out.println("Hello, phubing"); } @Override public void q() { System.out.println("Diana 释放了 Q 技能"); } @Override public void w() { System.out.println("Diana 释放了 W 技能"); } @Override public void e() { System.out.println("Diana 释放了 E 技能"); } @Override public void r() { System.out.println("Diana 释放了 R 技能"); } }
Irelia
@Component public class Irelia implements ISkill{ public Irelia() { System.out.println("Hello, phubing"); } @Override public void q() { System.out.println("Irelia 释放了 Q 技能"); } @Override public void w() { System.out.println("Irelia 释放了 W 技能"); } @Override public void e() { System.out.println("Irelia 释放了 E 技能"); } @Override public void r() { System.out.println("Irelia 释放了 R 技能"); } }
ISkill
public interface ISkill { void q(); void w(); void e(); void r(); }
测试类
@RestController @RequestMapping("banner") public class BannerController { @Autowired(required = false) private ISkill diana; @GetMapping("test") public String test(){ diana.e(); return ""; } }
此时,程序运行是没问题的,但规范来讲,应该将 ISkill diana; 改为 ISkill iSkill ,这个时候程序运行就会出错
但变量名应该是由开发者定义的,而 Spring 的特点是约定大于配置,所以命名也有可能对代码的正确性造成影响
变量的名字对注入的 bean 是有影响的,如果定义为 ISkill diana 则会注入 Diana 这个类,优先级的规则是什么?
@Autowired 被动注入的方式
by type
.按照类型注入,默认注入形式,
.适用于接口只有一个实现类的情况
.找不到实现该接口的实现类,则会报错
.如果有多个类实现了接口,使用by type 方式注入失败的情况下,会根据命名推断进行注入
.根据类型推断到底应该从容器中注入哪个实现了该接口的 Bean
by name
.按照名字注入
以上都是SpringBoot 被动的进行判断,无论是 by type 还是 by name 都没有显式的指定注入的 Bean
使用 @Qualifier("diana") 显式(主动)指定注入的 Bean
@RestController @RequestMapping("banner") public class BannerController { @Autowired @Qualifier("diana") private ISkill iSkill; @GetMapping("test") public String test(){ iSkill.e(); return ""; } }
8、面向对象中变化的应对方案
1.指定一个接口,由多个类实现,然后这些类都有共同的接口(策略模式)
2.只有一个实现类,通过更改类的属性来适应变化(类似项目中定义的常量、配置文件)
9、@Configuration 配置类
对于没有明确指向/意义的 Bean 推荐使用@Component
在7的基础上增加一个类 Camille
public class Camille implements ISkill{ public Camille() { System.out.println("Hello, phubing"); } @Override public void q() { System.out.println("Camille 释放了 Q 技能"); } @Override public void w() { System.out.println("Camille 释放了 W 技能"); } @Override public void e() { System.out.println("Camille 释放了 E 技能"); } @Override public void r() { System.out.println("Camille 释放了 R 技能"); } }
新增一个 HeroConfiguration 配置类
@Configuration public class HeroConfiguration { @Bean public ISkill camille(){ return new Camille(); } }
每一个@Bean注解下的方法都要求方法返回一个Bean
测试类
@RestController @RequestMapping("banner") public class BannerController { @Autowired private ISkill camille; @GetMapping("test") public String test(){ camille.e(); return ""; } }
10、@Configuration表面意义上的解释(标注了此注解的类称之为 配置类)
已经有了@Component、@Service、@Repository ,Spring 为什么还要增加 @Configuration?
对于一个类来说,除了行为之外,还有特征
.行为:类中的方法就体现了类的行为
.特征:类中定义的属性都是要被赋值的,@Component 只是将Bean加入IOC而不能对属性进行赋值,因为实例化并不是本身去实例化的,所以没办法对属性进行赋值
@Configuration 中是自行实例化的,在实例化时可以传入相关参数,并且可以有多个@Bean
11、@Configuration 是用来替换 bean 的 xml 配置
以前 Spring 使用XML实例化Bean的方式
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 装配Studnt对象到IOC容器中 --> <bean id="student" class="com.demo.domain.Student"/> </beans>
12、为什么Spring 热衷于配置?
OCP 是为了解决变化,变化是不能消除的,只能隔离,或者反映到配置文件中(配置文件中的改动不算变化)
使用@Configuration 和 @Bean注入,改动这个配置类的代码算不算违反OCP?
.如果将配置类看做是以前的xml文件的替代品,那么改动代码是不会违反OCP
为什么要把变化隔离到配置文件里?
.配置文件具有集中性
.配置文件比较清晰,不包含业务逻辑
13、@Configuation和@Bean的真实作用
从面向对象的角度理解XML,是以类和对象作为XML的配置
SpringBoot 使用的是配置类加上普通/常规配置的形式,参数不是直接固定在配置类中,而可以写在配置文件中,例如application.properties
新建一个MySQL类
public class MySQL implements IConnect{ private String ip; private Integer port; @Override public void connct() { System.out.println(this.ip + ", " + this.port); } public MySQL(String ip, Integer port) { this.ip = ip; this.port = port; } public MySQL() { } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } }
新建一个IConnect 接口
public interface IConnect { void connct(); }
新建一个 DatabaseConfiguration 配置类
@Configuration public class DatabaseConfiguration { @Value("#{mysql.ip}") private String ip; @Value("#{mysql.port}") private Integer port; @Bean public IConnect mysql(){ return new MySQL(this.ip, this.port); } }
在 application.properties 配置文件中配置 IP 和 PORT
mysql.port=3306 mysql.ip=127.0.0.1
14、@Configuration 的意义
1.在IOC容器启动时,有选择/有条件地注入某一个Bean
2.对于某一个方向的业务问题,不一定只有一个Bean,可能具有多个Bean,此时就可以通过配置类,把所有需要的配置都导入到IOC容器中,而不是分散到各个Bean
15、几种注入方式
@Autowired :字段/成员变量注入
Setter 注入
构造函数注入
16、属性,到底是什么意思?
面向对象中的一个概念,对于Java来讲,严格意义上是不存在属性这样的一个概念
@Autowired private ISkill camille;
这种最合适的应该称之为成员变量/字段
17、POJO 与 Bean
https://www.cnblogs.com/aiyowei/p/10443161.html
18、@ComponentScan包扫描机制
注解的基本原理是通过反射机制来实现的,通过反射可以读取到注解的信息
SpringBoot启动时只能扫描当前包以及子包下的类
有些组件/Bean需要复用时,可能是以jar的形式导入的,此时可以在启动类增加@ComponentScan 并指定扫描位置
19、策略模式的几种实现方案
1、切换 Bean 的name
2、使用@Qualifier 注解指定注入的Bean
3、有选择地只注入一个Bean,注释掉某个Bean上的@Component注解
4、使用@Primary注解 提高Bean注入的优先级
20、条件注解@Conditional
顾名思义,当满足某种条件的时候,IOC 才会加载Bean
通过@Conditional + Condition 可以完成条件注解
将 9 中的 HeroConfiguration 更改一下
@Configuration public class HeroConfiguration { @Bean public ISkill diana(){ return new Diana("Diana", 1); } @Bean public ISkill irelia(){ return new Irelia(); } }
Irelia
//@Component public class Irelia implements ISkill{ public Irelia() { System.out.println("Hello, phubing"); } @Override public void q() { System.out.println("Irelia 释放了 Q 技能"); } @Override public void w() { System.out.println("Irelia 释放了 W 技能"); } @Override public void e() { System.out.println("Irelia 释放了 E 技能"); } @Override public void r() { System.out.println("Irelia 释放了 R 技能"); } }
Diana
@Data //@Component @AllArgsConstructor public class Diana implements ISkill{ private String name; private Integer age; public Diana() { System.out.println("Hello, phubing"); } @Override public void q() { System.out.println("Diana 释放了 Q 技能"); } @Override public void w() { System.out.println("Diana 释放了 W 技能"); } @Override public void e() { System.out.println("Diana 释放了 E 技能"); } @Override public void r() { System.out.println("Diana 释放了 R 技能"); } }
此时,如果不加条件,那么两个Bean都将加入到IOC中
@Conditional() 需要返回一个元类(条件类)
DianaCondition
public class DianaCondition implements Condition { /** * 此方法返回true时,满足条件的就会被加入到IOC中 * @param conditionContext * @param annotatedTypeMetadata * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return false; } }
HeroConfiguration
@Configuration public class HeroConfiguration { @Bean @Conditional(DianaCondition.class) public ISkill diana(){ return new Diana("Diana", 1); } @Bean public ISkill irelia(){ return new Irelia(); } }
此时运行就不会有问题了,因为Diana不满足,直接返回的false
21、Condition 接口的 ConditionContext 参数
新增一个Condition:IreliaCondition
public class IreliaCondition implements Condition { /** * 此方法返回true时,满足条件的就会被加入到IOC中 * @param conditionContext * @param annotatedTypeMetadata * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return true; } }
application.properties
hero.condition = irelia
通过 ConditionContext 可以获取非常丰富的信息,例如所有的配置信息、所有的Bean、资源加载的信息
IOC 在加载容器的时候并非直接把Bean实例化,当IOC要去加载Bean的时候,首先要生成一个Bean定义,通过Bean定义再来实例化Bean
DianaCondition
public class DianaCondition implements Condition { /** * 此方法返回true时,满足条件的就会被加入到IOC中 * @param conditionContext * @param annotatedTypeMetadata * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { String name = conditionContext.getEnvironment().getProperty("hero.condition"); return "diana".equalsIgnoreCase(name); } }
IreliaCondition
public class IreliaCondition implements Condition { /** * 此方法返回true时,满足条件的就会被加入到IOC中 * @param conditionContext * @param annotatedTypeMetadata * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { String name = conditionContext.getEnvironment().getProperty("hero.condition"); return "irelia".equalsIgnoreCase(name); } }
任何@Component 、@Configuration 以及其衍生类都可以追加条件注解
22、@ConditionalOnProperty成品条件注解
更新一下:HeroConfiguration,Diana 与 Irelia 的@Component 需要加上
@Configuration public class HeroConfiguration { @Bean @ConditionalOnProperty(value = "hero.condition", havingValue = "diana") //@Conditional(DianaCondition.class) public ISkill diana(){ return new Diana("Diana", 1); } @Bean @ConditionalOnProperty(value = "hero.condition", havingValue = "irelia") //@Conditional(IreliaCondition.class) public ISkill irelia(){ return new Irelia(); } }
matchIfMissing 参数:如果Condition 没有读取到对应值(并非条件不成立),那么该参数为true的Bean将会被注入到IOC中,也即指定一个默认值
23、@ConditionalOnBean
当SpringIOC容器内存在指定的Bean的条件
更改 HeroConfiguration
@Configuration public class HeroConfiguration { @Bean @ConditionalOnBean(name = "mysql") public ISkill diana(){ return new Diana("Diana", 1); } @Bean public ISkill irelia(){ return new Irelia(); } }
如果IOC存在mysql这样一个Bean,那么Diana也会随之被加入到IOC中
24、与@ConditionalOnBean相反的注解:@ConditionalOnMissingBean
如果不存在该注解的value指定的Bean,则加了此注解的Bean将会被加入到IOC中
为了保证同一个实现只有一个相同类型的Bean
Bean与Bean之间很有可能存在依赖,比如常规的Controller类,加了RestController,而RestController中包含了@Component,那Controller类就会被当做一个Bean注入到IOC中;同时Controller类还会依赖别的Bean(例如Service接口实现类)