Spring框架(源码级详解)

473 阅读13分钟

什么是spring?

广义上来说就是以springFramwork为核心的spring技术栈,狭义上来说就是特指springframwork。

springframwork的特点

1.非侵入式:因为只需要几个简单的注解,完全不会破坏原有的结构。

2.控制反转IOC:即翻转资源获取方向,之前都是我们去写资源数据,现在成了我们向环境索取即可。

3.面向切面编程AOP:在不改变源代码的基础上增强代码功能。

4.容器:Ioc容器帮我们管理组件的生命周期。

5.组件化:在spring中使用xml和java注解来组合对象。

IOC容器

IOC容器里面存放Bean,第一步我们当然需要先创建IOC容器,底层的接口是BeanFactory,但是这个不面向开发者,我们可以使用它的子接口:ApplicationContext,比如我们最常见的ClassPathXmlApplicationContext实现类,这个可以读取类路径下的xml配置文件并生成Ioc容器。

既然是控制反转,那就是将创建对象的权利交给容器,对象与对象之间的依赖和维护交给容器。

那么是如何获得对象的呢,很简单,通过bean的信息属性等内容后再通过反射创建对象,然后进行依赖注入初始化对象,最后生成对象,这个在容器里面称作bean。存放在map集合里面。

容器里面的bean管理就是依赖注入。主要是两种方式:setter注入构造注入

基于xml管理bean-获取bean

获取bean有三种方式,id,类型,id+类型。我直接举个例子就明白了。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="org.example.User"></bean>
</beans>

可以看的出来里面有类的全名称和id属性。

package org.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) throws Exception {
        //加载bean.xml里面的bean,获取里面bean的属性值。然后反射生成对象。
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = (User) context.getBean("user");
        System.out.println(user);
        User user1 = context.getBean(User.class);
        System.out.println(user1);
        User user2 = context.getBean("user",User.class);
        System.out.println(user2);
    }
}

注意,用类型获取Bean的时候,要求Ioc容器中指定类型的bean只能有一个。接口类型的bean也是同理。并且接口的多个实现类也不可以。

依赖注入——setter注入

首先就是我们的实体类:

package org.example;

public class User {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "User{"+ "name = " + this.name + ";age="+this.age+"}";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然后就是xml文件了:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="org.example.User">
        <property name="age" value="18"></property>
        <property name="name" value="xixi"></property>
    </bean>
</beans>

里面的name的属性值就是set后面的小写。

依赖注入——构造器注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="org.example.User">
        <constructor-arg name="name" value="xiix"/>
        <constructor-arg name="age" value="18"/>
    </bean>
</beans>

前提是在实体类中一定要先写好有参构造。

Bean的生命周期

bean的生命周期分为七步,最后一步的ioc容器关闭就不算了,我们直接看下:

1.Bean对象创建(调用无参构造)。

2.给Bean对象添加相关属性。

3.Bean的后置处理器(初始化方法之前)。

4.Bean的对象初始化。

5.Bean的后置处理器(初始化方法之后)。

6.Bean对象创建完成。

7.Bean对象的销毁。

光说没有用,看如下代码:

实体类:

package org.example;

public class User {
    private String name;
    private int age;

    public void destroyMethod(){
        System.out.println("7.销毁bean");
    }

    public void initMethod(){
        System.out.println("4.初始化方法");
    }

    public User() {
        System.out.println("1.bean对象创建调用无参构造");
    }

    @Override
    public String toString() {
        return "User{"+ "name = " + this.name + ";age="+this.age+"}";
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("2.对象添加属性");
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

后置处理器:

package org.example.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class springBeanConfig implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3.初始化方法之前的后置处理器");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5.初始化方法之后的后置处理器");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="org.example.User" init-method="initMethod" destroy-method="destroyMethod" scope="singleton">
        <property name="name" value="xixi"/>
        <property name="age" value="18"/>
    </bean>
    <!--bean的后置处理器必须放入ioc容器中才可以生效-->
    <bean id="BeanConfig" class="org.example.config.springBeanConfig"/>
</beans>

Main运行文件:

package org.example;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        //加载bean.xml里面的bean,获取里面bean的属性值。然后反射生成对象。
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean("user",User.class);
        System.out.println("6.对象初始化完成,可以使用");
        System.out.println(user);
        context.close();
        System.out.println("8.ioc容器关闭");
    }
}

运行结果如下所示:

![1039190a1ae12b9b2c286526c62de0c](C:\Users\86166\Documents\WeChat Files\wxid_fr4ly5sexit322\FileStorage\Temp\1039190a1ae12b9b2c286526c62de0c.png)

自动装配

所谓的自动装配就是我们不需要再像之前那样手动去依赖注入,而是ioc容器帮我们注入,当然这也需要我们手动去配置。

我设计了三个包,controller和service和dao,我们知道这三个是依次调用的,如下:

controller:
package org.example.controller;

import org.example.service.SpringService;

public class SpringController {

    private SpringService springService;

    public void setSpringService(SpringService springService) {
        this.springService = springService;
    }

    public void controller(){
        System.out.println("controller:执行了");
        springService.service();
    }
}
dao:
package org.example.dao;

public interface SpringDAO {
    public void dao();
}

package org.example.dao.impl;

import org.example.dao.SpringDAO;

public class SpringDAOImpl implements SpringDAO {
    @Override
    public void dao() {
        System.out.println("DAO方法执行了");
    }
}
service:
package org.example.service;

public interface SpringService {
    public void service();
}

package org.example.service.impl;

import org.example.dao.SpringDAO;
import org.example.service.SpringService;

public class SpringServiceImpl implements SpringService {

    private SpringDAO springDAO;

    public void setSpringDAO(SpringDAO springDAO) {
        this.springDAO = springDAO;
    }

    @Override
    public void service() {
        System.out.println("SpringService执行了。");
        springDAO.dao();
    }
}

然后我们的bean.xml文件是这样子写的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="controller" class="org.example.controller.SpringController" autowire="byType">
    </bean>
    <bean id="service" class="org.example.service.impl.SpringServiceImpl" autowire="byType">
    </bean>
    <bean id="dao" class="org.example.dao.impl.SpringDAOImpl">
    </bean>
</beans>

注意了,由于我们的自动装配是根据类型判断的,所以bean不可以重复,比如说SpringServiceImpl里面对应的类型是SpringDAO类型,那么ioc容器就会寻找SpringDAO类的bean,如果没有,则默认为null,如果有多个重复的bean或者接口被多个实现类实现,就会报错。

那么如果用名称注入会怎么办。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="controller" class="org.example.controller.SpringController" autowire="byName">
    </bean>
    <bean id="springService" class="org.example.service.impl.SpringServiceImpl" autowire="byName">
    </bean>
    <bean id="springDAO" class="org.example.dao.impl.SpringDAOImpl">
    </bean>
</beans>

注意了,bean的id名称必须和依赖注入里面的成员变量名称一模一样

创建Bean对象

那么像上面那样一个一个的去注入显得非常麻烦,这里我们可以直接在xml文件里面写入全局扫描即可,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 扫描指定包下的组件 -->
    <context:component-scan base-package="org.example"/>

</beans>

千万别忘记引入Spring Context的命名空间哦。

这样一来,我们在类名称上可以添加注解可以直接将他们注册为ioc容器里面的bean。例如component注解、repository注解、service注解、controller注解。

Autowired注解的相关使用

这个注解是根据类型判断的,我们看他的源码可以明白,它可以添加在属性注入,set注入,构造方法注入,形参注入,甚至可以和@Qualifier注解联合使用。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

另外还有一种情况,就是当bean里面只有一个有参构造函数时,无需添加注解便可以自动注入。

Resource注解的相关使用

这个注解相比于Autowired不同的是默认是使用名称name匹配的,如果没有name,那么就根据属性名匹配,如果还是没有,那就根据类型匹配。另外,它只能用于属性注入set注入

package org.example.service.impl;

import org.example.dao.SpringDAO;
import org.example.service.SpringService;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class SpringServiceImpl implements SpringService {

    @Resource(name = "dao")
    private SpringDAO springDAO;

    public void setSpringDAO(SpringDAO springDAO) {
        this.springDAO = springDAO;
    }

    @Override
    public void service() {
        System.out.println("SpringService执行了。");
        springDAO.dao();
    }
}
package org.example.dao.impl;

import org.example.dao.SpringDAO;
import org.springframework.stereotype.Service;

@Service(value = "dao")
public class SpringDAOImpl implements SpringDAO {
    @Override
    public void dao() {
        System.out.println("DAO方法执行了");
    }
}

AOP

这里我只说动态代理,静态代理就是创建了另一个代理类,直接将处理逻辑写死在里面,想理解非常简单,所以我这里只说动态代理。

动态代理原理简单详解

其实就是通过类加载器在编译后也能动态的加载相关的类并获得类的信息,然后再通过反射获得实际对象的方法以及再调用它。

1.创建接口和实现类

package org.example.service;

public interface Service {
    public void add();
}
package org.example.service.impl;

import org.example.service.Service;

public class ServiceImpl implements Service {
    @Override
    public void add() {
        System.out.println("方法执行了");
    }
}

2.创建代理类(又称为切面

package org.example.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class AopTest {

    //生成实际对象
    private Object target;

    //注入实际对象
    public void setTarget(Object target) {
        this.target = target;
    }

    //返回代理对象
    public Object getTarget() {
        //1.先生成动态代理对象需要的类加载器。
        ClassLoader classLoader = target.getClass().getClassLoader();
        //2.生成动态代理对象要实现的接口数组。
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //3.写调用处理器
        InvocationHandler handler = new InvocationHandler() {

            //里面的method方法我们已经通过反射获取到了。
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //调用方法前的逻辑(横切关注点或通知)
                System.out.println(method.getName() + "方法开始执行啦");

                //执行相关方法
                Object invoke = method.invoke(target, args);

                //方法执行后的相关逻辑(横切关注点或通知)
                System.out.println(method.getName() + "方法已经执行完啦");

                return invoke;
            }
        };
        //返回动态代理对象
        return Proxy.newProxyInstance(classLoader,interfaces,handler);
    }

}

3.在Main方法里面运行看看结果:

package org.example;

import org.example.aop.AopTest;
import org.example.service.Service;
import org.example.service.impl.ServiceImpl;

public class Main {
    public static void main(String[] args) {
        AopTest aopTest = new AopTest();
        aopTest.setTarget(new ServiceImpl());
        Service target = (Service) aopTest.getTarget();
        target.add();
    }
}

结果如下:

add方法开始执行啦
方法执行了
add方法已经执行完啦

Process finished with exit code 0

另外在aop里面还有主要的两点,就是连接点和切入点。前者就是指spring允许你使用通知的地方,后者是指要实际去增强的地方。举个例子,如果连接点是数据库中的记录,那么切入点就是查询记录的SQL语句。

但是大家看到这样子写代理类好像很麻烦,我们是不是可以直接通过注解和xml方式来解决这种问题呢?答案是肯定可以的。

基于注解的AOP

第一步,添加依赖

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.2</version>
        </dependency>

第二步,配置xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 扫描指定包下的组件 -->
    <context:component-scan base-package="org.example"/>

    <!-- 启用 AspectJ 自动代理 -->
    <aop:aspectj-autoproxy/>

</beans>

第三步,编写代码

这里我们是对有接口进行动态代理,所以使用的是JDK的动态代理,并且接口和实现类我已经写好了,这里我们只露出来代理类,里面的五种通知注解大家需明白。

package org.example.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class Aop {

    //里面的*代表 任意修饰符 和 任意数据类型,后面的sout代表类的目标方法里面的..代表参数
    @Before("execution(* org.example.service.impl.UserServiceImpl.sout(..))")
    public void before(){
        System.out.println("前置通知");
    }

    @After("execution(* org.example.service.impl.UserServiceImpl.sout(..))")
    public void after(JoinPoint joinPoint){
        System.out.println("后置通知"+joinPoint.getSignature());
    }

    @AfterReturning("execution(* org.example.service.impl.UserServiceImpl.sout(..))")
    public void afterReturning(JoinPoint joinPoint){
        System.out.println("返回通知");
    }

    @AfterThrowing("execution(* org.example.service.impl.UserServiceImpl.sout(..))")
    public void afterThrowing(){
        System.out.println("异常通知,只有代码发生异常时会抛出。");
    }

    @Around("execution(* org.example.service.impl.UserServiceImpl.sout(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // 在目标方法执行前的代码
        System.out.println("Before method execution: " + joinPoint.getSignature());

        Object result = joinPoint.proceed(); // 执行目标方法

        // 在目标方法执行后的代码
        System.out.println("After method execution: " + joinPoint.getSignature());

        return result;
    }
    
}

额外知识:重用切入点和切面优先级

先说说什么是重用切入点,大家在注解里面的"execution(* org.example.service.impl.UserServiceImpl.sout(..))"应该已经看到了,这个很多地方都要重复用到这个东西,因此我们写个方法,如下:

    @Pointcut("execution(* org.example.service.impl.UserServiceImpl.sout(..))")
    public void pointCut(){}

当然,如果想让其他通知使用也是非常简单,直接如下:

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

上面是指在同一个切面中使用,那么在不同的切面中使用则如下:

    @Before(value = "org.example.aop.Aop.pointCut()")
    public void before(){
        System.out.println("前置通知");
    }

然后接下来就该切面优先级了,如下所示:

1.通过@Order注解 Spring 提供了@Order注解来控制切面的优先级。@Order注解用于标识切面的执行顺序,值越小优先级越高。即带有较低@Order值的切面会先执行。

@Aspect
@Order(1) // 优先级最高,最先执行
public class FirstAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeMethod() {
        System.out.println("FirstAspect: before method");
    }
}

@Aspect
@Order(2) // 优先级较低,后执行
public class SecondAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeMethod() {
        System.out.println("SecondAspect: before method");
    }
}

上述代码中,FirstAspect@Order(1)SecondAspect@Order(2)优先级更高,因此FirstAspect会先执行。

2.默认顺序 如果没有使用@Order注解来指定优先级,Spring 会默认认为这些切面具有相同的优先级。对于相同优先级的切面,Spring 不保证它们的执行顺序。

基于XML的AOP

假设你有一个Service类,里面有一个方法doSomething(),你希望在这个方法执行前后添加通知。以下是使用XML配置AOP的步骤:

1. 配置切面类和通知方法

首先,你需要定义一个切面类,里面包含你希望执行的通知方法。例如:

package com.example.aspect;

public class MyAspect {

    public void beforeAdvice() {
        System.out.println("Before method execution");
    }

    public void afterAdvice() {
        System.out.println("After method execution");
    }

    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Before method execution in around");
        joinPoint.proceed(); // 执行目标方法
        System.out.println("After method execution in around");
    }
}

2. 在XML中配置AOP

然后,你在Spring的XML配置文件中定义切面和通知。例如:

<!-- 开启AOP支持 -->
<aop:config>
    <!-- 定义一个切面 -->
    <aop:aspect ref="myAspect">
        
        <!-- 定义前置通知 -->
        <aop:before method="beforeAdvice" pointcut="execution(* com.example.service.*.*(..))"/>
        
        <!-- 定义后置通知 -->
        <aop:after method="afterAdvice" pointcut="execution(* com.example.service.*.*(..))"/>
        
        <!-- 定义环绕通知 -->
        <aop:around method="aroundAdvice" pointcut="execution(* com.example.service.*.*(..))"/>
        
    </aop:aspect>
</aop:config>

<!-- 声明切面类为一个Bean -->
<bean id="myAspect" class="com.example.aspect.MyAspect"/>

XML配置解读

  • <aop:config>:启动AOP配置。
  • <aop:aspect>:定义一个切面,通过ref属性引用已经定义的切面类。
  • <aop:before>:配置前置通知,method属性指定切面类中的方法名,pointcut属性指定切入点表达式,决定在哪些方法之前执行通知。
  • <aop:after>:配置后置通知,methodpointcut属性的含义同上。
  • <aop:around>:配置环绕通知,使用ProceedingJoinPoint控制目标方法的执行。

3. 使用XML配置切面优先级

在XML中,切面执行顺序默认是按照声明的顺序执行的。如果你需要设置切面优先级,可以在XML中通过order属性来指定优先级:

<aop:aspect ref="firstAspect" order="1">
    <!-- 通知配置 -->
</aop:aspect>

<aop:aspect ref="secondAspect" order="2">
    <!-- 通知配置 -->
</aop:aspect>

order属性值越小,优先级越高。

总结

通过XML配置AOP通知,虽然配置过程比注解方式稍微复杂,但它提供了更灵活的管理方式,尤其在一些配置文件驱动的项目中仍然具有实际应用价值。

事务

数据库事务就是指一组以单个逻辑单元执行的数据库操作。这些操作要么全部成功,要么全部失败。不会出现部分成功或部分失败的情况。事务具有四个基本特性,简称ACID特性。

1.原子性(Atomicity)

事务中的操作要么全部执行完成,要么都不执行,如果事务在执行中发生错误,那么回滚**(rollback)**到事务开始的状态,就好像完全没执行过一样。

2.一致性(Consistency)

事务执行前和执行后的数据库状态必须是一致的。数据库从一个一致的状态转换到另一个一致的状态。这意味着事务在完成时,所有的数据规则(如约束、触发器等)都必须得到满足。

3.隔离性(Isolation)

事务的执行不会受到其他并发事务的影响。即使有多个事务并发执行,数据库的状态应该与这些事务按某个顺序顺序执行的效果一致。

4.持久性(Durability)

一旦事务提交(Commit),其结果将被永久保存到数据库中,即使系统发生故障,已提交的事务数据也不会丢失。