SpringBoot相关知识

1、SpringBoot介绍

1.1 什么是Spring框架,说一说对Spring框架的理解

Spring 是重量级企业开发框架 Enterprise JavaBean(EJB) 的替代品,Spring 为企业级 Java 开发提供了一种相对简单的方法,通过 依赖注入 和 面向切面编程 ,用简单的 Java 对象(Plain Old Java Object,POJO) 实现了 EJB 的功能。

Spring是很多模块的集合,这些模块是:核心容器数据访问/集成WebAOP(面向切面编程)工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
在这里插入图片描述

2、什么是SpringBoot?

SpringBoot本质上就是Spring,它做了一些必要的Spring Bean的配置,比如原先需要自己写一个配置类来写数据库驱动之类的东西,现在只需要一个配置文件,然后SpringBoot可以自动去读取配置完成数据库的相关装配。
它的主要优点如下:

  1. Spring Boot 不需要编写大量样板代码、XML 配置和注释。
  2. Spring Boot 遵循“固执己见的默认配置”,以减少开发工作(默认配置可以修改)。
  3. Spring Boot 应用程序提供嵌入式 HTTP 服务器,如 Tomcat 和 Jetty,可以轻松地开发和测试 web 应用程序。
  4. Spring Boot 提供命令行接口(CLI)工具,用于开发和测试 Spring Boot 应用程序,如 Java 或 Groovy。

2、SpringBoot相关注解

2.1 @SpringBootApplication注解分析

我们大概可以将它看成是@Configuration、@EnableAutoConfiguration、@ComponentScan三个注解的集合。三个注解的主要作用是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。
  • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类。
    将这些注解集合起来是为了省事儿,方便,避免每次开发SpringBoot项目都需要写一些必备的注解。

2.2 Controller层用到的一些注解

  • @RestController:将返回的对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中(一般返回的都是json,很少返回xml)。
  • @Controller:单独使用 @Controller 不加 @ResponseBody的话一般使用在要返回一个视图的情况。如果加上@ResponseBody就是和@RestController一样的。
  • @RequestMapping :上面的示例中没有指定 GET 与 PUT、POST 等,因为**@RequestMapping默认映射所有HTTP Action**,你可以使用@RequestMapping(method=ActionType)来缩小这个映射。一般将它与@PostMapping@GetMapping@DeleteMapping@PutMapping等来进行RESTFUL编程风格的编写。
  • @PathVariable :取url地址中的参数。@RequestParam url的查询参数值。
  • @RequestBody:可以将 HttpRequest body 中的 JSON 类型数据反序列化为合适的 Java 类型。
  • @ResponseEntity:表示整个HTTP Response:状态码,标头和正文内容。我们可以使用它来自定义HTTP Response 的内容。

2.3 @Component和@Bean注解的区别是什么

@Component 注解作用于类,而@Bean注解作用于方法。

  • @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我,经常和@Configuration在写配置类时一起使用。有的时候我们只能通过@Bean来实现
@Bean
public OneService getService(status) {
    case (status)  {
        when 1:
                return new serviceImpl1();
        when 2:
                return new serviceImpl2();
        when 3:
                return new serviceImpl3();
    }
}

2.4 将一个类声明为Spring的 bean 的注解有哪些?

我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

2.5 @Transactional(rollbackFor = Exception.class)注解了解吗?Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

当@Transactional注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。

2.6 如何使用JPA在数据库非持久化一个字段?

非持久化,就是这个POJO类中定义的这个变量是临时的,我们不会将其写进数据库,直接在这个字段前面加上@Transient注解

2.7 更多注解相关的知识在这里

https://snailclimb.gitee.io/javaguide/#/./docs/system-design/framework/spring/SpringBoot+Spring%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3%E6%80%BB%E7%BB%93

3、SpringBoot的一些基本操作

3.1 读取配置文件

wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油!

my-profile:
  name: Guide哥
  email: koushuangbwcx@163.com

library:
  location: 湖北武汉加油中国加油
  books:
    - name: 天才基本法
      description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。
    - name: 时间的秩序
      description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。
    - name: 了不起的我
      description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?

3.1.1 通过@value读取比较简单的配置信息

@Value("${wuhan2020}")
String wuhan2020;

这种方式不推荐

3.1.2 通过@ConfigurationProperties读取并与bean绑定

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@ConfigurationProperties(prefix = "library")
@Setter
@Getter
@ToString
class LibraryProperties {
    private String location;
    private List<Book> books;
    @Setter
    @Getter
    @ToString
    static class Book {
        String name;
        String description;
    }
}

这里我们的配置类中加上了一个@Component注解,然后我们在SpringBoot就直接可以进行加载,因为我们@componentscan注解

package cn.javaguide.readconfigproperties;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author shuang.kou
 */
@SpringBootApplication
public class ReadConfigPropertiesApplication implements InitializingBean {

    private final LibraryProperties library;

    public ReadConfigPropertiesApplication(LibraryProperties library) {
        this.library = library;
    }

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

    @Override
    public void afterPropertiesSet() {
        System.out.println(library.getLocation());
        System.out.println(library.getBooks());    }
}

我们还可以通过一些注解来对配置文件中的参数进行校验,同样是要新建一个配置类:

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;

/**
* @author shuang.kou
*/
@Getter
@Setter
@ToString
@ConfigurationProperties("my-profile")
@Validated
public class ProfileProperties {
   @NotEmpty
   private String name;

   @Email
   @NotEmpty
   private String email;
 
   //配置文件中没有读取到的话就用默认值
   private Boolean handsome = Boolean.TRUE;
}

这个时候我们没有加上@Component注解,可以在我们要使用ProfileProperties 的地方使用@EnableConfigurationProperties注册我们的配置bean:

package cn.javaguide.readconfigproperties;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/**
 * @author shuang.kou
 */
@SpringBootApplication
@EnableConfigurationProperties(ProfileProperties.class)
public class ReadConfigPropertiesApplication implements InitializingBean {
    private final ProfileProperties profileProperties;

    public ReadConfigPropertiesApplication(ProfileProperties profileProperties) {
        this.profileProperties = profileProperties;
    }

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

    @Override
    public void afterPropertiesSet() {
        System.out.println(profileProperties.toString());
    }
}

因为我们的邮箱格式不正确,所以程序运行的时候就报错,根本运行不起来,保证了数据类型的安全性。

3.1.3 @PropertySource读取指定 properties 文件

import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:website.properties")
@Getter
@Setter
class WebSite {
    @Value("${url}")
    private String url;
}

3.1.4 Spring加载配置文件的优先级

在这里插入图片描述

3.2 处理异常

3.2.1 使用 @ControllerAdvice和@ExceptionHandler处理全局异常

这是一种常用的处理方式

  1. 新建异常信息实体类
    非必要的类,主要用于包装异常信息。
public class ErrorResponse {

    private String message;
    private String errorTypeName;
  
    public ErrorResponse(Exception e) {
        this(e.getClass().getName(), e.getMessage());
    }

    public ErrorResponse(String errorTypeName, String message) {
        this.errorTypeName = errorTypeName;
        this.message = message;
    }
    ......省略getter/setter方法
}
  1. 自定义异常类型
    一般我们处理的都是 RuntimeException ,所以如果你需要自定义异常类型的话直接集成这个类就可以了。
public class ResourceNotFoundException extends RuntimeException {
    private String message;

    public ResourceNotFoundException() {
        super();
    }

    public ResourceNotFoundException(String message) {
        super(message);
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  1. 新建异常处理类
    我们只需要在类上加上@ControllerAdvice注解这个类就成为了全局异常处理类,当然你也可以通过 assignableTypes 指定特定的 Controller 类,让异常处理类只处理特定类抛出的异常,只是这种方式现在用的比较少了,一般都是通过在exceptionHandler函数中去判断异常类型。
@ControllerAdvice(assignableTypes = {ExceptionController.class})
@ResponseBody
public class GlobalExceptionHandler {

    ErrorResponse illegalArgumentResponse = new ErrorResponse(new IllegalArgumentException("参数错误!"));
    ErrorResponse resourseNotFoundResponse = new ErrorResponse(new ResourceNotFoundException("Sorry, the resourse not found!"));

    @ExceptionHandler(value = Exception.class)// 拦截所有异常, 这里只是为了演示,一般情况下一个方法特定处理一种异常
    public ResponseEntity<ErrorResponse> exceptionHandler(Exception e) {

        if (e instanceof IllegalArgumentException) {
            return ResponseEntity.status(400).body(illegalArgumentResponse);
        } else if (e instanceof ResourceNotFoundException) {
            return ResponseEntity.status(404).body(resourseNotFoundResponse);
        }
        return null;
    }
}
  1. controller模拟抛出异常
@RestController
@RequestMapping("/api")
public class ExceptionController {

    @GetMapping("/illegalArgumentException")
    public void throwException() {
        throw new IllegalArgumentException();
    }

    @GetMapping("/resourceNotFoundException")
    public void throwException2() {
        throw new ResourceNotFoundException();
    }
}

使用 Get 请求 localhost:8080/api/resourceNotFoundException (curl -i -s -X GET url),服务端返回的 JSON 数据如下:

{
    "message": "Sorry, the resourse not found!",
    "errorTypeName": "com.twuc.webApp.exception.ResourceNotFoundException"
}

3.2.2 ResponseStatusException

这种也是分为两种,第一使用@ResponseStatus注解,将异常映射为状态码

  1. 自定义异常类型
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class ResourseNotFoundException2 extends RuntimeException {

   public ResourseNotFoundException2() {
   }

   public ResourseNotFoundException2(String message) {
       super(message);
   }
}
  1. 使用Controller模拟抛出异常
@RestController
@RequestMapping("/api")
public class ResponseStatusExceptionController {
    @GetMapping("/resourceNotFoundException2")
    public void throwException3() {
        throw new ResourseNotFoundException2("Sorry, the resourse not found!");
    }
}

使用 Get 请求 localhost:8080/api/resourceNotFoundException2 ,服务端返回的 JSON 数据如下:

{
    "timestamp": "2019-08-21T07:11:43.744+0000",
    "status": 404,
    "error": "Not Found",
    "message": "Sorry, the resourse not found!",
    "path": "/api/resourceNotFoundException2"
}

还有一种方式直接可以让我们避免去生成额外的异常类,直接在controller中就完成了异常的处理

@GetMapping("/resourceNotFoundException2")
    public void throwException3() {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Sorry, the resourse not found!", new ResourceNotFoundException());
    }

使用 Get 请求 localhost:8080/api/resourceNotFoundException2 ,服务端返回的 JSON 数据如下,和使用 ResponseStatus 实现的效果一样:

{
   "timestamp": "2019-08-21T07:28:12.017+0000",
   "status": 404,
   "error": "Not Found",
   "message": "Sorry, the resourse not found!",
   "path": "/api/resourceNotFoundException3"
}

ResponseStatusException 提供了三个构造方法:

public ResponseStatusException(HttpStatus status) {
        this(status, null, null);
    }

    public ResponseStatusException(HttpStatus status, @Nullable String reason) {
        this(status, reason, null);
    }
	//分别代表http status、reseponse中的消息内容、抛出的异常
    public ResponseStatusException(HttpStatus status, @Nullable String reason, @Nullable Throwable cause) {
        super(null, cause);
        Assert.notNull(status, "HttpStatus is required");
        this.status = status;
        this.reason = reason;
    }

3.3 与JPA相关的配置

  1. 配置数据库连接信息和JPA配置
#database
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tmall_springboot?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto = none

最后这个我们一般不会配置任何东西,数据库一般是自己在底下创建好

#jpa对实体类的默认字段会把驼峰命名的属性,转换为字段名的时候自动加上下划线。 这个配置的作用就是去掉下划线
#比如属性名称是 createDate, jpa 默认转换为字段名 create_Date。 有了这个配置之后,就会转换为同名字段 createDate
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

#显示 hibernate运行的 sql 语句
spring.jpa.show-sql=true

如何检验你是否正确完成了上面 3 步?很简单,运行项目,查看数据如果发现控制台打印出创建表的 sql 语句,并且数据库中表真的被创建出来的话,说明你成功完成前面 3 步。
控制台打印出来的 sql 语句类似下面这样:

drop table if exists person
CREATE TABLE `person` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULLPRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
alter table person add constraint UK_p0wr4vfyr2lyifm8avi67mqw5 unique (name)
  1. 创建操作数据库的 Repository 接口
@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}

首先这个接口加了 @Repository 注解,代表它和数据库操作有关。另外,它继承了 JpaRepository<Person, Long>接口,而JpaRepository<Person, Long>长这样:

@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAllById(Iterable<ID> var1);

    <S extends T> List<S> saveAll(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

这表明我们只要继承了 JpaRepository<T, ID> 就具有了 JPA 为我们提供好的增删改查、分页查询以及根据条件查询等方法。

我们如果需要自己的查询方法直接把方法写进去就行,比如通过名字查询用户:

public interface UserDAO extends JpaRepository<User,Integer>{

    User findByName(String name);

    User getByNameAndPassword(String name, String password);

}

同时我们还可以自定义sql语句
根据 name 来查找 Person:

 @Query("select p from Person p where p.name = :name")
    Optional<Person> findByNameCustomeQuery(@Param("name") String name);

Person 部分属性查询,避免 select *操作:

@Query("select p.name from Person p where p.id = :id")
    String findPersonNameById(@Param("id") Long id);

3.4 过滤器实现

Filter 过滤器主要是用来过滤用户请求的,它允许我们对用户请求进行前置处理和后置处理,比如实现 URL 级别的权限控制、过滤非法请求、敏感字符等等。
如果我们需要自定义 Filter 的话非常简单,只需要实现 javax.Servlet.Filter 接口,然后重写里面的 3 个方法即可!

public interface Filter {
  
   //初始化过滤器后执行的操作
    default void init(FilterConfig filterConfig) throws ServletException {
    }
   // 对请求进行过滤
    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
   // 销毁过滤器后执行的操作,主要用户对某些资源的回收
    default void destroy() {
    }
}

如何实现拦截:
在这里插入图片描述

  1. 用户发送请求到 web 服务器,请求会先到过滤器;
  2. 过滤器会对请求进行一些处理比如过滤请求的参数、判断是否让用户访问该接口等等。
  3. 修改返回给客户端的 response 的内容、用户请求响应完毕。

自定义filter主要有以下几个步骤,有两种方式,第一种是通过自定义配置类:

  1. 自定义的 Filter 需要实现javax.Servlet.Filter接口,并重写接口中定义的3个方法。
@Component
public class MyFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(MyFilter.class);

    @Override
    public void init(FilterConfig filterConfig) {
        logger.info("初始化过滤器:", filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //对请求进行预处理
        logger.info("过滤器开始对请求进行预处理:");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestUri = request.getRequestURI();
        System.out.println("请求的接口为:" + requestUri);
        long startTime = System.currentTimeMillis();
        //通过 doFilter 方法实现过滤功能
        filterChain.doFilter(servletRequest, servletResponse);
        // 上面的 doFilter 方法执行结束后用户的请求已经返回
        long endTime = System.currentTimeMillis();
        System.out.println("该用户的请求已经处理完毕,请求花费的时间为:" + (endTime - startTime));
    }

    @Override
    public void destroy() {
        logger.info("销毁过滤器");
    }
}
  1. 编写配置类,将过滤器加进去
@Configuration
public class MyFilterConfig {
    @Autowired
    MyFilter myFilter;
    @Bean
    public FilterRegistrationBean<MyFilter> thirdFilter() {
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();

        filterRegistrationBean.setFilter(myFilter);

        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));

        return filterRegistrationBean;
    }
}

第二种是采用注解的方式:
在自己的过滤器的类上加上@WebFilter 然后在这个注解中通过它提供好的一些参数进行配置。

@WebFilter(filterName = "MyFilterWithAnnotation", urlPatterns = "/api/*")
public class MyFilterWithAnnotation implements Filter {

   ......
}

另外,为了能让 Spring 找到它,你需要在启动类上加上 @ServletComponentScan 注解

如果定义了多个配置器,就只能在配置类中进行执行顺序的配置了:

@Configuration
public class MyFilterConfig {
    @Autowired
    MyFilter myFilter;

    @Autowired
    MyFilter2 myFilter2;

    @Bean
    public FilterRegistrationBean<MyFilter> setUpMyFilter() {
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.setFilter(myFilter);
        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));

        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean<MyFilter2> setUpMyFilter2() {
        FilterRegistrationBean<MyFilter2> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setFilter(myFilter2);
        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));
        return filterRegistrationBean;
    }
}
  1. 自定义controller验证过滤器
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/hello")
    public String getHello() throws InterruptedException {
        Thread.sleep(1000);
        return "Hello";
    }
}

测试结果如下:

2019-10-22 22:32:15.569  INFO 1771 --- [           main] g.j.springbootfilter.filter.MyFilter2    : 初始化过滤器2
2019-10-22 22:32:15.569  INFO 1771 --- [           main] g.j.springbootfilter.filter.MyFilter     : 初始化过滤器
2019-10-22 22:32:55.199  INFO 1771 --- [nio-8080-exec-1] g.j.springbootfilter.filter.MyFilter2    : 过滤器开始对请求进行预处理2:
请求的接口为2:/api/hello
2019-10-22 22:32:55.199  INFO 1771 --- [nio-8080-exec-1] g.j.springbootfilter.filter.MyFilter     : 过滤器开始对请求进行预处理:
请求的接口为:/api/hello
该用户的请求已经处理完毕,请求花费的时间为:1037
该用户的请求已经处理完毕,请求花费的时间为2:1037

3.5 拦截器实现

3.5.1 拦截器和过滤器的区别

对于过滤器和拦截器的区别:

  • 过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。
  • 拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。
    自定义拦截器,我们只需要实现HandlerInceptor接口或者继承HandlerInterceptorAdapter类,并且重写下面三个方法:
public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler)
 
 
public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,
                       ModelAndView modelAndView)
 
 
public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex)

在这里插入图片描述
自定义拦截器:

  1. 实现拦截器类,这里我们定义三个拦截器类,分别用继承接口和实现类的方式实现:
    LogInterceptor用于过滤所有请求
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LogInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        long startTime = System.currentTimeMillis();
        System.out.println("\n-------- LogInterception.preHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Start Time: " + System.currentTimeMillis());

        request.setAttribute("startTime", startTime);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, //
                           Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("\n-------- LogInterception.postHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());

        // You can add attributes in the modelAndView
        // and use that in the view page
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
                                Object handler, Exception ex) throws Exception {
        System.out.println("\n-------- LogInterception.afterCompletion --- ");

        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("End Time: " + endTime);

        System.out.println("Time Taken: " + (endTime - startTime));
    }

}

OldLoginInterceptor**是一个拦截器,如果用户输入已经被废弃的链接 **“ / admin / oldLogin”,它将重定向到新的 “ / admin / login”。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class OldLoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        System.out.println("\n-------- OldLoginInterceptor.preHandle --- ");
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Sorry! This URL is no longer used, Redirect to /admin/login");

        response.sendRedirect(request.getContextPath() + "/admin/login");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, //
                           Object handler, ModelAndView modelAndView) throws Exception {

        // This code will never be run.
        System.out.println("\n-------- OldLoginInterceptor.postHandle --- ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
                                Object handler, Exception ex) throws Exception {

        // This code will never be run.
        System.out.println("\n-------- QueryStringInterceptor.afterCompletion --- ");
    }

}

AdminInterceptor用于过滤“ / admin / login”的请求

package org.o7planning.sbinterceptor.interceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
public class AdminInterceptor extends HandlerInterceptorAdapter {
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
 
        System.out.println("\n-------- AdminInterceptor.preHandle --- ");
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, //
            Object handler, ModelAndView modelAndView) throws Exception {
         
        System.out.println("\n-------- AdminInterceptor.postHandle --- ");
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
            Object handler, Exception ex) throws Exception {
 
        System.out.println("\n-------- AdminInterceptor.afterCompletion --- ");
    }
 
}

在这里插入图片描述
2. 配置拦截器

import github.javaguide.springbootfilter.interceptor.AdminInterceptor;
import github.javaguide.springbootfilter.interceptor.LogInterceptor;
import github.javaguide.springbootfilter.interceptor.OldLoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // LogInterceptor apply to all URLs.
        registry.addInterceptor(new LogInterceptor());

        // Old Login url, no longer use.
        // Use OldURLInterceptor to redirect to a new URL.
        registry.addInterceptor(new OldLoginInterceptor())//
                .addPathPatterns("/admin/oldLogin");

        // This interceptor apply to URL like /admin/*
        // Exclude /admin/oldLogin
        registry.addInterceptor(new AdminInterceptor())//
                .addPathPatterns("/admin/*")//
                .excludePathPatterns("/admin/oldLogin");
    }

}
  1. 自定义controller验证拦截器
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class InterceptorTestController {

    @RequestMapping(value = { "/", "/test" })
    public String test(Model model) {

        System.out.println("\n-------- MainController.test --- ");

        System.out.println(" ** You are in Controller ** ");

        return "test";
    }

    // This path is no longer used.
    // It will be redirected by OldLoginInterceptor
    @Deprecated
    @RequestMapping(value = { "/admin/oldLogin" })
    public String oldLogin(Model model) {

        // Code here never run.
        return "oldLogin";
    }

    @RequestMapping(value = { "/admin/login" })
    public String login(Model model) {

        System.out.println("\n-------- MainController.login --- ");

        System.out.println(" ** You are in Controller ** ");

        return "login";
    }
}

4、SpringBoot底层原理

4.1 谈谈自己对于Spring IoC和AOP的理解

IOC是一种设计思想,就是将原本在程序中手动创建对象的控制权交给了Spring框架来管理。将对象之间的相互依赖关系交给哦IOC来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。大大降低了可维护性和开发难度。
在这里插入图片描述
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。他底层的实现是
动态代理
,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理,如下图所示:
在这里插入图片描述
当然你也可以使用 AspectJ,Spring AOP 已经集成了AspectJ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。

4.2 Spring AOP和AspectJ AOP有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。

4.3 Spring bean相关内容

4.3.1 Spring bean的作用域

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。

4.3.2 Spring中单例bean的线程安全问题

的确是存在安全问题的。因为,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。

但是,一般情况下,我们常用的 Controller、Service、Dao 这些 Bean 是无状态的。无状态的 Bean 不能保存数据,因此是线程安全的。
常见的有 2 种解决办法:

  1. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
  2. 改变 Bean 的作用域为 prototype:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。

4.3.3 bean的生命周期

  • Bean 容器找到配置文件中 Spring Bean 的定义。
  • Bean 容器利用 Java Reflection API 创建一个Bean的实例。
  • 如果涉及到一些属性值 利用 set()方法设置一些属性值。
  • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入Bean的名字。
  • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
  • 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
  • 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
  • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
  • 如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
  • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
  • 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

在这里插入图片描述
更加详细的过程见下面:
https://www.zhihu.com/question/38597960

4.4 说说对Spring MVC的理解

MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的Web层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。

4.5 Spring MVC工作原理

在这里插入图片描述
流程说明:

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
  3. 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  4. HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
  6. ViewResolver 会根据逻辑 View 查找实际的 View。
  7. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  8. 把 View 返回给请求者(浏览器)

4.6 Spring中用到了哪些设计模式

其中要学会写前三种代理模式

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式(装饰器模式) : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
    更多设计模式相关知识:
    https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/Spring-Design-Patterns

4.7 Spring事务

4.7.1 事务的隔离级别

  • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

4.7.2 事务的传播行为

支持当前事务的情况

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
    不支持当前事务的情况

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
    其他情况

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
    更多事务有关的知识
    https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/Spring%E4%BA%8B%E5%8A%A1%E6%80%BB%E7%BB%93

4.8 Spring Boot自动装配原理

在原先没有自动装配的时候,我们需要自己写Spring配置类,来说明类与类之间的依赖关系,需要在xml文件中声明类和它的全路径类名。但是有了Spring Boot的自动装配,只需要一个@SpringBootApplication注解就能解决问题,我们只需要在全局配置文件application.properties以及application.yml中进行项目的一些设置,如端口号的更换。

简单的说如下:

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot定义的标准,就能将自己的功能装置进 SpringBoot。对于使用者来说,只需要在pom.xml中加入响应的starter即可

其中最核心的是@SpringBootApplication注解中的@EnableAutoConfiguration

  1. EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}

public interface DeferredImportSelector extends ImportSelector {

}

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}
  1. AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。
private static final String[] NO_IMPORTS = new String[0];

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // <1>.判断自动装配开关是否打开
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
          //<2>.获取所有需要装配的bean
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值