SPring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。

Spring的构成

image-20230114102626339

  1. 核心容器(Spring Core)

核心容器提供Spring框架的基础功能。Spring以bean的方式进行java应用的各大组件及关系的组织和管理。Spring使用BeanFactory来产生和管理bean,是工厂模式的实现。BeanFactory使用控制反转(IOC)模式来将应用的配置和依赖性规范与实际的应用程序代码分开。

  1. 应用上下文(Spring Context)

实现了ApplicationContext接口,Spring的上下文,拓展了核心容器,提供事件处理、国际化等功能。它还提供了一些企业级服务的功能,提供了JNDI、EJB、RMI的支持。

  1. Spring面向切面编程(Spring AOP)

提供切面支持,是个轻量级的容器。Spring管理的任何对象都支持AOP,SpringAOP模块基于Spring的应用程序中的对象提供了事务管理服务,通过使用SpringAOP,就可以将声明性事务管理集成在应用程序中。

  1. JDBC和DAO模块(Spring DAO)

提供对JDBC的支持,还提供了DAO的支持,提供事务支持。

JDBC、DAO的抽象层,提供了有意义的异常层次结构实现,可用该结构来管理异常处理,和不同数据库提供商抛出的错误信息,异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。

  1. 对象实体映射(Spring ORM)

ORM:Object Relational Mapping,指对象实体映射。Spring插入了若干个ORM框架,提供了ORM对象的关系工具,其中包括Hibernate,JDO和IBatisSQL Map等,所有这些都遵从Spring的通用事务和DAO异常层次结构。

  1. Web模块(Spring Web)

拓展了Spring上下文,提供Web应用上下文,对Web开发提供功能上的支持,如请求、表单、异常等。

  1. MVC模块(SpringWebMVC)

MVC框架是一个全功能的构建Web应用程序的MVC实现,通过策略接口,MVC框架编程高度可配置的,MVC容纳了大量视图技术,其中包括JSP,POI等,模型由JavaBean来构成,存放于m当中,而视图是一个接口,负责实现模型,控制器表示逻辑代码,由c的事情。

spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境,spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。

IOC(Inversion of Control)

在以往的业务中,用户的需求会影响原代码,这时会根据用户的需求频繁修改代码。如果程序代码量较大, 修改的代价较高。这时可以使用注入。例如:

//传统方式
private UserDao userDao = new UserDao();

private void getUser(){
  userDao.getUser();
}

//新方式-----------------------------

private UserDao userDao;
//利用set实现动态的注入
public void setUserDao(UserDao userDao){
  this.userDao = userDao;
}

private void getUser(){
  userDao.getUser();
}

在上述示例中,若UserDao发生修改,直接修改Dao 的传入即可,无需再逐个修改UserDao在各个调用中的代码。

  • 传统方式为程序主动创建对象,控制权在程序员手中
  • 使用注入后,程序不再有主动性,而是被动接收对象

至此,实现了对象创建的自动管理,编码时无需管理对象的创建。系统的耦合性大大降低。

此即为IOC的原型

控制反转(Inversion of Control)是一种设计思想,DI(Dependence Injection,依赖注入)是实现IOC的一种方法,在没有IOC的程序中,对象的创建与对象件的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。使用控制反转后将对象的创建转移给第三方。

IOC是Spring框架的核心内容,Spring使用了多种方式实现了IOC,如XML配置、注解等。

IoC定义

控制反转是一种通过描述(XML或注解)并通过第三方获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方式是依赖注入(Dependence Injection,DI)。

Spring创建Bean过程

UserService.class —> 调用无参构造方法 —> 对象—>依赖注入(属性赋值)初始化前—>初始化后(AOP,如有需要)—> 代理对象—>Bean

如进行AOP则生成代理对象作为Bean,如不需要AOP则直接将普通对象作为Bean。

  1. 利用该类的构造方法来实例化得到一个对象(但是如何一个类中有多个构造方法,Spring则会进行选择,叫做推断构造方法
  2. 得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属性,把这些属性找出来并由Spring进行赋值(依赖注入
  3. 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调
  4. Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring会调用该方法(初始化前
  5. 紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化
  6. 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后

通过最后一步,我们可以发现,当Spring根据UserService类来创建一个Bean时:

  1. 如果不用进行AOP,那么Bean就是UserService类的构造方法所得到的对象。

  2. 如果需要进行AOP,那么Bean就是UserService的代理类所实例化得到的代理对象,而不是UserService本身所得到的对象。

Bean对象创建出来后:

  1. 如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就是单例池
  2. 如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次getBean时会再次执行上述创建过程,得到一个新的Bean对象。

推断构造方法

Spring在基于某个类生成Bean的过程中,需要利用该类的构造方法来实例化得到一个对象。

  1. 若存在多个构造方法,Spring会尝试使用无参构造方法创建对象。

  2. 若存在多个构造方法的同时没有无参的构造方法 , Spring会直接报错。

  3. 若仅有一个有参的构造方法,Spring会执行该构造方法。

  4. 如果某个构造方法上添加了@Autowired注解,Spring会直接调用该构造方法。

若构造方法的参数为某个对象,则该对象必须是Bean(@Component@Bean
Spring会先从Bean列表中寻找相应类型的Bean,若有多个类型相同的bean会根据beanName从Map(beanName,bean)中获得相应的Bean对象。若有重名的Bean则会在Map中发生覆盖。

例如:

@Component
public UserService(Order order){//Spring根据Order这个类型和order这个名字寻找相应的Bean
  
}

@Component
public UserService(Order order123){//若系统中没有order123这个名字的Bean会直接报错
  
}

确定用哪个构造方法,确定入参的Bean对象,这个过程就叫做推断构造方法

依赖注入

依赖注入可以通过xml文件或注解实现

p命名空间注入可以直接注入属性的值,c命名空间通过构造器注入

@Autowired
private Userservice userService;

依赖注入即给加了@AutoWired对象的属性赋值

赋值过程

  • 依赖:Bean的创建依赖于容器
  • 注入:通过容器注入属性
  1. 获取对象所有字段并遍历,找出加了@Autowired注解的字段
for(Field field : userService.getClass().getFields()){
  if(field.isAnnotationPresent(Autowired.class)){
    field.set(userService,??);
  }
}

通过@PostConstruct进行属性初始化

该注解可以在Spring进行依赖注入时运行某些方法,例如想在依赖注入时从数据库中获取某个值给属性进行初始化,可以写连接数据库的方法并在该方法上添加@PostConstruct注解。该注解执行过程同@AutoWired

通过InitializationBean接口进行属性初始化

也可以通过实现InisializatingBean接口中的afterProperiesSet()方法来达成初始化目的。

执行过程

  1. 判断是否实现了该接口boolean is InisializatingBean = (bean instanceof InisializatingBean);
  2. 如为true则使用((InisializatingBean)bean).afterProperiesSet();进行强制转换后执行该方法

Bean的自动装配

  • 自动装配是Spring满足bean依赖的一种方式
  • Spring会在上下文中自动寻找信息并自动给bean装配属性

Spring中有三种装配方式

  1. xml配置

设置好bean的id之后使用autowired即可

image-20230115174036072

  1. java配置
  2. 隐式自动装配

@Autowired

相当于xml配置中的autowired = "byType "

补充:

**@Nullable**表示该字段可以为空

例:

public class People{
  public void People(@Nullable String name){
    
  }
}

也可以使用@Autowired(required = false)

例:

public class People{
  @AutoWired(required = false)
  private Dog god;
}

如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个@Autowired注解完成,可以使用@Qualifier(value = xxx)配合@Autowired使用指定唯一的Bean。

@Resource

Java的原生注解,功能强大一些。

@Autowired区别:

  • 都用于自动装配,可以加在属性字段上
  • @Autowired通过byType的方式实现
  • @Resource默认通过byName的方式实现,如果名字不存在则通过byType的方式实现

AOP流程

  1. Aspect Oriented Programming(面向切面编程),OOP是面向对象编程,AOP是在OOP基础之上的一种更高级的设计思想。
  2. OOP和AOP之间也存在一些区别,OOP侧重于对象的提取和封装。----封装对象
  3. AOP侧重于方面组件,方面组件可以理解成封装了通用功能的组件,方面组件可以通过配置方式,灵活地切入到某一批目标对象方法上。----封装功能

AOP用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等。
所谓的动态代理,就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

在创建一个Bean的过程中,Spring在最后一步会去判断当前正在创建的这个Bean是不是需要进行AOP,如果需要则会进行动态代理。

判断是否需要进行AOP

  1. 找出所有的切面Bean
  2. 遍历切面中的每个方法,看是否写了@Before@After等注解
  3. 如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
  4. 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP

利用cglib进行AOP的大致流程

  1. 生成代理类UserServiceProxy,代理类继承UserService
  2. 代理类中重写了父类的方法,比如UserService中的test()方法
  3. 代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的对象)
  4. 代理类中的test()方法被执行时的逻辑如下:
    1. 执行切面逻辑(@Before)
    2. 调用target.test()

当我们从Spring容器得到UserService的Bean对象时,拿到的就是UserServiceProxy所生成的对象,也就是代理对象。

UserService代理对象.test()—>执行切面逻辑—>target.test(),注意target对象不是代理对象,而是被代理对象。

Spring事务

当我们在某个方法上加了@Transactional注解后,就表示该方法在调用时会开启Spring事务,而这个方法所在的类所对应的Bean对象会是该类的代理对象

Spring事务的代理对象执行某个方法时的步骤:

  1. 判断当前执行的方法是否存在@Transactional注解
  2. 如果存在,则利用事务管理器(TransactionMananger)新建一个数据库连接
  3. 修改数据库连接的autocommit为false
  4. 执行target.test(),执行程序员所写的业务逻辑代码
  5. 执行之后若没有异常,则提交,否则回滚

Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法被调用时,如果该方法是由代理对象直接调用的,则事务失效

使用注解开发

@Component

  • @Component等价于<bean id = xxx class = "xxx.xxx.xxx">
  • @Value("test") 等价于<property name="xxx" value = "test">

@Component的衍生注解

在web开发中按照SpringMVC三层架构分层

  • dao:@Repository
  • service:@Service
  • controller:@Controller

以上注解功能相同,都是将某个类注册到Spring中装配Bean

代理模式

代理模式为SpringAOP的底层

组合、继承和代理三者的定义:

  1. 组合:在新类中new 另外一个类的对象,以添加该对象的特性。
  2. 继承:从基类继承得到子类,获得基类的特性。
  3. 代理:在代理类中创建某功能的类,调用类的一些方法以获得该类的部分特性。

代理模式的分类:

  • 静态代理
  • 动态代理

静态代理

角色:

  • 抽象角色:一般使用接口或抽象类解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实真实角色的角色,代理后一般会做一些附属操作
  • 客户:访问代理角色的角色

优点:

  • 可以使真实角色的操作更加纯粹,无需关注公共业务
  • 公共业务交给代理角色完成,实现了业务分工
  • 公共业务发生扩展时,方便集中管理

缺点:

  • 一个真实角色对应产生一个代理角色,代码量较大

代码步骤

  1. 接口
public interface Rent {
    public void rent();
}
  1. 真实角色
public class Host implements Rent{
    public void rent(){
        System.out.println("出租");
    }
}
  1. 代理角色
public class Proxy implements Rent{
    private Host host;
    
    public Proxy(){
        
    }
    
    public Proxy(Host host){
        this.host = host;
    }
    
    public void rent(){
        seeHouse();
        host.rent();
        fare();
    }
    public void seeHouse(){
        System.out.println("看房");
    }
    
    public void fare(){
        System.out.println("收费");
    }

  1. 客户访问角色
public class Client {
    public static void main(String[] args){
        //房东要出租房子
        Host host = new Host();
        //代理帮房东出租,且进行额外的附属操作
        Proxy proxy = new Proxy(host);
        //客户无需直面房东
        proxy.rent();
    }
}

动态代理

角色:

  • 抽象角色:一般使用接口或抽象类解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实真实角色的角色,代理后一般会做一些附属操作
  • 客户:访问代理角色的角色

动态代理的代理类是动态生成的

动态代理的分类:

  1. 基于接口的动态代理——JDK动态代理
  2. 基于类的动态代理——cglib
  3. Java字节码实现:JAVAssit

基于接口的动态代理

InvocationHandler

属于java.lang.reflect包下的一个接口InvocationHandler是由代理实例的调用处理程序实现的接口,每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,方法调用将被分派到其调用处理程序的invoke方法

object invoke (Object proxy,Method method,Objectp[] args)

Proxy

Proxy提供了创建动态代理类和实例的静态方法,它本身也是由这些方法创建的所有动态代理类的超类。

代码实现:

  1. 编写代理类(固定)
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到的代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    //处理代理实例并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        return result;
    }
}
  1. 客户端调用
public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //设置要代理的对象
        pih.setTarget(userService);
        //动态生成代理类
        UserService proxy = (UserService)pih.getProxy();
      	//调用
        proxy.query();
    }
}

动态代理优点:

  • 静态代理的所有优点
  • 一个动态代理类代理一个接口
  • 一个动态代理类可以代理多个不同的类,仅要求这些类实现了同一个接口

AOP

AOP(Aspect Oriented Programming)面向切面编程,通过预编译的方式和运行期间的动态代理 实现程序功能统一维护的一种技术。

AOP在Spring中的作用

提供声明式事务;允许用户自定义切面

  • 横切关注点:跨应用程序的多个模块的方法或功能。
  • 切面(Aspect):横切关注点被模块化的特殊对象,即类
  • 通知(Advice):切面必须要完成的工作,即类中的一个方法
  • 目标(Target):被通知的对象
  • 代理(Proxy):向目标对象应用通知之后创建的对象
  • 切入点(PointCut):切面通知执行的“地点”的定义
  • 连接点(JointPoint):与切入点匹配的执行点

image-20230116144252990

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

使用注解实现AOP

  1. 编写注解实现的增强类
@Aspect
public class AnnotationPointcut {
    @Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
    }
    @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}
  1. Spring配置文件中注册bean并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>

aop:aspectj-autoproxy:说明

通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@AspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了 <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

声明式事务

  1. 原子性(atomicity)

一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性

  1. 一致性(consistency)

事务的执行不能破坏数据数据的完整性和一致性,一个事务在执行之前和执行之后,数据都必须处于一致性状态。

  1. 隔离性(isolation)

事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。

Spring中的事务管理

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
  • 将事务管理作为横切关注点,通过AOP方法模块化。Spring中通过SpringAOP框架支持声明式事务管理。

Spring管理事务需要的头文件约束:

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

JDBC事务

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        
  <property name="dataSource" ref="dataSource" /> 
</bean>

配置好事务管理器后我们需要去配置事务的通知

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">   
  <tx:attributes>        
    <!--配置哪些方法使用什么样的事务,配置事务的传播特性-->        
    <tx:method name="add" propagation="REQUIRED"/>        
    <tx:method name="delete" propagation="REQUIRED"/>        
    <tx:method name="update" propagation="REQUIRED"/>        
    <tx:method name="search*" propagation="REQUIRED"/>        
    <tx:method name="get" read-only="true"/>        
    <tx:method name="*" propagation="REQUIRED"/>    
  </tx:attributes>
</tx:advice>

spring事务传播特性:

在声明式事务中需要配置一个切面,其中需要propagation,表示如何对这些方法使用事务。

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring 默认的事务传播行为是REQUIRED,它适合于绝大多数的情况

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!