[Java基础]—SpringBoot

news2024/11/19 16:45:30

Springboot入门

Helloworld

依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>

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

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

主程序

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

业务程序

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        return "Hello,Sentiment!";
    }
}

配置文件

application.properties

server.port=8888

自动装配原理

容器功能

组件添加

1、@Configuration

在Spring中各实体的都是通过bean管理的,需要配置对应的xml文件

如:

<bean id="user01" class="com.sentiment.boot.bean.User">
    <property name="name" value="Sentiment"></property>
    <property name="age" value="18"></property>
</bean>

而在SpringBoot中则可以通过@Configuration来自定义组件,它的默认ID为方法名即:user01、tomcatPet

@Configuration
public class MyConfig {

    @Bean
    public User user01(){
        User user = new User("Sentiment", 18);
        user.setPet(tomcatPet());
        return user;
    }

    @Bean
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

测试

//1、从容器中获取组件为单实例的
Pet tomcat01 = run.getBean("tomcatPet", Pet.class);
Pet tomcat02 = run.getBean("tomcatPet", Pet.class);
System.out.println("组件:"+(tomcat01==tomcat02));

MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);

//2、如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
//保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);


User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tomcatPet", Pet.class);

System.out.println("用户的宠物:"+(user01.getPet() == tom));

结果:

组件:true
com.sentiment.boot.config.MyConfig$$EnhancerBySpringCGLIB$$809ee907@6bfdb014
true
用户的宠物:true

但是当@Configuration(proxyBeanMethods = false)时,结果为:

组件:true
com.sentiment.boot.config.MyConfig@408613cc
false
用户的宠物:false

这是由于proxyBeanMethods的默认值为true,但当值为true时SpringBoot总会检查这个组件是否在容器中有,因此两个bean对象都是从容器中获取的所以相等

而设为false时,就不会从容器中检查,因此相当于创建了两个实例

2、@Bean、@Component、@Controller、@Service、@Repository

跟MVC的一样

3、@Import

再MyConfig上加上@Import

@Import(User.class)

这样在加载bean时除了前边加载的user01外,还会加载User

String[] namesForType = run.getBeanNamesForType(User.class);
for (String s : namesForType) {
    System.out.println(s);
}

结果:

com.sentiment.boot.bean.User
user01

4、@Conditionnal

条件装配:满足Conditional指定的条件,则进行组件注册

在这里插入图片描述

以@ConditionalOnBean为例,当组件中有对应的bean时,则会进行组件注册

此时Config文件中有两个bean,分别为:user01、tomcat,但我们的条件设置为当有tomcatPet组件时才会进行组件注册

@ConditionalOnBean(name="tomcatPet")
public class MyConfig {

    @Bean
    public User user01(){
        User user = new User("Sentiment", 18);
        user.setPet(tomcatPet());
        return user;
    }

    @Bean("tomcat")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

由于两个bean中不包含tomcatPet,因此默认不会进行注入,

测试

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);        boolean tomcat = run.containsBean("tomcat");
        System.out.println("容器中tomcat组件:"+tomcat);
        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+user01);
    }
}

结果

容器中tomcat组件:false
容器中user01组件:false

而当@ConditionalOnBean改为@ConditionalOnMissingBean,即:当存在tomcatPet时注入,改为不存在时注入,结果:

容器中tomcat组件:true
容器中user01组件:true

原生配置文件引入

1、@ImportResource

假设在Spring配置文件中设置了依赖注入,SpringBoot并不会识别Spring的配置文件,这就需要@ImportResource注解进行初始化

在这里插入图片描述

未使用该注解前,查看师傅注入test组件

boolean test = run.containsBean("test");
System.out.println("容器中test组件"+test);

结果

容器中test组件false

此时在config文件上加上:

@ImportResource("classpath:test.xml")

此时该配置文件中的bean就被注入到了组件当中,结果:

容器中test组件true

配置绑定

读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时修改使用;

1、@Component + @ConfigurationProperties

定义一个Car类,设置properties前缀为mycar

@Component
@ConfigurationProperties(prefix ="mycar")
public class Car {
    private String brand;
    private Integer price;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }


    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

接着再properties文件设置,前缀为mycar的赋值参数

mycar.brand=BYD
mycar.price=10000

写个控制器直接获取car类中属性的值

@Autowired
Car car;

@RequestMapping("/car")
public Car car(){
    return car;
}

访问/car路径,成功初始化属性

在这里插入图片描述

2、@EnableConfigurationProperties + @ConfigurationProperties

将@EnableConfigurationProperties(Car.class)绑定到配置类上即可,主要用于调用其它没有加@Component的类

自动装配原理

看 03、了解自动配置原理 (yuque.com)

配置文件

推荐使用Yaml格式

数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
  • 对象:键值对的集合。map、hash、set、object
k: {k1: v1,k2: v2,k3: v3}k:
  k1: v1
  k2: v2
  k3: v3
  • 数组:一组按次序排列的值。array、list、queue
k: [v1,v2,v3]
k:
  - v1
  - v2
  - v3

示例

Person

@ConfigurationProperties("person")
@Component
@ToString
@Data
public class Person {
    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;
}

Pet

@ToString
@Data
public class Pet {
    private String name;
    private Double weight;
}

配置文件application.yaml

person:
  userName: Sentiment
  boss: false
  birth: 2001/12/7
  age: 20
#  interests: [唱,跳,rap]
  interests:
    - 唱歌
    -- rap
#  animal: [猫,狗]
  animal:
    --#  score: {english: 80,math: 90}
  score:
    english: 80
    math: 90
  salarys:
    - 99.9
    - 999.9
  pet:
    name:weight: 90
  allPets:
    sick:
      - name:weight: 90
      - {name:,weight: 80}
    health:
      - {name:,weight: 20}

写一个控制器返回person类的结果

{"userName":"Sentiment","boss":false,"birth":"2001-12-06T16:00:00.000+00:00","age":20,"pet":{"name":"猫","weight":90.0},"interests":["唱","跳","rap"],"animal":["猫","狗"],"score":{"english":80,"math":90},"salarys":[99.9,999.9],"allPets":{"sick":[{"name":"猫","weight":90.0},{"name":"狗","weight":80.0}],"health":[{"name":"鱼","weight":20.0}]}}

配置提示

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

打包配置

 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

web开发

静态资源访问

1、静态资源目录

静态资源默认存放在/static/publicMETA-INF/resources/resources,默认静态映射路径为/**

原理

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

因此请求顺序为:Controller —> 静态资源 —>404

修改静态资源设置

1、匹配静态资源的URL路径模式。即请求路径中带有/res才会被当做静态资源处理

spring:
  mvc:
    static-path-pattern: /res/**

2、修改静态资源位置

spring:
    resources:
      static-locations: classpath:/test
#      可用数组形式
#      static-locations: [classpath:/test,/demo]

欢迎页支持

默认欢迎页有两种:

  • 静态资源路径下的 index.html
  • controller处理的/index请求

需注意不要设置访问前缀,否则会加载不到

spring:
#  mvc:
#    static-path-pattern: /res/**         这个会导致welcome page功能失效

  resources:
    static-locations: classpath:/test

自定义Favicon

favicon.ico 放在静态资源目录下即可,但同样不要设置访问前缀

原理分析

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}

往下看给容器配置了什么,找到了WebMvcAutoConfigurationAdapter资源适配器

@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

其中WebMvcProperties就是获取spring.mvc下的配置,ResourcePropertiesspring.resources

在这里插入图片描述

1、配置类只有一个有参构造器

//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
	public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
		}

2、资源处理规则

在这里插入图片描述

1、首先会从资源配置文件中查看是否设置了addMapping值(默认值为:true,代表启用静态资源规则),当我们手动设为false后,下边注册流程便都不会被注册

spring:
    resources:
      static-locations: classpath:/test
      add-mappings: false

在这里插入图片描述

2、首先获取cache的period,即浏览器缓存时间,配置文件:

spring:
    resources:
        cache:
          period: 10000

接着给webjar注册请求路径/webjars/**,对应的本地映射文件位置在classpath:/META-INF/resources/webjars/

3、获取默认静态映射路径

在这里插入图片描述

并与本地文件对应

在这里插入图片描述

这也就解释了上边默认映射路径为什么是/**,以及默认资源访问为什么是这四个路径

3、欢迎页处理规则

欢迎页首先会判断在指定路径是否有index.html、默认路径是否为/**,如果都符合则跳转到index.html

在这里插入图片描述

如果前边条件不符合,则会进入else判断是否注册对应的/index的controller,如果有则跳转,没有则结束

请求参数处理原理

1、请求映射

1.1、rest原理

当我们使用rest风格时,我们的提交表单时无法提交delete、put等请求数据的

需将表单修改成_method的形式,即:

<form action="/user" method="post">
    <input name="_method" type="hidden" value="delete">
    <input value="DELETE" type="submit">
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="put">
    <input value="PUT" type="submit">
</form>

Controller

@RestController
public class HelloController {
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET";
    }
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String saveUser(){
        return "POST";
    }
    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE";
    }
    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String putUser(){
        return "PUT";
    }
}

但即使这样设置后,SpringBoot仍然无法识别delete、put请求,因此根据源码看下原理

实现原理

在WebMvcAutoConfiguration中定义了MethodFilter,这里的enabled的设置为false因此无法正常匹配delete、put请求

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
   return new OrderedHiddenHttpMethodFilter();
}

因此需要再配置文件中设置

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

接着看下为什么提交表单时,要设置成method="post"name="_method"

<form action="/user" method="post">
    <input name="_method" type="hidden" value="delete">

OrderedHiddenHttpMethodFilter中没什么东西,而它继承了HiddenHttpMethodFilter,因此直接看HiddenHttpMethodFilter,其中重写了doFilterInternal方法

在这里插入图片描述

主要流程:

获取request请求—>

判断该请求是否为POST是否有异常(这也就解释了为什么必须是POST) —>

获取methodParam的值,而它是_method,所以获取的值就是delete(这里就解释了为什么name=“_method”) —>

将delete改为大写—>

判断这个方法是否在ALLOWED_METHODS中—>

将request、method传入HttpMethodRequestWrapper中,而wrapper重写了getmethod方法,因此method重新赋值为delete,最后通过doFilter方法,完成methodFilter

扩展

流程中提到了获取_method的值,那如何修改这种方式?

再OrderedHiddenHttpMethodFilter中可以看到,他的Bean加载机制是当环境中没有HiddenHttpMethodFilter时,才会加载,从而一步步执行到刚刚的doFilterInternal方法中

在这里插入图片描述

那既然这样我们可以自定义Bean对象HiddenHttpMethodFilter,在其中修改methodParam的默认值,这样在环境启动后,Springboot便不会加载OrderedHiddenHttpMethodFilter,进而不会进行methodParam等初始化操作,也就不会修改我们自定义的methodParam

@Configuration(proxyBeanMethods = false)
public class MyConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}

1.2、请求映射原理

映射是通过DispatcherServlet实现的,所以从这看起

DispatcherServlet通过一级级继承关系,继承了HttpServlet,而继承它就需要实现doGetdoPost方法

在这里插入图片描述

FrameworkServlet发现,实现了上述方法

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   processRequest(request, response);
}

/**
 * Delegate POST requests to {@link #processRequest}.
 * @see #doService
 */
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   processRequest(request, response);
}

主要是调用processRequest(),直接看调用栈最终调用到了doDispatch

doDispatch:1016, DispatcherServlet (org.springframework.web.servlet)
doService:943, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)

doDispatch中经过getHandler()方法后,发现已经获取到了需要映射的getUser()方法

在这里插入图片描述

因此直接看getHandler中进行了什么操作

不想写了,跟着调用栈最后通过createWithResolvedBean进行了注册,一些重要操作都在lookupHandlerMethod中,自己调试看吧

在这里插入图片描述

2、普通参数与基本注解

2.1、注解

1、@PathVariable 路径变量
@RestController
public class ParamController {
    @GetMapping("/car/{id}/owner/{name}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("name") String name,
                                     @PathVariable Map<String,String> pv){
        HashMap<String, Object> map = new HashMap<>();
        map.put("id",id);
        map.put("name",name);
        map.put("pv",pv);
        return map;
    }
}

在这里插入图片描述

2、@RequestHeader 获取请求头
@RestController
public class ParamController {
    @GetMapping("/car/{id}/owner/{name}")
    public Map<String,Object> getCar(@RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> headers){
        HashMap<String, Object> map = new HashMap<>();
        map.put("userAgent",userAgent);
        map.put("headers",headers);
3、@CookieValue 获取Cookie
@RestController
public class ParamController {
    @GetMapping("/car/{id}/owner/{name}")
    public Map<String,Object> getCar(@CookieValue("_ga") String _ga,
                                     @CookieValue Cookie cookie){
        HashMap<String, Object> map = new HashMap<>();
        map.put("_ga",_ga);
        map.put("cookie",cookie);
4、@RequestBody 获取Body数据
@PostMapping("/post")
public Map postMethod(@RequestBody String content){
    HashMap<String, Object> map = new HashMap<>();
    map.put("content",content);
    return map;
}
5、@RequestAttribute 获取request域属性
@Controller
public class AttributeController {

    @GetMapping("/goto")
    public String  go(HttpServletRequest request){
        request.setAttribute("name","Sentiment");
        request.setAttribute("age",20);
        return "forward:/success";
    }

    @ResponseBody
    @GetMapping("/success")
    public Map<Object, Object> success(@RequestAttribute("name") String name,
                                       HttpServletRequest request){
        HashMap<Object, Object> map = new HashMap<>();
        map.put("name",name);
        
        //除注解外也可以通过request获取
        Object age = request.getAttribute("age");
        System.out.println(age);
        return map;
    }
}
6、@MatrixVariable 矩阵变量

1、语法:/cars/sell;low=34;brand=byd,audi,yd

2、SpringBoot默认是禁用了矩阵变量的功能,因此需要手动开启

在WebMvcAutoConfiguration的configurePathMatch()中,实例化了UrlPathHelper类

在这里插入图片描述

而该类中的removeSemicolonContent默认设置了移除分号功能因此需要将其关闭

在这里插入图片描述

configurePathMatch是由WebMvcConfigurer定义的,所以需要对其进行操作,有两种方法:

1、让自定义的config类继承WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
public class MyConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }

2、自定义WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
public class MyConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }

定义完成后

在这里插入图片描述

若路径中两处涉及到age的值,MatrixVariable可以通过设置pathVar参数来指定具体路径

@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
    Map<String,Object> map = new HashMap<>();

    map.put("bossAge",bossAge);
    map.put("empAge",empAge);
    return map;
}

路径/boss/1;age=20/2;age=10

在这里插入图片描述

视图解析与模板引擎

1、Thymeleaf

官方文档:教程:使用百里香叶 (thymeleaf.org)

1.1、基本语法

1、表达式

表达式名字语法用途
变量取值${…}获取请求域、session域、对象等值
选择变量*{…}获取上下文对象值
消息#{…}获取国际化等值
链接@{…}生成链接
片段表达式~{…}jsp:include 作用,引入公共页面片段

2、字面量

文本值: ‘one text’ , ‘Another one!’ **,…**数字: 0 , 34 , 3.0 , 12.3 **,…**布尔值: true , false

空值: null

变量: one,two,… 变量不能有空格

3、文本操作

字符串拼接: +

变量替换: |The name is ${name}|

4、数学运算

运算符: + , - , * , / , %

5、布尔运算

运算符: and , or

一元运算: ! , not

6、比较运算

比较: > , < , >= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )

7、条件运算

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

8、特殊操作

无操作: _

1.2、设置属性值-th:attr

设置单个值

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

设置多个值

<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

以上两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

所有h5兼容的标签写法

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

1.3、迭代

可参考(dynamic_table.html#506-517)

<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

若没有标签只是纯文本形式则使用双括号的形式,例如**(common.html#539)**:

[[${session.flag.userName}]]

1.4、条件运算

<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

1.5、包含模板片段

可参考(common.html)

用于将多个模板的重复内容,集合到一个模板中使用

//定义
<div th:fragment="copy">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>
 
//使用
  	<div th:insert="~{footer :: copy}"></div>
或: <div th:insert="footer :: copy"></div>

也可以用选择器

//定义
<div id="copy-section">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>

//使用
   <div th:insert="~{footer :: #copy-section}"></div>
或:<div th:insert="footer :: #copy-section"></div>

官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout

1.6、属性优先级

img

2、使用

2.1、依赖

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

2.2、自动配置好了thymeleaf

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面
	public static final String DEFAULT_PREFIX = "classpath:/templates/";

	public static final String DEFAULT_SUFFIX = ".html";  //xxx.html

2.3、页面开发

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
    <a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/>
    <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>

拦截器

1、HandlerInterceptor 接口

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object flag = session.getAttribute("flag");
        if (flag!=null){
            return true;
        }
        request.setAttribute("msg","请重新登录");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

2、配置拦截器

@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).
                addPathPatterns("/**").
                excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");
    }
}

文件上传

1、页面表单

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>

若多文件上传可设置

<input type="file" name="file" multiple>

2、文件上传代码

设置最大文件、最大请求

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
                     @RequestParam("username")String username,
                     @RequestPart("headerImage")MultipartFile headerImage,
                     @RequestPart("photos") MultipartFile[] photos) throws IOException {
    //单文件上传
    if (!headerImage.isEmpty()){
        String originalFilename = headerImage.getOriginalFilename();
        headerImage.transferTo(new File("D:\\cache\\"+originalFilename));
    }

    //多文件上传
    if (photos.length > 0){
        for (MultipartFile photo : photos) {
            if (!photo.isEmpty()){
                String originalFilename = photo.getOriginalFilename();
                photo.transferTo(new File("D:\\cache\\"+originalFilename));
            }
        }
    }

异常处理

/error/下的4xx,5xx页面会被自动解析

Servlet、Filter、Listener组件注入

1、使用Servlet API

Servlet

@WebServlet("/my")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("This is my servlet!");
    }
}

这样设置后,并不能访问到设置的路径,需在启动文件加上对应的扫描组件@ServletComponentScan(basePackages = "com.sentiment.admin")

Filter

@Slf4j
@WebFilter("/css/*")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyFilter初始化完成");
    }

    @Override
    public void destroy() {
        log.info("MyFilter销毁");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("MyFilter工作中");
        chain.doFilter(request,response);
    }
}

Listener

@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MyServletContextListener监听到项目初始化");
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MyServletContextListener监听到项目销毁");
    }
}

2、使用RegistrationBean

除@WebServlet、@WebFilter、@WebListener这些注释外,还可以使用RegistrationBean的方式:ServletRegistrationBean, FilterRegistrationBean,ServletListenerRegistrationBean

@Configuration
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    public FilterRegistrationBean myFilter(){

        MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MyServletContextListener mySwervletContextListener = new MyServletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

3、扩展

前边设置了拦截器,但是为什么MyServlet的/my没有被拦截?

在Spring流程中用的是DispatcherServlet,而在tomcat中如果多个Servlet都能处理到同一层路径,就会优先精确原则。

因此当访问/my时,由于精确优先原则就绕过了DispatcherServlet,便绕过了拦截器

数据操作

1、SQL

1.1、导入数据库场景

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

导入数据库场景后,因为官方不知道我们要用什么驱动,因此还需要导入数据库驱动

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.3</version>
</dependency>

若不设置version,springboot2.3.4的默认版本为

<mysql.version>8.0.21</mysql.version>

也可以在pom文件中重新声明版本

<properties>
    <java.version>1.8</java.version>
    <mysql.version>5.1.3</mysql.version>
</properties>

1.2、自动配置

  • DataSourceAutoConfiguration : 数据源的自动配置

    ○修改数据源相关的配置:spring.datasource

    数据库连接池的配置,是自己容器中没有DataSource才自动配置的

    ○底层配置好的连接池是:HikariDataSource

1.3、修改配置文件

spring:
    datasource:
      url: jdbc:mysql://localhost:3306/mybatis
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver

1.4、测试类

@SpringBootTest
@Slf4j
class MainApplicationTests {
  @Autowired
  JdbcTemplate jdbcTemplate;

  @Test
   void contextLoads() {
    Long aLong = jdbcTemplate.queryForObject("select count(*) from student ", long.class);
    log.info("条数{}",aLong);
  }
}

2、使用Druid数据源

官方文档:https://github.com/alibaba/druid

整合第三方技术的两种方式

  • 自定义
  • 找starter

2.1、自定义方法

依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.17</version>
</dependency>

1、创建数据源

默认创建方式

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		destroy-method="close">
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxActive" value="20" />
		<property name="initialSize" value="1" />
		<property name="maxWait" value="60000" />
		<property name="minIdle" value="1" />
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<property name="poolPreparedStatements" value="true" />
		<property name="maxOpenPreparedStatements" value="20" />

在Springboot中可以通过自定义配置类的方式,初始化数据源

@Configuration
public class DataSourceConfig {

    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }
}

2、配置监控页面

配置_StatViewServlet配置 · alibaba/druid Wiki (github.com)

web.xml配置

<servlet>
	<servlet-name>DruidStatView</servlet-name>
	<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>DruidStatView</servlet-name>
	<url-pattern>/druid/*</url-pattern>
</servlet-mapping>

SpringBoot自定义配置类

@Bean
public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
    return registrationBean;
}

3、监控统计功能

配置_StatFilter · alibaba/druid Wiki (github.com)

此时SQL监控中,并没有记录,需要打开监控统计功能

web.xml配置

  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  	... ...
  	<property name="filters" value="stat" />
  </bean>

SpringBoot自定义配置类

@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() throws SQLException {
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setFilters("stat");
    return druidDataSource;
}

由于上边设置了spring.datasource的@ConfigurationProperties,因此直接在yaml文件中配置亦可

spring:
	datasource:
        url: jdbc:mysql://localhost:3306/mybatis
        username: root
        password: 123456
        driver-class-name: com.mysql.jdbc.Driver
        filters: stat

4、配置Web关联监控

配置_配置WebStatFilter · alibaba/druid Wiki (github.com)

web.xml配置

  <filter>
  	<filter-name>DruidWebStatFilter</filter-name>
  	<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
  	<init-param>
  		<param-name>exclusions</param-name>
  		<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
  	</init-param>
  </filter>
  <filter-mapping>
  	<filter-name>DruidWebStatFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>

SpringBoot自定义配置

@Bean
public FilterRegistrationBean webStatFilter(){
    WebStatFilter webStatFilter = new WebStatFilter();
    FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter);
    registrationBean.setUrlPatterns(Arrays.asList("/*"));
    registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    return registrationBean;
}

5、配置防火墙

配置 wallfilter · alibaba/druid Wiki (github.com)

web.xml配置

  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
      ...
      <property name="filters" value="wall"/>
  </bean>

SpringBoot自定义配置

@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() throws SQLException {
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setFilters("stat,wall");
    return druidDataSource;
}

或者可以用yaml文件配置

spring:
	datasource:
        url: jdbc:mysql://localhost:3306/mybatis
        username: root
        password: 123456
        driver-class-name: com.mysql.jdbc.Driver
        filters: stat,wall

6、配置账号密码

web.xml配置

<!-- 配置 Druid 监控信息显示页面 -->  
<servlet>  
    <servlet-name>DruidStatView</servlet-name>  
    <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>  
    <init-param>  
	<!-- 允许清空统计数据 -->  
	<param-name>resetEnable</param-name>  
	<param-value>true</param-value>  
    </init-param>  
    <init-param>  
	<!-- 用户名 -->  
	<param-name>loginUsername</param-name>  
	<param-value>druid</param-value>  
    </init-param>  
    <init-param>  
	<!-- 密码 -->  
	<param-name>loginPassword</param-name>  
	<param-value>druid</param-value>  
    </init-param>  
</servlet>  
<servlet-mapping>  
    <servlet-name>DruidStatView</servlet-name>  
    <url-pattern>/druid/*</url-pattern>  
</servlet-mapping>  

SpringBoot配置

@Bean
public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
    registrationBean.addInitParameter("loginUsername","admin");
    registrationBean.addInitParameter("loginPassword","admin");
    return registrationBean;
}

2.2、官方starter

依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>

1、分析自动配置

  • 扩展配置项 spring.datasource.druid

  • DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns

  • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启

  • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启

  • DruidFilterConfiguration.class}) 所有Druid自己filter的配置

    • private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
      private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
      private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
      private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
      private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
      private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
      private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
      private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
      

2、配置示例

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      aop-patterns: com.sentiment.admin.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)

      stat-view-servlet:   # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter:  # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


      filter:
        stat:    # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

3、整合Mybatis

https://github.com/mybatis

starter

  • SpringBoot官方的Starter:spring-boot-starter-*

  • 第三方的Starter: *-spring-boot-starter

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>6
</dependency>

img

1、配置模式

  • 全局配置文件
  • SqlSessionFactory: 自动配置好了
  • SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class);
  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class)MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}

@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties

可以在配置文件中设置驼峰命名方式:

<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

也可通过springBoot配置文件修改

mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/UserMapper.xml
  configuration:
    map-underscore-to-camel-case: true

步骤:

  • 导入mybatis官方starter
  • 编写mapper接口并标注@Mapper注解
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议;配置在mybatis.configuration

2、注解模式

直接用@Select注解代替Mapper.xml文件

@Mapper
public interface CityMapper {
    @Select("select * from city where id=#{id}")
    public City getCity(Long id);
}

3、混合模式

@Mapper
public interface CityMapper {
    @Select("select * from city where id=#{id}")
    public City getCity(Long id);

    public void insert(City city);
}

Mapper.xml

<mapper namespace="com.sentiment.admin.mapper.CityMapper">
    <insert id="insert">
        insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})
    </insert>
</mapper>

这种方式自增的id回显值为null

在这里插入图片描述

所以在Mapper.xml文件上,可以加上

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})
</insert>

或者直接用注解形式

@Mapper
public interface CityMapper {
    @Select("select * from city where id=#{id}")
    public City getCity(Long id);

    @Insert("insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})")
    @Options(useGeneratedKeys = true,keyProperty = "id")
    public void insert(City city);
}

4、MyBatis-Plus 完成CRUD

参考mybatis-plus官方文档,添加User数据库信息,以及Bean中字段

快速开始 | MyBatis-Plus (baomidou.com)

UserMapper

只需要继承BaseMapper并定义好User类型即可

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

测试类

@Test
void testUserMapper(){
    User user = userMapper.selectById(1L);
    log.info("用户信息为:{}",user);
  }
}

运行后报错,提示表中没有user_name列。

Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'user_name' in 'field list'

@TableField

这是因为之前用过User类,并且定义了userName和password属性,而mabtis-plus不允许有非数据库中字段,因此可以用注解@TableField来解决该问题

public class User {
    @TableField(exist = false)
    private String userName;
    @TableField(exist = false)
    private String password;

@TableName

除此外BaseMapper<>中的类型,代表的就是数据库名称即:User

public interface UserMapper extends BaseMapper<User> {

而此时若我把表名换成User_tbl则需要用到注解

@TableName("user_tbl")
public class User {
    @TableField(exist = false)
    private String userName;
    @TableField(exist = false)
    private String password;

    private Long id;
    private String name;
    private Integer age;
    private String email;
}

Service层

mybatis-plus中提供了 IService接口,来定义Service层的查询方法

public interface UserService extends IService<User> {
}

这时Service接口层的实现类,就需要实现对应的方法,而mybatis-plus有提供了对应的接口ServiceImpl

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

}

分页功能

@GetMapping("/dynamic_table")
public String dynamic(Model model, @RequestParam(value = "pn",defaultValue = "1") long pn){

//    model.addAttribute("user",list);
    List<User> list = userService.list();
//    model.addAttribute("users",list);
    Page<User> tPage = new Page<>(pn, 2);
    Page<User> page = userService.page(tPage, null);
    model.addAttribute("page",page);
//    page.getCurrent();         获取当前页数
//    page.getPages();           获取总页数
//    page.getRecords();         获取所有查询记录
    return "table/dynamic_table";
}

分页功能还需要自定义config,否则可能显示错误

@Configuration
public class MyBatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //设置请求的页面大于最大页后操作,true调回到首页,false继续请求默认false
        // paginationInterceptor.setoverfLow(false);
        //设置最大单页限制数量,默认50日条,-1不受限制paginationInterceptor.setMaxLimit( 500 ) ;
        //开启count 的join优化,只针对部分left join
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setOverflow(true);
        paginationInnerInterceptor.setMaxLimit(500L);
        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
        return mybatisPlusInterceptor;
    }
}

html

<div class="row-fluid">
    <div class="span6">
        <div class="dataTables_info" id="dynamic-table_info">当前[[${page.current}]]页
            总共[[${page.pages}]]页 共[[${page.total}]]条记录
        </div>
    </div>
    <div class="span6">
        <div class="dataTables_paginate paging_bootstrap pagination">
            <ul>
                <li class="prev disabled"><a href="#">← Previous</a></li>
                <li th:class="${num == page.current?'active':'' }"
                    th:each="num:${#numbers.sequence(1,page.pages)}"><a
                        th:href="@{/dynamic_table(pn=${num})}">[[${num}]]</a></li>
                <li class="next"><a href="#">Next → </a></li>
            </ul>
        </div>
    </div>
</div>

删除

控制器

@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
                         @RequestParam(value = "pn",defaultValue = "1")Integer pn,
                         RedirectAttributes ra){
    userService.removeById(id);
    ra.addAttribute("pn",pn);
    return "redirect:/dynamic_table";
}

前端

<td>
    <a class="btn btn-danger btn-sm" type="button" th:href="@{/user/delete/{id}(id=${user.id},pn=${page.current})}">删除</a>
</td>

单元测试

1、JUnit5 的变化

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。

JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

img

注意:

SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

依赖

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

现在版本:

@SpringBootTest
class Boot05WebAdminApplicationTests {


    @Test
    void contextLoads() {

    }
}

以前:

@SpringBootTest + @RunWith(SpringTest.class)

SpringBoot整合Junit以后。

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

2、JUnit5常用注解

JUnit5的注解与JUnit4的注解有所变化

https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

  • **@Test 😗*表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • **@ParameterizedTest 😗*表示方法是参数化测试,下方会有详细介绍
  • **@RepeatedTest 😗*表示方法可重复执行,下方会有详细介绍
  • **@DisplayName 😗*为测试类或者测试方法设置展示名称
  • **@BeforeEach 😗*表示在每个单元测试之前执行
  • **@AfterEach 😗*表示在每个单元测试之后执行
  • **@BeforeAll 😗*表示在所有单元测试之前执行
  • **@AfterAll 😗*表示在所有单元测试之后执行
  • **@Tag 😗*表示单元测试类别,类似于JUnit4中的@Categories
  • **@Disabled 😗*表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • **@Timeout 😗*表示测试方法运行如果超过了指定时间将会返回错误
  • **@ExtendWith 😗*为测试类或测试方法提供扩展类引用
@DisplayName("Junit5功能测试类")
public class Junit5Test {

    @DisplayName("测试displayname注解")
    @Test
    void testDisplayName(){
        System.out.println(1);
    }

    @BeforeEach
    void testBeforeEach(){
        System.out.println("测试开始了");
    }

    @AfterEach
    void testAfterEach(){
        System.out.println("测试结束了");
    }
    @BeforeAll
    static void testBeforeAll(){
        System.out.println("测试马上开始...");
    }
    @AfterAll
    static void testAfterAll(){
        System.out.println("测试马上结束....");
    }

    @Disabled
    @Test
    void testDisabled(){
        System.out.println("反正也不会执行");
    }

    //超时会报错
    @Timeout(value = 500,unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
        Thread.sleep(400);
    }
}

@ExtendWith

上边测试中,若使用自动装配会发现返回null,这是由于并没有把Spring的内容扩展进来,这就需要用到@ExtendWith注解

而SpringBootTest就封装了该注解,所以直接用@SpringBootTest即可

在这里插入图片描述

@RepeatedTest

重复执行

@RepeatedTest(5)
void testRepeateTest(){
    System.out.println(5);
}

3、断言(assertions)

3.1、简单断言

用来对单个值进行简单的验证。如:

方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null

示例

@DisplayName("简单断言")
@Test
public void simple(){
    int calc = calc(2, 3);
    assertEquals(5,calc);
    assertNotEquals(3,calc);

    Object o1 = new Object();
    Object o2 = new Object();
    assertSame(o2,o2);
    assertNotSame(o1,o2);

    assertTrue(true == true);
    assertFalse(true == false);

    assertNull(null);
    assertNotNull(o1);
}
int calc(int i , int j){
    return i+j;
}

3.2、数组断言

assertArrayEquals

@DisplayName("数组断言")
@Test
public void array(){
    assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}

3.3、组合断言

assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

@Test
@DisplayName("assert all")
public void all() {
    assertAll("Math",
            () -> assertEquals(2, 1 + 1),
            () -> assertTrue(1 > 0)
    );
}

3.4、异常断言

@Test
@DisplayName("异常测试")
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
           //扔出断言异常
            ArithmeticException.class, () -> System.out.println(1 % 0));

}

3.5、超时断言

Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

3.6、快速失败

通过 fail 方法直接使得测试失败

@Test
@DisplayName("快速失败")
public void shouldFail() {
 fail("This should fail");
}

4、前置条件(assumptions)

package com.sentiment.admin;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Objects;

import static org.junit.jupiter.api.Assumptions.*;

@DisplayName("前置条件测试")
public class AssumptionsTest {
        private final String environment = "DEV";

        @Test
        @DisplayName("simple")
        public void simpleAssume() {
            assumeTrue(Objects.equals(this.environment, "DEV"));
            assumeFalse(() -> Objects.equals(this.environment, "PROD"));
        }

        @Test
        @DisplayName("assume then do")
        public void assumeThenDo() {
            assumingThat(
                    Objects.equals(this.environment, "DEV1"),
                    () -> System.out.println("In DEV")
            );
        }
    }

5、嵌套测试

JUnit 5 User Guide

直接看官方示例代码吧。。

6、参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@EnumSource: 表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
}


@ParameterizedTest
@MethodSource("method")    //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
    System.out.println(name);
    Assertions.assertNotNull(name);
}

static Stream<String> method() {
    return Stream.of("apple", "banana");
}

7、迁移指南

在进行迁移的时候需要注意如下的变化:

  • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
  • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
  • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
  • 把@Ignore 替换成@Disabled。
  • 把@Category 替换成@Tag。
  • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。

监控指标

1、SpringBoot Actuator

1.1、简介

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

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

1.2、使用

  • 引入场景
  • 访问 http://localhost:8080/actuator/**
  • 暴露所有监控信息为HTTP
management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露
  • 测试

http://localhost:8080/actuator/beans

http://localhost:8080/actuator/configprops

http://localhost:8080/actuator/metrics

http://localhost:8080/actuator/metrics/jvm.gc.pause

http://localhost:8080/actuator/endpointName/detailPath

2、Actuator Endpoint

2.1、最常使用的端点

ID描述
auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans显示应用程序中所有Spring Bean的完整列表。
caches暴露可用的缓存。
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops显示所有@ConfigurationProperties
env暴露Spring的属性ConfigurableEnvironment
flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
health显示应用程序运行状况信息。
httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info显示应用程序信息。
integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
loggers显示和修改应用程序中日志的配置。
liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics显示当前应用程序的“指标”信息。
mappings显示所有@RequestMapping路径列表。
scheduledtasks显示应用程序中的计划任务。
sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown使应用程序正常关闭。默认禁用。
startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump执行线程转储。

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID描述
heapdump返回hprof堆转储文件。
jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

最常用的Endpoint

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录

2.2、Health Endpoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制

img

2.3、管理Endpoint

禁用所有的Endpoint然后手动开启指定的Endpoint

management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true

1、Profile功能

为了方便多环境适配,springboot简化了profile功能。

1.1、application-profile功能

  • 默认配置文件 application.yaml;任何时候都会加载

  • 指定环境配置文件 application-{env}.yaml

  • 激活指定环境

    • 配置文件激活 spring.profiles.active=prod
    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
      • 修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效

  • 同名配置项,profile配置优先

1.2、@Profile条件装配功能

先定义两个类分别实现Person

Boss.java

若Profile使用的是prod则加载Boss

@Profile("prod")
@ConfigurationProperties("person")
@Component
@Data
public class Boss implements Person{
    private String username;
    private Integer age;
}

Worker.java

若Profile使用的是test则加载worker

@Profile("test")
@ConfigurationProperties("person")
@Component
@Data
public class Worker implements Person {
    private String username;
    private Integer age;
}

测试

配置为:spring.profiles.active=test

@Autowired
private Person person;

@GetMapping("/person")
public String person(){
    return person.getClass().toString();
}

结果:

class com.sentiment.boot.bean.Worker

1.3、profile分组

分组功能是在SpringBoot 2.4.0版本以后才有的功能

spring.profiles.active=myprod

spring.profiles.group.myprod[0]=prod
spring.profiles.group.myprod[1]=prodd

在这里插入图片描述

此时retrun person,结果为

{"name":"prod-张三","age":20}

2、外部化配置

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

1、外部配置源

常用:Java属性文件YAML文件环境变量命令行参数

  • 环境变量:

    • @Value("${JAVA_HOME}")
      private String env;
      
      @GetMapping("/")
      private String test(){
          return env;
      }
      
    • 环境变量在ConfigurableApplicationContext也可获取

      @SpringBootApplication
      public class MainApplication {
      
          public static void main(String[] args) {
              ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
              
              ConfigurableEnvironment environment = run.getEnvironment();
              Map<String, Object> systemEnvironment = environment.getSystemEnvironment();
              Map<String, Object> systemProperties = environment.getSystemProperties();
              
              System.out.println(systemEnvironment);
              System.out.println("=========================");
              System.out.println(systemProperties);
      
          }
      }
      
  • 命令行参数:如profile中的命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha

2、配置文件查找位置

(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

3、配置文件加载顺序:

  1. 当前jar包内部的application.properties和application.yml
  2. 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3. 引用的外部jar包的application.properties和application.yml
  4. 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

4、指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

3、自定义starter

看到这里非常兴奋,有两个原因:

  1. 前边有了解SpringBoot的自动装配原理,而自定义starter感觉就是自动装配原理的使用体现,并且感觉这部分是SpringBoot中最核心的部分。
  2. 看完这节SpringBoot2的初步了解就到这了。

所以这部分我也会更认真详细的记录

首先创建两个module,分别为:starter、starter-autuconfigure

starter-autuconfigure

充当自动配置包

sayHello()方法为例

HelloService

这里sayHello()会retrun prefix 和sufix,而这两个属性在HelloSerivce中并没有定义,是从properties配置文件获取的

public class HelloService {
    @Autowired
    HelloProperties helloProperties;

    public String sayHello(String username){
        return helloProperties.getPrefix() + ":"+username+"=>"+helloProperties.getSufix();
    }
}

HelloProperties

用于获取application.properties中person.hello的配置

@ConfigurationProperties("person.hello")
public class HelloProperties {
   private String prefix;
   private String sufix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSufix() {
        return sufix;
    }

    public void setSufix(String sufix) {
        this.sufix = sufix;
    }
}

HelloServiceAutoConfiguration

用于自动装配

  • @EnableConfigurationProperties:获取HelloProperties中的配置
  • @ConditionalOnMissingBean:由于要自动配置HelloService,所以首先要判断环境中是否有该类。
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @ConditionalOnMissingBean(HelloService.class)
    @Bean
     public HelloService helloService(){
         HelloService helloService = new HelloService();
         return helloService;
     }
}

除此外需要配置一下Springboot在启动时的自动配置,放在resources/META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sentiment.hello.auto.HelloServiceAutoConfiguration

starter

starter

主要充当启动器使用

只需要将start-autoconfigure引入到该类中即可,无需进行其他配置

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>sentiment-hello-spring-boot-starter-autoconfigure</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

starter-test

在新建一个项目,用作starter测试,引入xml文件

<dependency>
    <groupId>org.example</groupId>
    <artifactId>sentiment-hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

application.properties

person.hello.sufix = 123
person.hello.prefix = 345

HelloController

@RestController
public class HelloController {
    @Autowired
    HelloService helloService;

    @GetMapping("/hello")
    public String sayHello(){
        return helloService.sayHello("Sentiment");
    }
}

访问/hello

在这里插入图片描述

后记

整个过程学习周期比预期时间长了很多,可能是因为内容的相对枯燥,全文很多地方都在进行原理分析,相对体验会差一些,但内容的质量上真的无可挑剔,从入门配置、装配原理、web开发、各组件利用、各数据库访问到最后的自定义配置,每一部分都是可圈可点,这也让我对开发有了一些理解,更好地着手于代码审计。最后贴上尚硅谷的视频链接,致敬!

参考链接:SpringBoot2核心技术与响应式编程 (yuque.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/541429.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试基础知识整理(四)- 软件开发模型、测试过程模型

目录 一、软件开发模型 1.1 瀑布模型 1.1.1 特点 1.1.2 优缺点 1.2 快速原型模型&#xff08;了解&#xff09; 1.2.1 特点 1.2.2 优缺点 1.3 螺旋模型&#xff08;了解&#xff09; 1.3.1 特点 1.3.2 优缺点 二、测试过程模型 2.1 V模型&#xff08;重点&#xff…

LeetCode_29. 两数相除

目录 题目描述 思路分析 我的题解 题目描述 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff…

8个免费的高质量UI图标大全网站

UI图标素材是设计师必不可少的设计元素。 高质量的UI图标会让设计师的设计效率事半功倍。 本文分享8个免费的高质量UI图标大全网站。 即时设计资源社区 即时设计资源广场中精选了多款专业免费的UI图标设计资源&#xff0c;无需下载即可一键保存源文件&#xff0c;同时还提供…

深入浅析Linux Perf 性能分析工具及火焰图

Perf Event 子系统 Perf 是内置于 Linux 内核源码树中的性能剖析&#xff08;profiling&#xff09;工具。它基于事件采样的原理&#xff0c;以性能事件为基础&#xff0c;支持针对处理器相关性能指标与操作系统相关性能指标的性能剖析。可用于性能瓶颈的查找与热点代码的定位…

Maven PKIX path building failed 错误提示

最近公司的项目突然出现了下面的提示。 PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target -> [Help 2]问题和解决 出现上面的提示的问题是因为 SSL 签名的问题。 …

经典面试题:理解Cookie和Session之间的区别

文章目录 一、Cookie概念先知1、Cookie是什么&#xff1f;2、Cookie从哪里来&#xff1f;3、Cookie要存到哪里去&#xff1f;4、Cookie是存在哪里的&#xff1f;5、浏览器是如何通过Cookie来记录的&#xff1f;6、Cookie的过期时间有什么用&#xff1f; 二、见见Cookie三、会话…

软件设计师考试笔记,已通过

目录 系统可靠度 外部实体 内聚类型 编译过程 逆波兰式 前驱图 scrum框架模型 编译和解释 有限自动机 聚簇索引和非聚簇索引 二叉树的前序,中序,后序遍历 动态规划贪心算法 算法 01背包问题 系统可靠度 1. 串联部件可靠度 串联部件想要这条路走通&#xff0c;只有…

软件测试行业7年了,薪资从10k到了22k,感觉到头了?

蜕变之前 明天的希望&#xff0c;让我们忘了今天的痛苦。 怎样区别一个废柴和一个精英&#xff1f;看外貌&#xff0c;看气质&#xff0c;看谈吐&#xff0c;看消费… 有人忙着把人和人进行分类&#xff0c;有人忙着怎么从这一阶层过渡到上一阶层。当你很累的时候&#xff0c…

引入外部文件实现步骤

1.引入数据库相关依赖 2.创建外部属性文件&#xff0c;properties格式&#xff0c;定义数据信息&#xff1a;用户名 密码 地址等 3.创建spring配置文件&#xff0c;引入context命名空间&#xff0c;引入属性文件&#xff0c;使用表达式完成注入 <beans xmlns"http://w…

交友项目【集成环信Api】

目录 1&#xff1a;自动装配 2&#xff1a;查询用户环信账户 3&#xff1a;环信ID查询用户信息 1&#xff1a;自动装配 在项目中集成环信API&#xff0c;完成即时通信等 环信官方文档地址&#xff1a;Java Server SDK [IM 开发文档] 自动装配模块&#xff1a; pom文件相关…

2.数据结构期末复习之顺序表和链表

1.表是可以是线性结构 学号姓名19(数据项)jams(数据项)20(数据项)ming(数据项) 19 jams或 20 ming是数据元表单个的是数据项‘’线性结构可以表示为 19 jams->20 ming2.什么是逻辑结构?:具有相同类型的有限序列(元素排序的位置,排兵布阵操作的方法) a1 a2 a3 .... an (空…

jenkins流水线使用入门示例

之前采用Jenkins的自由风格构建的项目&#xff0c;每个步骤流程都要通过不同的方式设置&#xff0c;并且构建过程中整体流程是不可见的&#xff0c;无法确认每个流程花费的时间&#xff0c;并且问题不方便定位问题。 Jenkins的Pipeline可以让项目的发布整体流程可视化&#xf…

低代码开发大势所趋,这款无代码开发平台你值得拥有

文章目录 什么是低代码iVX和其它低代码的平台的区别没有创新的“拼凑”&#xff0c;没有好东西iVX在线编辑器体验 什么是低代码 低代码&#xff08;Low Code&#xff09;是一种可视化的应用开发方法&#xff0c;用较少的代码、以较快的速度来交付应用程序&#xff0c;将程序员…

ElasticSearch漫游 (1.安装ELK)

前期准备&#xff1a; 请搭建好linux环境 推荐使用centos7系统请关闭linux防火墙请安装好docker 安装ES 创建网络 我们需要部署kibana容器&#xff0c;因此需要让es和kibana互联&#xff0c;这里先创建一个网络。 docker network create es-net加载es镜像 运行docker命令 部…

智能无线温振传感器:提高锂电设备故障诊断精度的利器

当今锂电工厂对于设备可靠性和生产效率的要求越来越高&#xff0c;而设备故障诊断是其中非常重要的一环。针对锂电设备的振动和温度等健康状态的监测&#xff0c;智能无线温振传感器是一款非常有用的工具。 图.太阳能面板生产&#xff08;iStock&#xff09; 智能无线温振传感器…

和数组处理有关的一些OJ题(JAVA)(ArrayList)

1、给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须确保时间复杂度为O(N)&#xff0c;空间复杂度为O&#xff0c;并原地修改输入数组。元素的顺序可以改…

Android系统原理性问题分析 - Android Java框架层的结构

声明 在Android系统中经常会遇到一些系统原理性的问题&#xff0c;在此专栏中集中来讨论下。Android系统&#xff0c;为了能够更好的理解Android的Java世界的运行规律&#xff0c;此篇分析Android Java框架的结构。此篇参考一些博客和书籍&#xff0c;代码基于Android 7.1.1&a…

资产处置求变,京东拍卖如何做好“价值枢纽”?

近年来&#xff0c;随着资产处置市场规模快速成长以及互联网行业飞速发展&#xff0c;金融资产、司法拍卖、罚没物资等处置方式从最初单纯线下拍卖逐渐落地互联网&#xff0c;服务专业化程度也在不断提高。为更好适应市场变化&#xff0c;满足不断增长的市场需求&#xff0c;5月…

NISP二级证书含金量如何

国家信息安全水平考试&#xff08;National Information Security Test Program&#xff0c;简称NISP&#xff09;&#xff0c;是由中国信息安全测评中心实施培养国家网络空间安全人才的项目。 为培养更多优秀的实践型网络安全人才&#xff0c;中国信息安全测评中心推出了国家…

替代MySQL半同步复制,Meta技术团队推出MySQL Raft共识引擎

作者&#xff1a;Anirban Rahut、Abhinav Sharma、Yichen Shen、Ahsanul Haque 原文链接&#xff1a;https://engineering.fb.com/2023/05/16/data-infrastructure/mysql-raft-meta/ 译者&#xff1a;ChatGPT 责编&#xff1a;张红月 MySQL Raft是MySQL数据库中一种基于Raft协议…