快速搭建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
配置热部署
编写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,除非你明确地进行配置。
被导入的模块:
在另一个模块导入: 在springboot启动类开启组件扫描
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()~~~~~~~");
}
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、定义目标类型
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 {
}
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的类名,最后就通过
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、@SpringBootConfiguration
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})
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.
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、定义打印配置信息类
打印时需要的参数,比如行数,打印的字符
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
3.10、打包、上传到maven本地仓库
3.11、使用我们的printer
添加依赖,我们还是在Community-boot项目中进行测试
<dependencies>
<dependency>
<groupId>com.wngz.printer</groupId>
<artifactId>printer-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
现在如果我们要使用打印功能就要用到PrintService
java">
@SpringBootTest(classes = {CommunitySBApp.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class BeanTest {
@Autowired
private PrintService printService;
@Test
public void testSchool2(){
Assert.assertNotNull(printService);
printService.printSquare();
}
}