Springboot学习

深度学习SpringBoot(1)

1、介绍

springboot在spring的基础之上,搭建起来的框架,能够帮助我们整合市面上最流行的框架,帮助我们快速搭建起来项目。

知道最近,我才真正觉得springboot是真的好用。用来创建java、Javaweb项目以及spring项目,所以现在选择了springboot来进行开发学习。springboot不是新的技术,而是新的框架,是基于spring来搭建起来的。

最显著的几点:

1、配置方面大大简化了,springboot使用的是application.yml或者是application.properties,推荐使用applicaiton.yml;
2、整合了世界上最流行的框架;
3、起步依赖。利用maven、gradle等快速构建项目;
4、自动配置。导入对应的starter,利用起步依赖,快速导入对应的jar包,而且会帮助我们自动配置好对应的配置信息。

开发效率直接开挂。

2、构建springboot项目的方式

我这里使用的工具是idea,以idea方式来进行演示。

第一步:创建springboot项目

在idea中找到Spring Initializr快速创建springboot项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRTMX6cu-1619860119838)(E:\编程总结\springboot\springboot基础-picture\创建springboot项目的方式.png)]

可以直接使用国外的网站来进行构建,也可以使用阿里云的来配置。但是阿里云的没有parent,对于一个使用久了的国外网站来构建项目的人来说就很难受,通过parent来可以查看很多有用的东西,但是阿里的没有,很难受。所以推荐使用外网的,慢就慢吧。

第二步:根据选择创建项目结构

Springboot学习_第1张图片

我使用的是java语言基于maven、jdk8来构建的。

第三步:项目集成框架和组件

选择集成哪些框架和组件集成到springboot框架中去,可以根据自己的选择来进行勾选。

Springboot学习_第2张图片

我在这里进行演示的,我就简单的选择了几个,可以看红色箭头指向的。

记得选择可能要使用到的。开发者工具里面的我是习惯都要选择。

第一个:每次都不需要进行重启,项目代码发生变化了之后,自动刷新。但是可能有坑,有缓存。
第二个:给我们的实体类加上Lombok注解,这样子不必每个都写set/get/tostring方法,需要在自己的idea中安装一下lombok插件,一般自动		 都装好了。
第三个:当我们想要将配置的类和配置文件application.yml进行绑定的时候,如果不导入,可能会存在着提示,这个看起来很不爽。如果不导		   入,还需要自己手动的进行配置,最后还要在pom文件中排除掉。比较麻烦,这里选择了之后,之后就不用再去进行前面描述复杂的操作了

第四步:构建好的项目结构

Springboot学习_第3张图片

其中,application.properties首先将其改成applicaion.yml,查看起来比较好。

3、配置类

以往的学习中使用到了xml配置,太过于麻烦了。所以springboot引入了yml和properties,properties使用起来比较简单,但是我不喜欢用。所以我用yml来使用,yaml和yml都是可以的。习惯用yml。

需求:将java中的类的属性和yml中的配置进行绑定。

下面的几乎涉及到了开发中所有的可能性了。

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Animal {
    private String name;
    private Integer age;
}
@ConfigurationProperties(prefix="person") 
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Person {
    private String userName;
    private Boolean boss;
    private Date birthday;
    private Integer age;
    private Animal animal;
    private String[] interests;
    private List animals;
    private Map score;
    private Set salarys;
    private Map> allAnimals;
}

将上面的java类和springboot的配置文件进行绑定

# 一定要将实体中的属性和下面的配置项进行一一对应。遵循规范,不一样有的也可以,但是没必要,妨碍自己阅读。
# 类中要提供set/get方法,进行映射的时候通过set方法来进行set进去的。
person:
  userName: liguang
  boss: false
  birthday: 2021/12/03
  age: 12
  animal:
    name: tom
    age: 12
  interests:
    - 篮球
    - 足球
    - 唱歌
  animals:
    - mouse
    - rabbit
    - snake
  score:
    math:
      98
    chinese:
      78
    english:
      88
  salarys:
    - 8888
    - 5555
    - 6666
  all-animals:
    sick:
      - name: tom
        age: 12
      - name: jerry
        age: 13
    health:
      - name: linux
        age: 15

需要进行特别说明:使用了@ConfigurationProperties注解仅仅是将配置文件中的配置项和实体类进行了绑定而已,也就是仅此而已。并非将其添加进了容器中去。测试类中:

Person persion = new Person();
log.info("person值是:{}",person);

无法输出在配置类中编写的内容。如果需要使用到person,那么还需要搭配一个注解来进行使用

@SpringBootApplication
@EnableConfigurationProperties({Person.class})
public class HelloApplication {.......}

这个注解的意思是将Person注册到容器中去,那么在进行获取的时候,从容器中去拿。这个注解搭配着配置类使用更合适,所以在启动类上写上这个注解就可以来进行使用了。在测试类上加上@Slf4j注解进行测试,测试代码如下:

@Autowired
private Person person;
@Test
void testUser(){
    log.info("person对象中的属性{}",person);
}

输出信息如下:

2021-05-01 14:53:14.339  INFO 12120 --- [           main] com.guang.user.TestUser                  : person对象中的属性Person(userName=liguang, boss=false, birthday=Fri Dec 03 00:00:00 CST 2021, age=12, animal=Animal(name=tom, age=12), interests=[篮球, 足球, 唱歌], animals=[mouse, rabbit, snake], score={math=98, chinese=78, english=88}, salarys=[8888.0, 5555.0, 6666.0], allAnimals={sick=[Animal(name=tom, age=12), Animal(name=jerry, age=13)], health=[Animal(name=linux, age=15)]})

当然了,除了上面的操作之外,还有另外一种操作。将上面的注解全部注释掉,然后在Person类上加上@Component注解,然后搭配@ConfigurationProperties(prefix=“person”) ,因为只有容器中的组件才能够享受到这种特殊服务。

Animal类不做改变。

@ConfigurationProperties(prefix="person")
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Component
public class Person {
    private String userName;
    private Boolean boss;
    private Date birthday;
    private Integer age;
    private Animal animal;
    private String[] interests;
    private List<String> animals;
    private Map<String,Object> score;
    private Set<Double> salarys;
    private Map<String,List<Animal>> allAnimals;
}

总结1:

@ConfigurationProperties仅仅是将配置文件中的配置项绑定到类中的属性上去,@EnableConfigurationProperties是会让	       @ConfigurationProperties失效的;将是组件的类添加到容器中去【非常重要】,要想然一个类成为一个组件,那么也是非常简单的,使用   一些常用的注解即可来解决。但是为了区分web的三层架构,通常用@Component让一个类成为一个组件;或者使用另外一种@Import(字节码对象)

额外说明:配置类也是一个组件

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component  // 使用了这个注解来说明。
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

对于这个配置注解,也是大有文章的,是springboot2最大的一个变动。关注的是最后一个属性:proxyBeanMethods,代理bean的方法。换句话说就是:

proxyBeanMethods属性默认值是true,也就是说该配置类会被代理(CGLIB),在同一个配置文件中调用其它被@Bean注解标注的方法获取对象时会直接从IOC容器之中获取,这种效率会慢一点,因为为true时会经过一次代理方式获取得到对象;而为false的时候,则不会去检查容器中的是否存在着这个对象,直接创建新的对象来,为了让启动更快。主要目的是为了解决依赖的问题

通过一个Demo来进行说明,在另外一个类中新建

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Cat {
    private String username;
    private Integer age;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String username;
    private Integer age;
    // 依赖Cat类。一定要保持良好的习惯。依赖类谁,谁就在前面首先创建。
    private Cat cat;
}

写一个配置类,让这个两个java类成为组件

@Configuration(proxyBeanMethods=true) // 注意这里的区别
public class MyConfiguration2 {

    // 让这个对象成为组件,并将其注册到容器中去。bean的id是方法名字。
    @Bean("cat")
    public Cat getCat(){
        Cat cat = new Cat();
        cat.setUsername("tom");
        cat.setAge(25);
        return cat;
    }
    
    @Bean("user")
    public User getUser(){
        User user = new User();
        user.setUsername("guang");
        user.setAge(23);
        // 在同一个类中调用@Bean注解的方法。如果proxyBeanMethods=true,直接从容器中获取;如果是false,创建新的对象来依赖
        user.setCat(getCat());
        return user;
    }
}

使用这种方式来解决依赖的问题。其中循环依赖的前提就是每个对象都是单实例的。如果说,user组件在MyConfiguration2类中没有依赖于cat,那么就可以将proxyBeanMethods声明为false。如果说,user依赖了cat,那么将其调整为true。

@Component+@Bean在springboot2中的新的使用方式。解决bean和bean之间的依赖关系。
User user = application.getBean("user", User.class);
log.info("user--->{}",user);
Cat cat = application.getBean("cat", Cat.class);
log.info("cat---->{}",cat);
// 为true的时候,user.getCat()是从容器中获取得到的对象
// 为false的时候,user.getCat()不从容器中获取新的对象,创建新的使用
log.info("user.cat---{}",user.getCat().hashCode());
log.info("cat---{}",cat.hashCode());
log.info("true or false?:{}",user.getCat()==cat); // 为true时,从容器中获取;为false时,创建一个新的。

在这里我发现了一种有趣的现象,我将@Bean(“user”)和@Bean(“cat”)的顺序调整了一下

@Configuration(proxyBeanMethods = true)
public class MyConfiguration2 {

    @Bean("user")
    @ConditionalOnBean(name = "cat")
    public User getUser(){
        User user = new User();
        user.setUsername("guang");
        user.setAge(23);
        user.setCat(getCat());
        return user;
    }

    // 让这个对象成为组件,并将其注册到容器中去。bean的id是方法名字。
    @Bean("cat")
    public Cat getCat(){
        Cat cat = new Cat();
        cat.setUsername("tom");
        cat.setAge(25);
        return cat;
    }
}

结果发现启动就报错了。说容器中没有这个user组件

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'user' available

但是将顺序调整之后,就可以来运行了。这里先暂时搁浅一下,找不动原因在哪里。无论是将proxyBeanMethods设置为false还是true都不行。

@Bean的另外一种使用方式,如果@Bean之后,方法中有对象参数,那么优先从容器中获取得到得到对象。

@Configuration(proxyBeanMethods=false)// true还是false,测试类中都是true
public class MyConfiguration2 {

    // 让这个对象成为组件,并将其注册到容器中去。bean的id是方法名字。
    @Bean("cat")
    public Cat getCat(){
        Cat cat = new Cat();
        cat.setUsername("tom");
        cat.setAge(25);
        return cat;
    }

    @Bean("user")
    public User getUser(Cat cat){
        User user = new User();
        user.setUsername("guang");
        user.setAge(23);
        user.setCat(cat);
        return user;
    }
}
User user = application.getBean("user", User.class);
Cat cat = application.getBean("cat", Cat.class);
log.info("user.cat---{}",user.getCat().hashCode());
log.info("cat---{}",cat.hashCode());
log.info("true or false?:{}",user.getCat()==cat);  // true

总结2:

注意@Configuration配置中proxyBeanMethods的值;配置类中的组件是否会产生依赖,如果产生了依赖,那么将proxyBeanMethods设置为true;如果组件之间没有依赖,那么可以直接设置为false即可。



## 4、常用注解

@Conditional一般作用在类上,表示当前类下的@ConditionalXXX满足了,才去做某件事。

如:@ConditionalOnBean(name = "cat"),只有容器中有了id为cat的组件,才去执行下面的方法

@ConditionalOnClass,当容器中有某一个类的时候,才回去做某件事情。

@ConditionalOnMissingClass:当容器中没有某个类的时候,才去做某件事情。

@ConditionalOnResource:当类路径下存在着某一个资源的时候才回去做什么。

@ConditionalOnJava:当指定了java的某一版本号的时候,才去做什么。

@ConditionalOnWebApplication:当当前应用是一个web应用的时候,该去做些什么。

@ConditionalOnNotWebApplication:当当前应用不是一个web应用的时候,该去做些什么。

@ConditionalOnProperty:当配置文件中配置了某个属性的时候,再去做什么事情。

* 源码中用的很多,可以查看下对应的注解说明。

```java
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

	// 数组,获取对应property名称的值,与name不可同时使用
	String[] value() default {};

	// 配置属性名称的前缀,比如spring.http.encoding
	String prefix() default "";

	// 数组,配置属性完整名称或部分名称
	// 可与prefix组合使用,组成完整的配置属性名称,与value不可同时使用
	String[] name() default {};

	// 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
	String havingValue() default "";

	// 缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效
	boolean matchIfMissing() default false;

}

测试一下@ConditionalOnBean(name = “cat”)

@Configuration
public class MyConfiguration2 {

    @Bean("user")
    // 其他地方没有cat组件
    @ConditionalOnBean(name = "cat")
    public User getUser(){
        User user = new User();
        user.setUsername("guang");
        user.setAge(23);
        return user;
    }
}

测试类

User user = application.getBean("user", User.class);

控制台输出

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'user' available

使用了@ConditionalOnBean(name = “cat”)之后,容器中没有cat对象,这个方法将会失效。容器中就没有了user组件。

5、引入starter之后的故事

第零步:说白了,也就是准备工作。我们使用技术的时候,有两种选择,第一种是自己找依赖,然后需要用到什么,就位置什么;第二种就是直接知道starter之后,根据提示,需要用到什么就写什么。引入starter之后,开始用起来不方便,但是用习惯了就爽了。

第一步:找到starter。这里的starter会有springboot本身提供的,也有第三方技术。

官方文档中包含的starter:
https://docs.spring.io/spring-boot/docs/2.3.10.RELEASE/reference/html/using-spring-boot.html#using-boot-starter


第三方技术的starter:直接去github上搜索,找到Code,找到starter对应的坐标;或者直接去maven仓库找到对应的starer,找到star对稿的来使用。我喜欢从maven仓库中找到和springboot结合的starter,找到星星高的,拿过来直接用。省的自己去找哪个是发行版,如果是快照版,误入雷区就得不偿失了。

第二步:在springboot项目中引入之后,首先查看依赖是否导入进来了。如果导入进来了,那么查看引入了哪些依赖,是否有对应的AutoConfiguration。如果有了,直接去idea中下面的External Libraries中进行查询刚刚引入引来的坐标。有的话打开,首先映入眼帘的就是对应的XXXAutoConfigure,有的话打开。

第三步:可以看到类上有一大堆注解。那么这个时候肯定又会看到最特别的两个注解。一个是声明为组件的,另外一个是@EnableConfigurationProperties,在@EnableConfigurationProperties中肯定又会有YYYYYProperties,点进去,又会肯定一个注解

@ConfigurationProperties,这个配置项是不是特别的熟悉了?是用来将java类和配置文件中的配置项来进行绑定的。并且@ConfigurationProperties中肯定有值表名的是配置项的前缀。在YYYYYProperties中的属性,就是我们需要进行自定义配置的。我们字需要给值即可。

第四步:我前面勾选了一个组件spring configuration processor在对我们进行配置项配置的时候就起到了提示的作用。非常有用。

第五步:需要使用到什么,自己去官方文档查询。需要配置哪些东西就自己配置。

总结

Springboot学习_第4张图片

用一个Demo来进行说明,用一个Drdid数据库源来说明

第一步:找到Druid的starter

<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

第二步:利用maven来查看依赖是否导入了进来,有没有对应的starter。去External Libraries下面找

Springboot学习_第5张图片

Springboot学习_第6张图片

第三步:点击DruidDataSourceAutoConfigure查看,找到EnableConfigurationProperties和@Configuration(一般是这个)

@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {....}

找到xxxxProperties,点击进去。我进DruidStatProperties,另外一个先不分析

@ConfigurationProperties("spring.datasource.druid")
public class DruidStatProperties {.....}

找到@ConfigurationProperties,里面的spring.datasource.druid就是我们在配置文件中需要写的前缀。

Springboot学习_第7张图片

6、温馨说明

因为springboot的版本仲裁,每次更新都会将版本进行了锁定。所以我们使用的版本号都是springboot的锁定的版本号。这样可能和自己的环境有冲突。如果来解决版本号的冲突问题?利用maven的就近原则来选择。

举个例子,我从maven reposity中找到了驱动类的坐标。简单说明下为什么springboot为何不帮我们引入数据库驱动类,因为人家根本不知道你要哪个数据库!这才是本质原因,你是用oracle的还是oracle的,你自己选择导入对应的坐标即可。

我找到的数据库驱动的坐标。springboot创建的默认是基于8的,可以利用maven来查看依赖。

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version> // 数据库的版本要和这里的版本保持对应。不然鬼知道有什么错误出现。
</dependency>

第一种解决版本号的就是这种方式,直接导入了版本号。maven的就近原则;

第二种解决本号好的方式就是模仿springboot的方式:

先找到我们自己的pom.xml文件,大体结构如下:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
        <properties>
        	....
        </properties>
    <dependencies>
        .....
    <dependencies>

依赖了父工程,点进去,又依赖了一个父工程,再进去:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.10.RELEASE</version>
  </parent>

搜索,可以看到大量的在里面,这里就是springboot的版本仲裁。我要用自己的版本号,那么搜索

mysql,找到了8.0.23,那么拷贝这个,去我们自己的下,添加进去,然后版本号写我们自己的。

再次查看maven中的依赖,发现这个时候已经是我自己选择的5.1.47了。

可以看到pom文件的依赖关系

自己的pom文件---------->父工程的pom文件-------->父工程的pom文件

我们这里配置的 版本仲裁的地方

根据就业原则,那么只用就是我们自己的版本号。

你可能感兴趣的:(springboot,spring,boot)