JAVA——比较难和底层的面试题 - Spring高级部分

Spring AOP的实现原理和场景?

概述:

最近在开发中遇到了一个刚好可以用AOP实现的例子:自定义注解+AOP实现权限验证,就顺便研究了AOP的实现原理,把学习到的东西进行一个总结。会按照如下目录展开:

  • AOP简介
  • 代码中实现举例
  • AOP实现原理
  • 部分源码解析

1. AOP简介

相信大家或多或少的了解过AOP,都知道它是面向切面编程,在网上搜索可以找到很多的解释。这里我用一句话来总结:AOP是能够让我们在不影响原有功能的前提下,为软件横向扩展功能。 那么横向扩展怎么理解呢,我们在WEB项目开发中,通常都遵守三层原则,包括控制层(Controller)->业务层(Service)->数据层(dao),那么从这个结构下来的为纵向,它具体的某一层就是我们所说的横向。我们的AOP就是可以作用于这某一个横向模块当中的所有方法

我们在来看一下AOP和OOP的区别:AOP是OOP的补充,当我们需要为多个对象引入一个公共行为,比如日志,操作记录等,就需要在每个对象中引用公共行为,这样程序就产生了大量的重复代码,使用AOP可以完美解决这个问题。

接下来介绍一下提到AOP就必须要了解的知识点:

  • 切面:拦截器类,其中会定义切点以及通知
  • 切点:具体拦截的某个业务点。
  • 通知:切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下:
    1. 前置通知:@Before 在目标业务方法执行之前执行
    2. 后置通知:@After 在目标业务方法执行之后执行
    3. 返回通知:@AfterReturning 在目标业务方法返回结果之后执行
    4. 异常通知:@AfterThrowing 在目标业务方法抛出异常之后
    5. 环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行

2. AOP实现原理

那么AOP实现的原理是什么呢?之前看了一个博客说到,提到AOP大家都知道他的实现原理是动态代理。

讲到动态代理就不得不说代理模式了, 代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式包含如下角色:subject:抽象主题角色,是一个接口。该接口是对象和它的代理共用的接口; RealSubject:真实主题角色,是实现抽象主题接口的类; Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

那么代理又分为静态代理和动态代理,这里写两个小的demo,动态代理采用的就是JDK代理。举个例子就是现在一个班上的学生需要交作业,现在由班长代理交作业,那么班长就是代理,学生就是被代理的对象。

 

2.1 静态代理

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有交作业的行为。这样,学生交作业就可以让班长来代理执行。

/**
 * Created by Mapei on 2018/11/7
 * 创建person接口
 */
public interface Person {
    //交作业
    void giveTask();
}

Student类实现Person接口,Student可以具体实施交作业这个行为。

/**
 * Created by Mapei on 2018/11/7
 */
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
 
    public void giveTask() {
        System.out.println(name + "交语文作业");
    }
}

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,那么他可以代理学生类对象执行交作业的行为。

/**
 * Created by Mapei on 2018/11/7
 * 学生代理类,也实现了Person接口,保存一个学生实体,这样就可以代理学生产生行为
 */
public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;
 
    public StudentsProxy(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }
 
    //代理交作业,调用被代理学生的交作业的行为
    public void giveTask() {
        stu.giveTask();
    }
}

下面测试一下,看代理模式如何使用:

 

/**
 * Created by Mapei on 2018/11/7
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        //被代理的学生林浅,他的作业上交有代理对象monitor完成
        Person linqian = new Student("林浅");
 
        //生成代理对象,并将林浅传给代理对象
        Person monitor = new StudentsProxy(linqian);
 
        //班长代理交作业
        monitor.giveTask();
    }
}

 这里并没有直接通过林浅(被代理对象)来执行交作业的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。代理模式就是在访问实际对象时引入一定程度的间接性,这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。比如班长在帮林浅交作业的时候想告诉老师最近林浅的进步很大,就可以轻松的通过代理模式办到。在代理类的交作业之前加入方法即可。这个优点就可以运用在spring中的AOP,我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

2.2 动态代理

动态代理和静态代理的区别是,静态代理的的代理类是我们自己定义好的,在程序运行之前就已经编译完成,但是动态代理的代理类是在程序运行时创建的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。比如我们想在每个代理方法之前都加一个处理方法,我们上面的例子中只有一个代理方法,如果还有很多的代理方法,就太麻烦了,我们来看下动态代理是怎么去实现的。

首先还是定义一个Person接口:

/**
 * Created by Mapei on 2018/11/7
 * 创建person接口
 */
public interface Person {
    //交作业
    void giveTask();
}

接下来是创建需要被代理的实际类,也就是学生类:

/**
 * Created by Mapei on 2018/11/7
 */
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
 
    public void giveTask() {
        System.out.println(name + "交语文作业");
    }
}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

/**
 * Created by Mapei on 2018/11/7
 */
public class StuInvocationHandler implements InvocationHandler {
    //invocationHandler持有的被代理对象
    T target;
 
    public StuInvocationHandler(T target) {
        this.target = target;
    }
 
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" +method.getName() + "方法");
        Object result = method.invoke(target, args);
        return result;
    }
}

那么接下来我们就可以具体的创建代理对象了。

/**
 * Created by Mapei on 2018/11/7
 * 代理类
 */
public class ProxyTest {
    public static void main(String[] args) {
 
        //创建一个实例对象,这个对象是被代理的对象
        Person linqian = new Student("林浅");
 
        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler(linqian);
 
        //创建一个代理对象stuProxy来代理linqian,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler);
 
        //代理执行交作业的方法
        stuProxy.giveTask();
    }
}

我们执行代理测试类,首先我们创建了一个需要被代理的学生林浅,将林浅传入stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数,那么所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。

Spring bean的作用域和生命周期;

作用域的种类

Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring3 为 Bean 定义了五种作用域,具体如下。

1)singleton

单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。

2)prototype

原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。

3)request

在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。

4)session

在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。

5)global Session

在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

在上述五种作用域中,singleton 和 prototype 是最常用的两种。

Spring Boot比Spring做了哪些改进?Spring 5比Spring4做了哪些改进;

主观感受上就是,springboot好用,配置简单、傻瓜操作、部署快捷、上手速度快、周边资源丰富,但是想要深入理解Springboot建议从springMVC过渡到springboot。

客观上,

springboot属于微服务框架,内部集成了tomcat,因此在打包后得到jar包而不是war包,可以直接部署到生产环境中。微服务架构强调将功能拆分为微服务进行解耦开发,不同于传统MVC架构。但是拿springboot开发MVC架构的产品问题也不大。

springboot采用了强制使用注解进行装配的方式而不是使用配置文件进行装配,使得开发成本降低。但是其自动装配和多种配置机制比较复杂,因此在后期进阶阶段需要对springboot底层足够了解才能写出较好的代码。

springboot有较为丰富的周边产品,比如security、redis、mongdb等,一定程度上简化了开发过程。

 

如何自定义一个Spring Boot Starter?

SpringBoot starter 作用?

依赖管理是所有项目中至关重要的一部分。当一个项目变得相当复杂,管理依赖会成为一个噩梦,因为当中涉及太多 artifacts 了。

这时候 SpringBoot starter 就派上用处了。每一个 stater 都在扮演着提供我们所需的 Spring 特性的一站式商店角色。其他所需的依赖以一致的方式注入并且被管理。

所有的 starter 都归于 org.springframework.boot 组中,并且它们都以由 spring-boot-starter- 开头取名。这种命名方式使得我们更容易找到 starter 依赖,特别是当我们使用那些支持通过名字查找依赖的 IDE 当中

SpringBoot启动机制(starter机制)核心原理详解

https://www.cnblogs.com/chongaizhen/p/11151892.html

SpringBoot——手把手教你自定义starter

SpringBoot是如何定义starter的

比如我们需要引入web模块


	org.springframework.boot
	spring-boot-starter-web

 

spring-boot-starter-web相当于一个启动器,而启动器模块只是一个空jar文件,只提供辅助性依赖管理,管理的这些依赖可能用于自动配置或其他功能

在这里插入图片描述

在pom.xml中可以看到spring-boot-starter-web是依赖spring-boot-starter的,而spring-boot-starter又包含了spring-boot-autoconfigure


	org.springframework.boot
	spring-boot-starter



	org.springframework.boot
	spring-boot-autoconfigure

在spring-boot-autoconfigure-web中才包含java代码

JAVA——比较难和底层的面试题 - Spring高级部分_第1张图片
JAVA——比较难和底层的面试题 - Spring高级部分_第2张图片
JAVA——比较难和底层的面试题 - Spring高级部分_第3张图片

设计思路

  • 自定义一个启动器,只进行依赖导入

  • 再专门写一个自动配置模块

具体实现参考博客:https://blog.csdn.net/weixin_38938338/article/details/104806485

Spring IOC是什么?优点是什么?

IOC:Inversion of Control控制反转,也叫(Dependency Injection)依赖注入。IoC 不是一种技术,只是一种思想。它能指导我们如何设计出松耦合、更优良的程序。比如在程序中,依赖注入就是利用某种工具,将依赖注入到需要的位置。就好比:

药物注入就是利用注射器,将药物注入到需要的人体中,就是药物注入。

依赖注入还有另一层意思:就是依赖第三方工具完成注入的操作。依赖注入的核心原理是注解和反射。
优点是

  1. 内存控制:统一管理对象,避免对象乱创建导致额外的内存开销。便于内存的优化。
  2. 降低耦合度:便于项目的扩展、易于维护。如果IoC+接口情况下,删除任意实现类都不会导致程序编译出错。虽然运行到特定得代码会报错,但是其他代码在使用时不会有问题-----从侧面也反应出是松耦合。

JAVA——比较难和底层的面试题 - Spring高级部分_第4张图片 

事务隔离级别

Spring事务的隔离级别
 1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
      另外四个与JDBC的隔离级别相对应
 2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
      这种隔离级别会产生脏读,不可重复读和幻像读。
 3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
 4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
      它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
 5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
      除了防止脏读,不可重复读外,还避免了幻像读。

什么是脏数据,脏读,不可重复读,幻觉读?
 脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,
     另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一
     个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
   
 不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。
             那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据
             可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
           
 幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及
         到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,
         以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

扫码加入:

JAVA——比较难和底层的面试题 - Spring高级部分_第5张图片

 

你可能感兴趣的:(JAVA底层面试问题)