精华 springBoot快速上手

news/2024/11/8 21:33:02 标签: spring boot, java, spring

快速搭建springboot项目

项目包结构

SpringBoot_Project

src //java程序源代码

main

entity //实体类

mapper //mapper映射类接口

service //service层接口和实现类

controller //controller层接口

resources //资源文件夹

mappers //mapper映射文件

public //存放.html等网页文件

pages

static //存放 .js 等静态资源文件

js

application.yml //springboot配置文件

 

 

 

依赖

 <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit.version>4.12</junit.version>
    <lombok.version>1.18.30</lombok.version>
    <spring-boot.version>2.5.3</spring-boot.version>
  </properties>
	<dependencies>
       <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--knife4j帮助文档依赖-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
 
        <!--添加SpringBoot的依赖(自动帮我们创建bean,只要你添加了数据源的依赖,就可以自动为你创建数据源对象)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--配置web应用程序相关依赖(spring-webmvc,jsp, servlet)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--热部署的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!--测试相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

    </dependencies>
    <!--管理springboot依赖,在这里统一设置SpringBoot的版本号。一旦这是设置了SpringBoot的版本号
后面使用很多依赖时我们就不用指定版本号了,直接使用-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.5.3</version>
                <!--由于当前的这个依赖属于管理依赖,而不是项目中代码真正需要的依赖-->
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

application.yml配置文件

server:
  port: 8080  #配置项目端口号
spring:
  #配置SwaggerConfig
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  datasource:
    url: jdbc:mysql://192.168.3.8:3306/sddfp_log?characterEncoding=utf8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

配置热部署

c5d84be5cec045e8b39d521631cda0b7.png

828bac17099846958ca430f1b9afdd50.png

编写springboot启动类

java">//标记为springboot启动类
@SpringBootApplication
public class CommunitySBApp {
    public static void main(String[] args) {
        SpringApplication.run(CommunitySBApp.class, args);
    }
}

springboot特性

@ComponentScan

在 Spring Boot 项目中,如果你有一个公共模块(例如一个包含公共代码和组件的库模块),并且这个公共模块中的类被注解为 @RestController这些类不会自动被 Spring Boot 主项目中的 Spring 容器扫描和注册为 Spring Bean,除非你明确地进行配置。

被导入的模块:

0fcfea49683d49b589cca0fd90a9a647.png

在另一个模块导入: 2e000022293141f19d49e54d2ba27c8b.pngspringboot启动类开启组件扫描

java">@SpringBootApplication
@ComponentScan(basePackages = {"com.wngz.email"})
@MapperScan(value = "com.wngz.portal.mapper")
public class PortalApp {
    public static void main(String[] args) {
        SpringApplication.run(PortalApp.class,args);
        System.out.println(111);
    }
}

@Mapper/@MapperScan

springboot提供两种方式装配Mapper接口

方式一 @Mapper

在所需Mapper接口上添加@Mapper即可,无需额外配置

java">@Mapper
public interface BuildingMapper {
}

方式二 @MapperScan

使用方式一需要在每个Mapper接口都添加注解,使用方式二可以一劳永逸

直接在springboot启动类上添加@MapperScan,添加value属性指定mapper包名

java">@SpringBootApplication
@MapperScan(value = "com.wngz.community_boot.mapper")
public class CommunitySBApp {
    public static void main(String[] args) {
        SpringApplication.run(CommunitySBApp.class, args);
    }
}

@RestControllerAdvice

@RestControllerAdvice 是一个组合注解,通常与 @ExceptionHandler 注解一起使用

相当于@ControllerAdvice+@ResponseBody

作用是全局性地处理 Spring MVC 控制器抛出的异常,并将异常信息以 JSON 或 XML 格式等直接写入 HTTP 响应体中

spring boot>spring boot 默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好,下面自定义异常处理,提供友好展示。

标记GlobalExceptionHandler类作为全局异常处理器

java">@RestControllerAdvice // 声明全局异常处理器
public class GlobalExceptionHandler {
    
    // 捕获并处理 BusinessException 异常
    @ExceptionHandler(BusinessException.class)
    public ResponseResult handel(BusinessException e) {
        // 获取 BusinessException 中的错误码和错误信息
        IErrorCode iError = e.getIError();
        // 创建 ResponseResult 对象,包含错误信息和错误码
        ResponseResult result = ResponseResult.failure(iError.getErrorMsg(), iError.getCode());
        // 返回 ResponseResult 对象给客户端
        return result;
    }
    
    // 捕获并处理 Exception 异常(用于处理其他未被显式捕获的异常)
    @ExceptionHandler(Exception.class)
    public ResponseResult method(Exception e) {
        // 创建 ResponseResult 对象,包含异常信息和错误码 "500"
        ResponseResult result = ResponseResult.failure(e.getMessage(), "500");
        // 返回 ResponseResult 对象给客户端
        return result;
    }
}

声明式事务

当我们独立使用mybatis时,事务是默认开启的,在执行增删改时必须手动提交事务

java">public class App {
    public static void main(String[] args) {
        demo3();
    }

    @SneakyThrows
    public static void demo4() {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        OwnerDao ownerDao = sqlSession.getMapper(OwnerDao.class);
        Owner owner = new Owner();
        owner.setSex(true);
        owner.setUsername("张琪");
        owner.setPassword("123456");
        owner.setIdentity("7564534");
        owner.setNumbers("303");
        owner.setTel("100111");
        ownerDao.insert(owner);
        //提交事务
        sqlSession.commit();
        System.out.println("新增的owner id=" + owner.getId());
    }
}

springBoot集成mybatis后可以通过@Transactional注解来控制事务

Transactional源码

java">// 声明注解 @Transactional,指定适用的目标元素为类和方法
@Target({ElementType.TYPE, ElementType.METHOD})
// 声明注解 @Transactional 的生命周期为运行时
@Retention(RetentionPolicy.RUNTIME)
// 声明注解 @Transactional 具有继承性
@Inherited
// 声明注解 @Transactional 应该被包含在 Java 文档中
@Documented
public @interface Transactional {
    // 设置默认值为空字符串,用于指定事务管理器的名称
    @AliasFor("transactionManager")
    String value() default "";
    
    // 设置默认值为空字符串,用于指定事务管理器的名称,与 value 属性为别名
    @AliasFor("value")
    String transactionManager() default "";
    
    // 用于指定事务标签
    String[] label() default {};
    
    // 用于指定事务传播行为,默认为 REQUIRED
    Propagation propagation() default Propagation.REQUIRED;
    
    // 用于指定事务隔离级别,默认为 DEFAULT
    Isolation isolation() default Isolation.DEFAULT;
    
    // 用于指定事务超时时间,默认为 -1(表示使用事务管理器的默认超时时间)
    int timeout() default -1;
    
    // 用于指定事务超时时间的字符串表示,默认为空字符串
    String timeoutString() default "";
    
    // 用于指定事务是否为只读事务,默认为 false
    boolean readOnly() default false;
    
    // 用于指定在哪些异常情况下执行事务回滚操作,默认为空数组
    Class<? extends Throwable>[] rollbackFor() default {};
    
    // 用于指定在哪些异常情况下执行事务回滚操作的类名,默认为空数组
    String[] rollbackForClassName() default {};
    
    // 用于指定在哪些异常情况下不执行事务回滚操作,默认为空数组
    Class<? extends Throwable>[] noRollbackFor() default {};
    
    // 用于指定在哪些异常情况下不执行事务回滚操作的类名,默认为空数组
    String[] noRollbackForClassName() default {};
}

几种重要的属性设置

Propagation事务传播行为

java">public enum Propagation {
    // 表示该事务传播行为为“必须”(默认)
    REQUIRED(0),
    // 表示该事务传播行为为“支持”,即如果当前存在事务,就加入该事务;如果当前不存在事务,则以非事务方式执行
    SUPPORTS(1),
    // 表示该事务传播行为为“强制”,即当前必须存在事务,如果当前不存在事务,则抛出异常
    MANDATORY(2),
    // 表示该事务传播行为为“重新开始”,即每次都创建一个新的事务;如果当前存在事务,则将当前事务挂起
    REQUIRES_NEW(3),
    // 表示该事务传播行为为“不支持”,即当前方法不关心事务;如果当前存在事务,则将其挂起
    NOT_SUPPORTED(4),
    // 表示该事务传播行为为“绝不”,即当前方法不支持事务;如果当前存在事务,则抛出异常
    NEVER(5),
    // 表示该事务传播行为为“嵌套”,即如果当前存在事务,则在嵌套事务中执行;如果当前不存在事务,则创建一个新的事务
    NESTED(6);
	}
}

Isolation隔离级别

java">public enum Isolation {
    // 表示使用默认的事务隔离级别,通常由数据库或事务管理器决定
    DEFAULT(-1),
    // 表示事务可以读取未提交的数据,可能会导致脏读、不可重复读和幻读
    READ_UNCOMMITTED(1),
    // 表示事务只能读取已提交的数据,可以防止脏读,但是可能会出现不可重复读和幻读
    READ_COMMITTED(2),
    // 表示事务可以避免脏读和不可重复读,但是可能会出现幻读
    REPEATABLE_READ(4),
    // 表示事务可以避免脏读、不可重复读和幻读,是最高的事务隔离级别
    SERIALIZABLE(8);
	}
}

快速使用

java">@SpringBootApplication
//允许开启事务
@EnableTransactionManagement
public class CommunitySBApp {
    public static void main(String[] args) {
        SpringApplication.run(CommunitySBApp.class, args);
    }
}
java">//表示必须开启事务
//当然可以通过在方法上加注解表示该方法支持事务但只进行只读操作不开启事务
@Transactional(propagation = Propagation.REQUIRED)
@Service
public class BuildingServiceImpl implements BuildingService {
    @Autowired
    BuildingMapper buildingMapper;

    //标记该方法支持事务不过只读操作
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    @Override
    public PageInfo<Building> page(Integer pageNumber, Integer pageSize) {
        PageHelper.startPage(pageNumber, pageSize);
        List<Building> buildings = buildingMapper.query(null);
        PageInfo<Building> pageInfo = new PageInfo<>(buildings);
        return pageInfo;
    }
}

springboot中创建bean的方式

如果我们需要在项目中创建一些全局性的变量,比如EmailTemplate发送邮件的工具类,但是这只是一个类型,同时我们需要在多个地方都需要使用这个类型的实例,那么就需要通过容器来创建这个类的实例。

好处在于,容器一旦创建了这个实例,我们就可以在任何需要使用这个实例的地方,加上@AutoWired注解就可以进行引用了。

我们之前使用最多的创建实例的方式就是加注解:@Component(@Controller, [@Service]# "@Service"), @Mapper)

但是上面这些注解也是存在局限的:

1)如果目标类型需要临时从配置文件application.yml||application.properties中读取属性值,不方便使用。

2)如果目标类型不是我们自己定义的,我们是没有办法在该类上添加注解

1、@Component + @ConfigurationProperties + application配置文件

本方式的目的就是为了方便的从配置文件中获取属性值,目标类型必须是我们自己定义的,这样才能在这个类上添加@Component和@ConfigurationProperties注解。

1.1、定义实体类Student

java"> package com.wngz.community_boot.entity;

 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;

 import java.time.LocalDate;

 @Data
  @Component //通知spring容器,需要为这个类型创建一个实例
  @ConfigurationProperties(prefix = "stu") //意味着我们Student实例的属性值将取自application
  //配置文件中以stu开头的配置信息,配置信息中的属性名 和 Student实例的属性名严格保持一致
  public class Student {
   private String name;
   private LocalDate birthday;
   private Boolean sex;
   private String email;
  }


1.2、在application.yml中定义属性值

java">  stu:
   name: admin
   birthday: 2020/01/03
   sex: true
   email: 1234@qq.com

1.3、使用 @ConfigurationProperties提取属性值

Configuration:配置, Property:属性

使用@ConfigurationProperties注解时需要添加如下依赖

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-configuration-processor</artifactId>
   <optional>true</optional>
  </dependency>

1.4、单元测试

java"> @SpringBootTest(classes = {CommunitySBApp.class})
 @RunWith(SpringJUnit4ClassRunner.class)
 public class BeanTest {

  @Autowired
  private Student student;

  @Test
  public void testStudent(){
   Assert.assertNotNull(student);

   System.out.println("student = " + student);
   }
  }

2、@Configuration + @Bean(掌握)

现在我们使用@Configuration + @Bean来创建实例,假设目标实例的类型不是我们自己定义的,所以我们不会在目标类上添加ioc相关的任何注解

2.1、实体类

java">  @Data
  public class School {
   private String name;
   private String address;
  }

2.2、创建配置类

我们使用@Configuration来修饰配置类,使其实现原来spring-ioc的配置文件,

使用@Bean修改创建School对象的方法,使其实现原来<bean>配置节的作用

java"> package com.wngz.community_boot.config;

 import com.wngz.community_boot.entity.School;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

 @Configuration //表示当前类是配置类
 public class MyConfig {

   @Bean //容器才会调用这个方法,并将方法的返回结果存入到容器中,这个Bean的id默认就是方法名称
   public School school(){
   School school = new School();
   school.setName("窝里横学校");
   school.setAddress("天通苑1号");

   return school;
   }
  }

2.3、单元测试

java">  @Autowired
  private School school;

  @Test
  public void testSchool(){
   Assert.assertNotNull(school);

   System.out.println("school = " + school);
  }

思考一个问题?MyConfig这个类会不会也被容器创建一个对象

java">  @Configuration //表示当前类是配置类
  public class MyConfig {
   //spring容器创建bean的时候是调用无参构造函数的
   public MyConfig(){
   System.out.println("~~~~~~~MyConfig()~~~~~~~");
   }

685a2e136113460c87680d09eae29330.png

java">  school:
   name: 门头沟校区
   address: 深圳市南山区

说明spring容器一定会创建MyConfig的实例,为什么?因为@Bean修饰的方法都是实例方法,实例方法要被调用,一定是需要对象。

2.4、@Bean + @ConfigurationProperties

java">  school:
   name: 门头沟区
   address: 深圳市南山区
java">  public class MyConfig {
   ......

   @Bean
   @ConfigurationProperties(prefix = "school")
   public School szSchool(){
   School school = new School();

   return school;
   }
  }
java">  @Autowired
  @Qualifier("szSchool")
  private School szSchool;

  @Test
  public void testSchool2(){
   Assert.assertNotNull(szSchool);
   System.out.println("szSchool = " + szSchool); 
  }

3、@Import + @Configuration

前面创建Bean的方式都是有一个前提,无论目标类型是否是我们自己定义,但是在项目运行之前就已经能够确定要为哪些类创建Bean,但是有的时候,我们在项目的编译期间是不知道需要创建哪些Bean,只有在项目真正运行的时候才知道需要创建哪些Bean?

将自己看成是springboot的开发者,你在开发这个框架时就需要为将来的目标用户创建一些必要的bean。比如,如果发现用户添加了mybatis-spring-boot-starter依赖,就要为用户创建一个SqlSessionFactory的实例,但是这些操作只有在程序运行之后才能知道。也不可能事先就直接创建SqlSessionFactory的实例,因为可能这个项目根本就不是数据库项目。

注意:虽然我们讲解了@Import的注解,但是要注意的是,这种方式基本上都是springboot内部使用的,我们很少会用到。

场景,我们有一个bean的清单, bean_name.txt文件,里面存放多个完整类名,现在需要在程序启动完毕后,读取这个txt文件,spring根据文件内容创建多个实例。

3.1、定义目标类型

e11938e14b4d49788de89058b7dd44a7.png

java">  public interface ServiceA {
   void execute();
  }
java"> package com.wngz.community_boot.myservice.impl;

 import com.wngz.community_boot.myservice.ServiceA;

 public class ServiceAImpl implements ServiceA {
  public ServiceAImpl() {
  System.out.println("~~~ ServiceAImpl() ~~~");
  }

   @Override
   public void execute() {
   System.out.println("~~~ ServiceA.execute被调用了 ~~~");
   }
  }

其他类略….

注意:我们现在不需要在ServiceXXImpl上添加任何注解

3.2、创建ServiceA的实例

通过@Import + @Configuration

java"> package com.wngz.community_boot.config;

 import com.wngz.community_boot.myservice.impl.ServiceAImpl;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;

 @Configuration
 @Import({
  ServiceAImpl.class
  }) //注解中传值时,{}对应的就是数组
  public class ImportDemoConfig {
  }

3.3、单元测试

java"> @SpringBootTest(classes = {CommunitySBApp.class})
 @RunWith(SpringJUnit4ClassRunner.class)
 public class ImportTest {

  @Autowired
  private ServiceA serviceA;

  @Test
  public void testServiceA(){
   Assert.assertNotNull(serviceA);
   serviceA.execute();
   }
  }

3.4、ImportSelector导入选择器

ImportSelector:导入选择器类,

@Import注解除了可以直接接收需要创建的类的字节码之外,也可以接收ImportSelector接口的实现类的字节码,并且会处理这个接口中的selectImports方法的方法结果,为结果中每一个类名字符串创建对象。

java"> package com.wngz.community_boot.myimport;

 import org.springframework.context.annotation.ImportSelector;
 import org.springframework.core.type.AnnotationMetadata;

 public class MyImportSelector implements ImportSelector {
  //selectImports选择需要导入的类的类名
  @Override
  public String[] selectImports(AnnotationMetadata annotationMetadata) {
   return new String[]{
   "com.wngz.community_boot.myservice.impl.ServiceBImpl"
   };
   }
  }
java">  @Configuration
  @Import({
   ServiceAImpl.class,
   MyImportSelector.class
  }) //注解中传值时,{}对应的就是数组
  public class ImportDemoConfig {
  }
java">  @Autowired
  private ServiceB serviceB;

  @Test
  public void testServiceB(){
   Assert.assertNotNull(serviceB);
   serviceB.execute();
  }

3.5、DefererdImportSelector 延迟导入选择器

如果某些类型,需要在其他类型的bean都创建完毕后才会创建,可以使用延迟导入选择器来进行配置。

java">  public class MyDeferredImportSelector implements DeferredImportSelector {
   @Override
   public String[] selectImports(AnnotationMetadata annotationMetadata) {
   return new String[]{
   "com.wngz.community_boot.myservice.impl.ServiceCImpl"
   };
   }
  }
java">  @Configuration
  @Import({
   MyDeferredImportSelector.class,
   ServiceAImpl.class,
   MyImportSelector.class
  }) //注解中传值时,{}对应的就是数组
  public class ImportDemoConfig {
  }
java">  @Autowired
  private ServiceB serviceC;

  @Test
  public void testServiceC(){
   Assert.assertNotNull(serviceC);
   serviceB.execute();
  }

3.6、ImportBeanDefinitionRegister

ImportBeanDefinitionRegister也可以实现上面的功能,同时还可以处理Bean冲突的情况。如果发现容器中已经存在同名的bean,不会无条件继续创建。

java"> public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  //Definition定义; registry注册器
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  //serviceD届时就是我们serviceD的bean的id
  //首先判断容器中是否存在名为serviceD的bean
  if(!registry.containsBeanDefinition("serviceD")){
  //如果不存在,就创建一个Bean的定义对象,关联到ServiceD的字节码
  GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition(); //创建一个Bean的定义对象
   genericBeanDefinition.setBeanClass(ServiceDImpl.class);

   //将bean的定义对象添加到注册器中。这样注册器中就有了bean的定义,后续就可以不会重复创建
   registry.registerBeanDefinition("serviceD", genericBeanDefinition);
   } 
   }
  }
java">  @Configuration
  @Import({
   MyDeferredImportSelector.class,
   ServiceAImpl.class,
   MyImportSelector.class,
   MyImportBeanDefinitionRegistrar.class
  }) //注解中传值时,{}对应的就是数组
  public class ImportDemoConfig {
  }

单元测试

java">  @Autowired
  private ServiceD serviceD;

  @Test
  public void testServiceD(){
   Assert.assertNotNull(serviceD);
   serviceD.execute();
  }

Annotation注解

1、元注解

java"> @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Component
 public @interface Service {
  @AliasFor(
  annotation = Component.class
  )
  String value() default "";
  }

注解的关键字是@interface,本质上是一个接口,但是和接口的使用方式不同

在注解中可以定义多个属性,但是定义属性时,是以方法的形式出现。同时可以使用default关键字为属性指定默认值。

在我们自定义注解的上方也有一些注解,这些注解成为元注解,即修饰注解的注解,表示被修饰的注解的使用方式。

常见的元注解有四个:

@Target:表示我们的注解是用于修饰哪些目标?(@Override用于方法上,@Controller用于类上,@ExcelProperty属性)

@Retention:指定注解使用的时机,一般将这个注解的值配置为Runtime

@Documented: 用来指定被修饰的注解将被 javadoc 工具提取成文档。

@Inherited: 用来指定被修饰的注解具有继承性,如果一个类使用了被 @Inherited 修饰的注解,那么其子类也会继承这个注解。有两个类,Parent和Child,那么在Parent类上定义的注解在Child类能否获取到。取决于@Inherited的值

2、自定义注解

java"> package com.wngz.community_boot.annotation;

 import java.lang.annotation.*;

 @Target(ElementType.TYPE)
 @Documented
 @Inherited
 @Retention(RetentionPolicy.RUNTIME)
 public @interface MyAnnotation {
   //在自定义注解中定义一个属性,属性名为description, 默认值为1234
   String description() default "1234";
  }

3、使用自定义注解

使用自定义注解MyAnnotation修改目标类型

java">  package com.wngz.community_boot.annotation;

  @MyAnnotation(description = "这是我的一个测试类")
  public class MyClass {
  }

f22b34e5d90a41fdbc2cd39b5d15ce3d.png

4、在运行时获取目标对象上的注解

获取注解的方式很简单,通过字节码获取在类上的注解;

通过Method对象获取方法上的注解

通过Field对象获取属性上的注解

java">  public class AnnotationTest {
   @Test
   public void test1(){
   //获取MyClass类上的注解
   MyAnnotation annotation = MyClass.class.getAnnotation(MyAnnotation.class);

   if(annotation != null){
   System.out.println("annotation.description() = " + annotation.description());
   }
   }
  }

5、注解的运用

1、定义一个工具类,在其中定义一个方法,接收一个Object类型的参数,然后打印这个对象所有属性的属性名和属性值。但是有的时候,有些属性是不要打印的。请你来设计这个类,能够灵活的实现属性打印功能。

java">  public class MyUtil{
   public void showAllProperties(Object obj){
   //.........
   }
  }

 myUtil.showAllProperties(building);
 myUtil.showAllProperties(house);
 myUtil.showAllProperties(student);

  但是如果某些属性不向打印,如何通知我们的showAllProperties。解决方案就是添加注解,在不需要打印的属性上添加注解
java">  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public class Product { //产品
   private String name;

   @Hide
   private Integer price;

   private String imgUrl;
  }


java">  package com.wngz.community_boot.annotation;

  import java.lang.annotation.ElementType;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.annotation.Target;

  @Target(ElementType.FIELD)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface Hide { 
  }


java">  public class MyUtil {
   @SneakyThrows
   public static void showAllProperties(Object obj){
   //要获取全部属性,必须得到目标对象的字节码。但是现在的类型是Object, Object.class肯定不行
   Class<?> clazz = obj.getClass();

   Field[] fields = clazz.getDeclaredFields();
   for (Field field : fields) {
   //获取field属性上的Hide注解
   Hide hide = field.getAnnotation(Hide.class);
   if(hide != null){
   continue;
   }

   field.setAccessible(true); //将属性临时设为公开的

   System.out.printf("%s = %s \n", field.getName(),
   field.get(obj));
   }
   }
  }


springboot自动配置

1、springboot的优点

大大简化了我们的工作压力:

使用了很多的starter启动器,原来在ssm项目中如果要使用web技术,就需要自己添加所有web相关的依赖(servlet,jsp,web-mvc),在boot项目中,我们只需要引用一个依赖spring-boot-starter-web。在这个依赖中包含web应用程序所有需要用到的依赖。

一个starter启动器就可以将这个场景中所需要用到的全部依赖作为一个整体进行引用。

第二个优点就是能够自动的为我们创建bean,比如我们在数据访问层加了@Mapper,springboot就会自动创建SqlSessionFactory的实例,通过这个实例来创建Mapper对象。这就是自动配置。

springboot究竟是如何实现这一点的呢?

对于spring框架中的类,springboot可以直接操作,比如在目标类型上添加@Component注解,或者自己编写@Configuration配置类来创建。但是对于第三方的这些类而言,spring无法要求这些公司为spring框架,而在自己的类型添加这些注解。

但是MyBatis公司肯定不会在自己的代码中添加spring的相关注解,但是最后springboot又确实实现了的自动配置。

springboot设计,既然你不配合,不肯修改源码。我自己做一个,做一个mybatis-spirng-boot-starter MyBatis的启动器,在这个启动器中,我把你原有的jar都包含进来。当外界的请求来到这个starter之后,会调用内部包含的MyBatis的class。如何创建程序运行时需要的bean呢?在MyBatis的jar之外,加入一个程序清单文件spring.factories,在这个文件中就定义了MyBatis应用程序运行需要用到的所有实例,其中就包含SqlSessionFactory的类名,最后就通过

9a876ffb0c474c39b9869eb24dee69aa.pngcd359b498084491db1d54f21d5cad616.png753942291e944f9b89d05031c775a031.png

2、自动配置的流程

springboot自动配置相关的核心注解

java">  @SpringBootApplication
  public class CommunitySBApp{

@SpringBootApplication是springboot自动配置的核心注解

java">  @Target({ElementType.TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @Inherited

  @SpringBootConfiguration

  @EnableAutoConfiguration
  @ComponentScan(
   excludeFilters = {@Filter(
   type = FilterType.CUSTOM,
   classes = {TypeExcludeFilter.class}
  ), @Filter(
   type = FilterType.CUSTOM,
   classes = {AutoConfigurationExcludeFilter.class}
  )}
  )
  public @interface SpringBootApplication {

2.1、@SpringBootConfiguration2b227eb629a34ac1934c9bd413bc0e99.png

2.2、@ComponentScan

扫包,需要为所有类似@Component,@Service,@Controller等注解修饰的类创建实例。但是不包括@Mapper

2.3、@EnableAutoConfiguration 启动自动配置

java">  @Target({ElementType.TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @Inherited

  @AutoConfigurationPackage
  @Import({AutoConfigurationImportSelector.class})

  public @interface EnableAutoConfiguration {
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   Class<?>[] exclude() default {};

   String[] excludeName() default {};
  }


1)@AutoConfigurationPackage

这个注解也是扫包,扫描包中的目标注解就是@ComponentScan处理之外的注解,即类似@Mapper注解

2)@Import({AutoConfigurationImportSelector.class})5e76049b1bba4b248d692600f21efb50.png

77bba9112c0547aca0a644fb55bf0b2f.png

5808345ecb694f03a6ecfe654400319f.png

java">  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
   return configurations;
  }

上面的代码也说名了springboot的自动装配就是要去找META-INF/spring.factories清单文件。

3、自定义starter启动器

模拟打印功能

3.1、创建一个spring-boot模块

打印相关: printer-spring-boot-starter,我们自定义的启动器,命名方式必须是:xxx-spring-boot-starter,但是spring自己的定义的启动器名称是:spring-boot-starter-xxx.9d0872baf5db478eaf87b4ca4bada79c.png

3.2、添加依赖

添加springboot的相关依赖

  <dependencies>
   <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <scope>test</scope>
   </dependency>

   <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   </dependency>

   <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
   </dependency>

   <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   </dependency>
  </dependencies>

  <dependencyManagement>
   <dependencies>
   <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-dependencies</artifactId>
   <version>2.5.3</version>
   <scope>import</scope>
   <type>pom</type>
   </dependency>
   </dependencies>
  </dependencyManagement>


3.3、定义打印配置信息类

打印时需要的参数,比如行数,打印的字符12d617203a9e40968253dfedf38cfc0a.png

java">  package com.wngz.printer.property;

  import lombok.Data;
  import org.springframework.boot.context.properties.ConfigurationProperties; 

  //打印时需要用到的参数。希望这些属性值可以从配置文件中获取
  @Data
  //@Component 如果我们没有添加@Component注解,单单只是添加@ConfigurationProperties注解时会报错
  //原因是springboot认为,如果你不创建PrintProperty的对象,就没有必要配置@ConfigurationProperties注解
  //但是我们不改动代码,原因就是除非我们能够确定目标程序要用到打印功能,否则我们都不会实例化Printer打印工具类,从
  //而,也不需要创建PrintProperty实例
  @ConfigurationProperties(prefix = "printer")
  public class PrintProperty {
   private Integer line = 5; //行数
   private String symbol = “*”; //符号
  }


3.4、定义打印类PrintService

java">  package com.wngz.printer.service;

  import com.wngz.printer.property.PrintProperty;
  import org.springframework.beans.factory.annotation.Autowired;

  //该类负责打印输出
  public class PrintService {
   @Autowired
   PrintProperty printProperty;

   //打印正方形
   public void printSquare(){
   for(int i = 1; i <= printProperty.getLine(); i++){
   for(int j = 1; j <= printProperty.getLine(); j++){
   System.out.print(printProperty.getSymbol() + " ");
   }

   System.out.println();
   }
   }
  }


3.5、打印自动配置类

这个配置类用于创建打印服务类的实例

java">  //这是一个配置类
  @Configuration
  //但是现在创建的printService属性值都是默认值,没有去读配置文件。需要激活打印配置类
  @EnableConfigurationProperties({PrintProperty.class})
  public class PrinterAutoConfiguration {

   //这个配置类用于创建打印类实例
   @Bean
   public PrintService printService(){
   PrintService printService = new PrintService();

   return printService;
   }
  }


3.6、主程序类

java">  @SpringBootApplication
  public class App{
   public static void main( String[] args ){
   SpringApplication.run(App.class, args);
   }
  }

3.7、单元测试

java">  @SpringBootTest(classes = {App.class})
  @RunWith(SpringJUnit4ClassRunner.class)
  public class AppTest{
   @Autowired
   PrintService printService;

   @Test
   public void testPrint(){
   printService.printSquare();
   }
  }

3.8、有条件创建PrintService实例

除非用户设置了printer的配置,并且在其中明确指定了需要用到PrintService实例,否则我们不会创建目标对象

java">  printer:
   line: 7
   symbol: '*'
   want: "yes"
java">  //这是一个配置类
  @Configuration
  //但是现在创建的printService属性值都是默认值,没有去读配置文件。需要激活打印配置类
  @EnableConfigurationProperties({PrintProperty.class})
  @ConditionalOnProperty(prefix = "printer", )
  public class PrinterAutoConfiguration {
   ...

  }

3.9、创建清单

META-INF/spring.factories

3c98e6512016491ea87a6eafac0775e3.png

3.10、打包、上传到maven本地仓库

f76776dc04bb42fd9f7178a7c169d4a6.png

3.11、使用我们的printer

添加依赖,我们还是在Community-boot项目中进行测试

  <dependencies>
   <dependency>
   <groupId>com.wngz.printer</groupId>
   <artifactId>printer-spring-boot-starter</artifactId>
   <version>1.0</version>
   </dependency>

现在如果我们要使用打印功能就要用到PrintService 227494e9c0894fa3825d758ed0f257a4.png

 

java">
  @SpringBootTest(classes = {CommunitySBApp.class})
  @RunWith(SpringJUnit4ClassRunner.class)
  public class BeanTest {
   @Autowired
   private PrintService printService;

   @Test
   public void testSchool2(){
   Assert.assertNotNull(printService);

   printService.printSquare();
   }

  }

 

 


http://www.niftyadmin.cn/n/5744460.html

相关文章

Java SPI机制简单讲解

前言 在Java开发中&#xff0c;经常会遇到需要扩展系统功能的需求。为了使系统更加灵活和可扩展&#xff0c;Java提供了SPI&#xff08;Service Provider Interface&#xff09;机制。本文将简单介绍SPI机制的基本概念、工作原理&#xff0c;并通过一个具体的示例来展示如何使…

Nginx配置文件详解及常用功能配置、应用场景

一、Nginx配置文件结构 Nginx的配置文件通常命名为nginx.conf&#xff0c;其结构清晰&#xff0c;遵循简单的层次化设计&#xff0c;主要分为以下几个部分&#xff1a; 全局块&#xff1a; user&#xff1a;指定Nginx工作进程运行的用户和用户组。 worker_processes&#xf…

SCI期刊文章录用后,期刊被on hold了怎么办?

主要有以下两种选择&#xff1a; 递一&#xff0c;如果时间紧张&#xff0c;可以撤稿重投。比如说等着文章发表了好去申请项目、毕业啥的&#xff0c;那可得好好考虑下撤稿重投这条路哦。为啥呢&#xff1f; 因为 on hold 期间&#xff0c;数据库是会暂停检索该期刊新发表的文…

回溯算法详解与剪枝优化

1. 什么是回溯算法&#xff1f; 回溯算法&#xff08;Backtracking&#xff09;是一种通过探索所有可能情况来找到所有解的算法。它在一定程度上可以理解为带有返回操作的深度优先搜索(DFS)。 1.1 基本思想 从一个初始状态出发按照规则向前搜索当搜索到某一状态无法继续前进…

如何开发查找附近地点的微信小程序

我开发的是找附近卫生间的小程序。 在现代城市生活中&#xff0c;找到一个干净、方便的公共卫生间有时可能是一个挑战。为了解决这个问题&#xff0c;我们可以开发一款微信小程序&#xff0c;帮助用户快速找到附近的卫生间。本文将介绍如何开发这样一款小程序&#xff0c;包…

redis:zset有序集合命令和内部编码

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》《网络》 《redis学习笔记》 文章目录 前言命令ZADDZRANGEZREVRANGEZCARDZCOUNTZPOPMAXBZPOPMAXZPOPMINBZPOPMINZRANKZSCOREZREMZREMRANGEBYRANKZREMRANGEBYSCOREZINCRBY集合间操作…

网络规划设计师-(13)物理层

什么是物理层&#xff1f; 物理层是计算机网络中的一层&#xff0c;负责在物理媒介上传输数据比特流。它主要关注如何将比特流转换为电信号、光信号、无线信号等物理形式&#xff0c;以便能够在传输媒介上传输。物理层还负责确定物理连接的方式、传输速率、编码方式、电压等。物…

【Hadoop实训】Flume系统负载均衡测试

一、搭建并配置Flume机器 在master上&#xff0c;执行&#xff1a; scp -r /export/servers/flume slave1:/export/servers/scp -r /export/servers/flume slave2:/export/servers/scp /etc/profile slave1:/etc/profilescp /etc/profile slave2:/etc/profile 执行完上述指令后…