Spring系列原理解析

249 阅读14分钟

Spring系列

Spring的特点

  1. 轻量级:完整的spring框架可以在一个大小只有1M的jar包中发布

    spring同时是非入侵式的,spring应用中的对象不依赖于spring中特定的类

  2. 控制反转:通过控制反转IOC技术促进低耦合,创建对象后其他依赖对象会通过被动方式传递过来,而需要自己创建或者查找依赖对象

  3. 面向切面:支持面向切面编程,将业务逻辑和系统服务分开

  4. 容器:spring包含了一个管理应用对象的配置和生命周期的容器。可以监控管理一个对象的配置以及存在状态。

  5. 框架集合:可以将简单的配置组合成复杂的应用,可以继承第三方框架,简化开发。

Spring IOC原理

IOC让程序员不再关注怎么去创建对象,而是把对象的创建、初始化、销毁工作都交给spring容器去管理。IOC除了通过配置文件区描述Bean和bean之间的依赖关系,实例化bean并建立相应的关系之外,IOC还提供了bean实例缓存、生命周期管理、bean实例代理、事件发布、资源装载等高级服务。

ioc容器的实现

  1. BeanFactory

是IOC容器实现的基本方式,他是面向Spring的,是Spring的内部接口不提供给开发者使用,它的特点是加载配置文件的时候不会创建对象,只有使用的时候才会创建对象。

  1. ApplicationContext

也是IOC容器实现的一种方式,是BeanFactory接口的子接口,他提供给开发者使用的,它的特点是在加载配置文件的同时就会创建对象。

  1. BeanDefinitionRegistry注册表

Spring配置文件中的每一个节点在Spring容器中都用一个BeanDefinitionRegistry来表示,他保存了Bean的配置信息,其还向容器提供了手动注册BeanDefinition对象的方法。

  1. ListableBeanFactory

定义了访问容器中Bean基本信息的若干方法,例如,查看bean个数等。

  1. HierarchicalBeanFactory 父子级联

父子级联IOC容器的接口,子容器可以通过接口方法访问父容器,但是父容器不能访问子容器。比如,在SpringMVC中,展现层bean位于子容器,而业务层和持久层bean位于父容器的。那么展现层bean可以引用持久层和业务层的bean,而持久层和业务层的bean看不到展现层的bean。

  1. ConfigurableBeanFactory

增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容 器初始化后置处理器等方法;

  1. AutowireCapableBeanFactory 自动装配

定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;

  1. SingletonBeanRegistry 运行期间注册单例 Bean

定义了允许在运行期间向容器注册单实例 Bean 的方法;对于单实例( singleton)的 Bean 来说,BeanFactory 会缓存 Bean 实例,所以第二次使用 getBean() 获取 Bean 时将直接从 IoC 容器的缓存中获取 Bean 实例。(用hashmap实现了一个bean缓存器,将beanname缓存到hashmap中以保证单实例);

  1. 依赖日志框框

在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用 Log4J, 即在类路径下提 供 Log4J 配置文件,这样启动 Spring 容器才不会报错。

Spring Bean 作用域

  1. 单例模式(Singleton)

是spring IOC容器的默认作用域,容器中只会有一个bean,无论多少bean指向它,引用的都是同一个bean,在多线程的环境下是不安全的。

可以用双重检查加锁机制来解决线程不安全问题。单例模式不安全的原因是因为采用了懒汉式加载模式,也就是说,在bean加载的时候不会创建实例,而再要使用的时候才会创建实例,其核心代码是:

public static Singleton getInstance() {
    Singleton instance = (Singleton)map.get(DEFAULT_KEY);
    if(instance == null) {
        instance = new Singleton();
        //运用缓存思想通过hashmap缓存这个对象
    }
    return instance;
}

这段代码在并发环境下,可能会同时创建多个对象,所以线程不安全。

可以用双重检查加锁机制来解决这个问题。

所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

简单来说,就是在拥有实例的情况下,不用因为代码同步问题,导致bean的获取过慢,只有在实例不存在的会后才会进行代码同步,这样又保证了单例模式的线程安全问题。

private volatile static Singleton instance;
public static Singleton getInstance() {
    if(instance == null) {
        synchronize(Singleton.class) {
            if(instance == null) {
                instance = new Singleton();
        		//运用缓存思想通过hashmap缓存这个对象
            }
        }
    }
    return instance;
}

这里使用了一个volatile关键字,为的是让指令不会重排,保证实例创建的单一性。

  1. 原型模式(prototype)

每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建 一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对 象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton 作用域。

  1. request

在一次http请求中,容器会返回该bean的同一实例,而不同的http请求会仙剑一个bean,并且该bean的生命周期仅存在于这次http请求中。

  1. session

在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请 求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。

Spring Bean 的生命周期

  1. 实例化一个bean,创建一个bean对象
  2. IOC依赖注入

根据spring上下文对实例化bean进行配置,相当于执行set方法设置属性

  1. 如果实现了beannameaware接口,此时会执行setBeanName()方法。传递配置文件中bean节点的id值。
  2. 如果实现了beannameware接口,会调用它实现的 setBeanFactory,传递spring工厂自身。
  3. 如果这个bean实现了applicationcontextaware接口会调用setapplicationcontext()方法,传递spring的上下文。
  4. 如果这个bean关联了beanpostprocessor接口,那么将执行postProcessBeforeInitialization()方法,这个方法常用来对bean进行修改,由于这个实在bean初始化阶段调用的方法,所以可用于内存和缓存技术。
  5. 如果关联和init-method方法,那么就会执行配置的初始化方法。
  6. 如果bean关联了beanpostprocessor接口,再次调用6步骤
  7. 当bean不在需要时,会经过自动清理阶段,如果bean实现了DisposableBean接口,会调用其实现的destory()方法;
  8. 最后,如果这个bean的spring配置中配置了destroy-method 属性,会自动调用其配置的 销毁方法。

image-20210912162148436

Spring依赖注入的四种方式

  1. 构造器注入
/*带参数,方便利用构造器进行注入*/ 
 public CatDaoImpl(String message){ 
 this. message = message; 
 } 
<bean id="CatDaoImpl" class="com.CatDaoImpl"> 
<constructor-arg value=" message "></constructor-arg> 
</bean>

  1. setter 方法注入
public class Id { 
 private int id; 
 public int getId() { return id; } 
 public void setId(int id) { this.id = id; } 
} 
<bean id="id" class="com.id "> <property name="id" value="123"></property> </bean>
  1. 静态工厂注入

就是通过调用静态工厂的方法来获取自己需要的对象,为了让 spring 管理所 有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过 spring 注入的形式获 取:

public class DaoFactory { //静态工厂 
 public static final FactoryDao getStaticFactoryDaoImpl(){ 
 return new StaticFacotryDaoImpl(); 
 } 
} 
public class SpringAction { 
 private FactoryDao staticFactoryDao; //注入对象
 //注入对象的 set 方法 
 public void setStaticFactoryDao(FactoryDao staticFactoryDao) { 
 this.staticFactoryDao = staticFactoryDao; 
 } 
} 
//factory-method="getStaticFactoryDaoImpl"指定调用哪个工厂方法
 <bean name="springAction" class=" SpringAction" > 
 <!--使用静态工厂的方法注入对象,对应下面的配置文件--> 
 <property name="staticFactoryDao" ref="staticFactoryDao"></property> 
 </bean> 
 <!--此处获取对象的方式是从工厂类中获取静态方法--> 
<bean name="staticFactoryDao" class="DaoFactory" 
factory-method="getStaticFactoryDaoImpl"></bean>
  1. 实例工厂

实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先 new 工厂类,再调用普通的实例方法:

public class DaoFactory { //实例工厂 
 	public FactoryDao getFactoryDaoImpl(){ 
 		return new FactoryDaoImpl(); 
 	} 
} 
public class SpringAction { 
 	private FactoryDao factoryDao; //注入对象 
 	public void setFactoryDao(FactoryDao factoryDao) { 
 		this.factoryDao = factoryDao; 
 	} 
} 
 <bean name="springAction" class="SpringAction"> 
 <!--使用实例工厂的方法注入对象,对应下面的配置文件--> 
 <property name="factoryDao" ref="factoryDao"></property> 
 </bean> 
 <!--此处获取对象的方式是从工厂类中获取实例方法--> 
<bean name="daoFactory" class="com.DaoFactory"></bean> 
<bean name="factoryDao" factory-bean="daoFactory"
factory-method="getFactoryDaoImpl"></bean>

5种不同的自动装配方式

  1. no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。

  2. byName:通过参数名 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设 置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。

  3. byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被 设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多 个 bean 符合条件,则抛出错误。

  4. constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数 的构造器参数类型,将会抛出异常。

  5. autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式

Aop原理

AOP术语

连接点

类里面的哪些方法可以被增强,这些方法就叫做连接点

切入点

实际真正被增强的方法叫切入点

通知(增强)

实际被增强的逻辑部分叫通知

通知有多种类型

  • 前置通知

  • 后置通知

  • 环绕通知

  • 异常通知

  • 最终通知

切面

是一个动作,把通知应用到切入点的过程叫切面

AOP的两种代理方式

Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口, 则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。

JDK 动态接口代理

JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。 InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类 的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建 一个符合某一接口的实例,生成目标类的代理对象

有接口的情况(对接口实现类进行增强的情况):使用jdk动态代理

​ 创建一个接口实现类的代理对象,

​ 使用jdk中Proxy类中的newProxyInstance创建代理方法

​ 参数1:类加载器

​ 参数2:增强方法所在的类,这个类实现的接口,支持多个接口

​ 参数3:实现InvocationHandler,创建代理对象,写增强的方法

public class JDKProxy {
    
    public static void main(String[] args) {
        //需要增强方法实现的接口,可以是多个
        Class[] interfaces = {UserDao.class};
        UserDao o = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader()/*类加载器*/, interfaces/*增强方法实现的接口*/, new UserDaoProxy(new UserDaoImpl()) /*代理对象类*/);
        System.out.println(o.add(1, 2));
    }
}
class UserDaoProxy implements InvocationHandler{

    private  Object obj;
    //把创建的是谁的代理对象,把谁传过来(实现类)
    public UserDaoProxy(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //方法之前
        System.out.println("方法之前执行:  " + method.getName() +  "   参数:" + Arrays.toString(args));
        //原来的逻辑
        Object invoke = method.invoke(obj, args);

        //方法之后
        System.out.println("方法执行之后执行。。。。。。");
        return invoke;
    }
}

基于注解方式实现

1.创建一个类,在类里面定义方法,实现其增强

2.创建增强类(编写增强逻辑)

​ 在增强类上面加上@Aspect注解 生成代理对象

3.配置文件中开启生成代理对象

4.配置不同类型的通知

​ 在增强类作为通知的方法上面添加通知类型注解并写切入点表达式

@Component
public class User {
    public void add() {
        System.out.println("add ........");
    }
}

//增强类
@Component
@Aspect
public class UserProxy {
    //前置通知
    @Before(value = "execution(* com.example.spring5.service.aop.User.add(..))")
    public void before() {
        System.out.println("before.......");
    }
    //在方法执行后
    @After(value = "execution(* com.example.spring5.service.aop.User.add(..))")
    public void after() {
        System.out.println("after");
    }
    //在方法返回值返回之后
    @AfterReturning(value = "execution(* com.example.spring5.service.aop.User.add(..))")
    public void afterRtreuning() {
        System.out.println("afterReturning");
    }
    //异常
    @AfterThrowing(value = "execution(* com.example.spring5.service.aop.User.add(..))")//
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }
    @Around(value = "execution(* com.example.spring5.service.aop.User.add(..))") //方法执行前和执行后
    public void arount(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("增强前...");
        proceedingJoinPoint.proceed();//执行增强的方法
        System.out.println("增强后...");
    }

}

公共切入点抽取

 //切入点抽取
    @Pointcut(value = "execution(* com.example.spring5.service.aop.User.add(..))")
    public void point() {
        
    }

    //前置通知
    @Before(value = "point()")
    public void before() {
        System.out.println("before.......");
    }

CGLIB动态代理

CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库, 可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新 的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例, 而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。

SpringMVC原理

Spring 的模型-视图-控制器是围绕着DispatcherServlet设计的,这个servelet主要就是用来分发请求给各个处理器的。,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染 等,甚至还能支持文件上传。

MVC的流程:

  1. 浏览器发起请求到达DispatcherServlet

  2. 寻找处理器:由 DispatcherServlet 控制器查询一个或多个 HandlerMapping,找到处理请求的 Controller。

  3. 调用处理器: DispatcherServlet 将请求提交到 Controller。

  4. 调用模型处理业务:controller调用service

  5. 得到处理结果::Controller 调用业务逻辑处理后,返回 ModelAndView。

  6. 处理视图映射: DispatcherServlet 查询一个或多个 ViewResoler 视图解析器, 找到 ModelAndView 指定的视图。

  7. 将模型数据传给view显示

  8. 返回http响应

Springboot原理

Springboot是spring和springMVC的简化版,主要是致力于快速开发应用领域。

特点:

  1. 创建独立的spring应用程序
  2. 内嵌一个Tomcat,无需部署war文件
  3. 简化maven配置
  4. 自动配置spring
  5. 提供生产就绪型功能,如指标,健康检查和外部配置
  6. 不会强制要求配置xml
JPA原理

事务

**本地事务:**事务分为本地事务和分布式事务两种,本地事务指的是在单个数据源上进行访问和更新,分布式事务指在多个数据源上进行数据的访问和更新。

分布式事务:

public void transferAccount() { 
UserTransaction userTx = null; 
Connection connA = null; Statement stmtA = null; 
Connection connB = null; Statement stmtB = null; 
try{ 
// 获得 Transaction 管理对象
userTx = (UserTransaction)getContext().lookup("java:comp/UserTransaction"); 
connA = getDataSourceA().getConnection();// 从数据库 A 中取得数据库连接
connB = getDataSourceB().getConnection();// 从数据库 B 中取得数据库连接
userTx.begin(); // 启动事务
stmtA = connA.createStatement();// 将 A 账户中的金额减少 500 
stmtA.execute("update t_account set amount = amount - 500 where account_id = 'A'");
// 将 B 账户中的金额增加 500 
stmtB = connB.createStatement(); 
13/04/2018 Page 136 of 283
stmtB.execute("update t_account set amount = amount + 500 where account_id = 'B'");
userTx.commit();// 提交事务
// 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
} catch(SQLException sqle){ 
// 发生异常,回滚在本事务中的操纵
 userTx.rollback();// 事务回滚:数据库 A 和数据库 B 中的数据更新被同时撤销
} catch(Exception ne){ } 
}

Mybatis缓存

Mybatis 中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存 是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以 后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。二级缓存 是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的 sqlsession 是可以共享的

一级缓存:

当同一个SQLsession发出相同的SQL,那么第二次SQL查询就会从缓存中读取结果,不会从数据库中再次查询,但是如果两次查询中间出现了增删改的操作就会造成一级缓存被清空,第二次查询就会重新进入数据库进行查询并切入缓存。

二级缓存:

二级缓存是mapper级别的,也就是同一个命名空间,通过map实现。mybatis 的二级缓存是通过 CacheExecutor 实现的。CacheExecutor 13/04/2018 Page 139 of 283 其实是 Executor 的代理对象。所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存 在,不存在则查询数据库。

配置二级缓存:

  1. Mybatis 全局配置中启用二级缓存配置

  2. 在对应的 Mapper.xml 中配置 cache 节点

  3. 在对应的 select 查询节点中添加 useCache=true