spring框架详解(持续更新)

本文详细介绍了Spring框架,包括Ioc控制反转、Bean的作用域、依赖注入、AOP切面编程、事务管理以及事件监听。讲解了各种注入方式、事务属性、隔离级别和回滚规则,还探讨了Spring的事务实现、工具类和注解的使用。文章旨在帮助读者深入理解Spring的核心概念和实际应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

spring概述

spring容器启动

Ioc控制反转

  Bean创建

  Bean作用域

  Bean生命周期

  Bean属性

依赖注入

  构造函数方法注入

  Set方法注入

  注解方法注入

AOP切面

  示例(无参)

  示例(有参)

业务相关

spring事务

  spring事务管理器(PlatformTransactionManager)

  spring事务属性(TransactionDefinition)

  spring事务实现

spring事件监听

spring工具类大全

spring注解大全

spring概述

spring是一个轻量级的容器,用于管理业务相关对象的。其两大内核:Ioc(控制反转)、aop(面向切面编程)。容器功能包括依赖注入(Dependency,简称DI)和依赖查找(Dependency Lookup)。

耦合与解耦

耦合是程序间的依赖关系包括:类间的依赖、方法间的依赖

解耦是降低程序间的依赖关系,实际开发中应该做到,编译器不依赖,运行时才依赖。
解耦的思路有两种:
1.使用反射来创建对象,而避免使用new关键字
2.通过读取配置文件来获取要创建的对象全限定类名

spring容器启动

在web项目中,web.xml配置spring容器的方式有三种:

• ContextLoaderListener
• ContextLoadServlet
• ContextLoaderPlugin

  1. ContextLoaderListener(监听方式)

    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>/WEB-INF/classes/applicationContext-*.xml</param-value>
    </context-param>
    <listener> 
    	<listener-class>
    		org.springframework.web.context.ContextLoaderListener
    	</listener-class>
    </listener>	
    

    [注]还可以通过<import resource="classpath:/spring/spring-xxx.xml"/>的方式把其他的配置引进来

  2. ContextLoadServlet(servlet方式)

    在spring3.0以后不再支持,建议使用监听方式

    <servlet> 
    	<servlet-name>context</servlet-name> 
    	<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
    	<load-on-startup>1</load-on-startup>
    </servlet> 
    
  3. ContextLoaderPlugin(plugin配置方式)

    适用于spring与strust等整合,在struts-config.xml里面配置一个ContextLoaderPlugIn,用于spring的初始化工作

    <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">  
    	<set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml" />  
    </plug-in> 
    

Ioc控制反转

  1. 定义

    Ioc(控制反转)将对象的创建过程交给容器,让容器管理对象的生命周期,如创建,初始化,销毁等。
    目的是削减计算机程序的耦合。

  2. 容器实例创建

    Ioc核心容器接口:ApplicationContext

    a.ClassPathXmlApplicationContext 它可以加载类路径下的配置文件
    b.FileSystemXmlApplicationContext 它可以加载磁盘任意路径下的配置文件(必须有访问权限)
    c.AnnotationConfigApplicationContext 它是用于读取注解创建容器的

    ApplicationContext ac = new ClassPathXmlApplicationContext("Spring/bean.xml");
    

Bean创建

  1. 默认构造器创建

    使用bean标签。
    配置id 和class 属性后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建

    <bean id = "userService" class="com.ferao.Service.UserService"></bean>
    
  2. 普通工厂中的方法

    普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring 容器)

    <bean id= "instanceFactory" class="com.ferao.InstanceFactory"></bean>
    
    <bean id= "userService"  factory-bean="instanceFactory" factory-method="getUserService">
    </bean>
    
  3. 工厂中的静态方法

    创建对象(使用某个类中的静态方法创建对象,并存入Spring容器)

    <bean id= "userService" factory-bean="com.ferao.Factory.InstanceFactory" factory-method="getUserService">
    </bean>
    

Bean作用域

  1. 定义

    当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域(scope)。Spring支持如下5种作用域

  2. 作用域

    作用域 内容
    singleton(单例模式) 容器中,使用singleton定义的Bean将只有一个实例
    protorype(原型模式) 通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
    request(请求模式) 每次HTTP请求,使用request定义的Bean都将产生一个新实例
    session(会话模式) 每次HTTP Session,使用session定义的Bean都将产生一个新实例
    global-session(全局会话模式) 全局的HTTP Session,每个使用session定义的Bean都将产生一个新实例
  3. 使用推荐

    如果不指定Bean的作用域,Spring默认使用singleton作用域。
    Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。
    因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

  4. 示例

    <!-- 默认的作用域:singleton -->
    <bean id="p1" class="com.abc.Person" /> 
    <!-- 指定的作用域:prototype -->
    <bean id="p2" class="com.abc.Person" scope="prototype" />
    
    //加载类路径下的beans.xml文件以初始化Spring容器
    ApplicationContext context = new ClassPathXmlApplicationContext();
    //分两次分别取同一个Bean,比较二者是否是同一个对象
    System.out.println(context.getBean("p1") == context.getBean("p1"));
    System.out.println(context.getBean("p2") == context.getBean("p2"));
    

    控制台结果:
    true
    false

Bean生命周期

  1. 单例对象

    单例对象的生命周期和容器相同
    出生:容器创建
    活着:容器存在,对象存活
    死亡:容器销毁,对象消亡

  2. 多例对象

    出生:使用对象时spring容器创建
    活着:使用过程中存在
    死亡:长时间不用,由java的垃圾回收机制回收

Bean属性

属性 内容
id bean的唯一标识符
class 类的全限定名(包名+类名)
name 别名(alias),使用getBean(“name”),支持设置多个别名,之间用英文逗号分割
abstract bean是否为抽象类,默认abstract=“false”,如果设为true,将不能被实例化
autowire-candidate 默认true,如果为false,那么该bean不能作为其他bean自动装配的候选者
init-method 创建一个bean之后调用该方法,初始化方法必须是一个无参方法
factory-bean 指定创建bean的工厂类对象,class属性将失效
factory-method 指定创建bean的工厂方法
scope 作用范围,它包括singleton、prototype、request、session
destroy-method 销毁bean之前可以执行指定的方法。注意:必须满足scope=“singleton”,并且destroy方法参数个数不能超过1,并且参数类型只能为boolean
depends-on 一个bean实例化的过程需要依赖于另一个bean的初始化

依赖注入

  1. 定义

    由于对象的创建过程交给容器,在当前类用到其他类的对象时,我们只需要在配置文件中说明需要引入相关类的信息即可,依赖关系的维护称之为依赖注入。

  2. 注入数据类型

    • 基本类型和String
    • 其他bean类型(在配置文件中或者注解配置过的bean)
    • 复杂类型/集合类型

  3. 注入数据方式

    • 构造函数方法
    • set方法
    • 注解方法

构造函数方法注入

  1. 描述

    使用constructor-arg标签放置在bean标签的内部即可

  2. 标签属性

    • type:指定要注入的数据的类型
    • index:给构造函数中指定索引位置的参数赋值
    • name:给构造函数中指定名称的参数赋值
    • value:指定数据

    在这里插入图片描述

Set方法注入

  1. 描述

    使用property标签并放置在bean标签的内部

  2. 标签属性

    • name: 指定注入时所调用的set方法名称
    • value: 提供数据
    • ref: 指定其他的bean 类型的值(容器中的bean对象)

    在这里插入图片描述

注解方法注入

  1. 描述

    以注解形式注入,降低程序间的耦合。是配置文件的另一种形式,有四方面需要注解化:

    • 创建容器对象
    • 注入数据
    • 作用范围
    • 生命周期

  2. 注解化

    1. 创建容器对象

      @Component(替代bean标签),放置于类上即可将当前类对象存入spring容器中。

      @Component内有value属性,用于指定bean的id,默认当前类名,且首字母小写

      Controller、Service、Repository注解的作用和属性与Component是一模一样的,存在是为我们提供明确的三层使用的注解,使我们的三层对象更加清晰

      在这里插入图片描述

    2. 注入数据

      @Autowired(替代property标签),放置于变量、方法上,可自动按照类型注入,只要容器中有唯一的bean对象类型和要注入的变量类型匹配,就可以注入成功

      在这里插入图片描述
      @Qualifier:在按照类中注入的基础上再按照名称注入。它是给类成员注入时使用,不能单独使用,但是在给方法参数注入时可以.用于指定注入的,该注解有value属性,用于指定注入的bean的id

      @Resource:直接按照bean的id注入,它可以单独使用,该注解有name属性,用于指定bean的id

      @Value:用于注入基本类型和String类型的数据,该注解有value属性,用于指定数据的值,它可以使用spring中spEL(也就是spring的el表达式)

    3. 改变作用范围

      它们的作用集合在<bean>标签中使用scope属性实现的功能是一样的

    4. 和生命周期相关

      它们的作用就和在<bean>标签中使用init-method和destroy-method的作用是一样的

      1. 注解注入

        		重要细节
        
        		-Autowired注解与Resource注解的区别
        
        		相同点
        
        			Resource的作用相当于Autowired,均可在字段或属性的setter方法上
        
        		不同点
        
        			1. 提供方:Autowired是由org.springframework.beans.factory.annotation.Autowired提供,换句话说就是由spring提供
        			  Resource是由javax.annotation.Resource提供,即j2ee提供,且jdk1.6及以上
        			
        			2. 注入方式:Autowired只按照byType注入;
        			   Resource默认按byName自动注入,也提供按照byType注入
        
        			3. 属性:Autowired:按照类型装配依赖对象,默认情况下它要求依赖
        			   对象必须存在,如果允许Null值,可以设置它requested属性为
        			   false。如果开发者想要使用按名称装配,可以结合Qualifier注
        			   解一起使用。
        			   Resource:有两个重要的属性:name和type.name属性指定
        			   byName,如果没有指定Name属性,当注解标注在字段上,即默
        			   认取字段的名称作为bean名称寻找依赖对象。当注解标注在属性
        			   的setter方法上,即默认取属性名作为bean名称寻找依赖对象,
        			   需要注意的是,Resource如果没有指定name属性,并且按照默
        			   认的名称找不到依赖对象时,Resource注解会回退到按类型装
        			   配。但一旦指定了Name属性,就只能按照名称装配了
        
        		========以上三个注入都只能注入其他bean类型的数据,而基本类型
        		和String类型无法使用上述注解实现,以外,集合类型的注入只能通过
        		xml来实现
        
        改变作用范围示例
        
        			scope
        		
        				作用:用于指定bean的作用范围
        
        				属性:value:指定范围的取值,常用取值:singletion prototype
        

        注入后的使用

        问题点
        
        	项目中遇到一个问题:项目启动完成前,在A类中注入B类,并调用B类的某个方法。
        
        	那么调用B类的这个方法写在哪里呢,我选择写到构造器里,
        
        	但是构造器先于Spring注入执行,那么执行构造器时,注入B类肯定为null,
        
        	于是选择了构造器注入,解决问题
        
        执行顺序
        
        	静态变量或静态语句块–>实例变量或初始化语句块–>构造方法–>Spring注入
        
        常见的三种使用注入方式	
        
        	注解注入
        			
        		@Controller
        		public class FooController {
                 
                 
        		  @Autowired
        		  private FooService fooService;
        		   
        		  //简单的使用例子,下同
        		  public List<Foo> listFoo() {
                 
                 
        		      return fooService.list();
        		  }
        		}
        

        这种注入方式应该是笔者目前为止开发中见到的最常见的注入方式.原因很简单:
        1.注入方式非常简单:加入要注入的字段,附上@Autowired,即可完成。
        2.使得整体代码简洁明了,看起来美观大方。

        	构造器注入
        
        		@Controller
        		public class FooController {
                 
                 
        		   
        		  private final FooService fooService;
        		   
        		  @Autowired
        		  public FooController(FooService fooService) {
                 
                 
        		      this.fooService = fooService;
        		  }
        		   
        		  //使用方式上同,略
        		}
        

        在Spring4.x版本中推荐的注入方式就是这种,相较于上面的注解注入方式而言,就显
        得有点难看,特别是当注入的依赖很多(5个以上)的时候,就会明显的发现代码显得很臃肿。对于从注解
        注入转过来+有强迫症的开发人员来说,简直可以说是无法忍受。

        循序渐进示例

        	写了一个类,分别有两个构造器,一个是注入一个Bean的构造器,一个是注入两个Bean的构造器			
        
        		public class ConstructorAutowiredTest {
                 
                 
        		
        		    private User user;
        		    private Role role;
        		
        		    public ConstructorAutowiredTest() {
                 
                 
        		    }
        		
        		    public ConstructorAutowiredTest(User user) {
                 
                 
        		        this.user = user;
        		    }
        		
        		    public ConstructorAutowiredTest(User user, Role role) {
                 
                 
        		        this.user = user;
        		        this.role = role;
        		    }
        		
        		    public void test(){
                 
                 
        		        System.out.println("user: "+user);
        		        System.out.println("role: "+role);
        		    }
        		}	
        
        	Spring的配置文件context.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-2.0.xsd
        		       http://www.springframework.org/schema/context
        		        http://www.springframework.org/schema/context/spring-context-4.0.xsd ">
        		
        		    <!-- 使Spring关注Annotation -->
        		    <context:annotation-config/>
        		
        		    <bean class="com.mytest.demo.model.Role" id="role">
        		        <property name="name" value="testRole"/>
        		        <property name="id" value="2"/>
        		    </bean>
        		
        		    <bean class="com.mytest.demo.model.User" id="user">
        		        <property name="id" value="1"/>
        		        <property name="name" value="testUser"/>
        		    </bean>
        		
        		    <bean class="com.mytest.demo.autowired.ConstructorAutowiredTest" id="test"/>
        		
        		</beans>	
        
        	那么,该类三个构造器,Spring会使用哪个构造器初始化
        	ConstructorAutowiredTest这个Bean呢?写个测试便知:
        
        		public class TestBeanAutowiredConstructor {
                 
                 
        		
        		    public static void main(String[] args) {
                 
                 
        		        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        		        context.setConfigLocation("context.xml");
        		        context.refresh();
        		
        		        ConstructorAutowiredTest test = (ConstructorAutowiredTest) context.getBean("test");
        		        test.test();
        		    }
        		}	
        

        在这里插入图片描述

        	从这里我们可以看出来,此时三个构造器中Spring使用了无参构造器。
        	我们换一个方式,将无参构造器注释掉,看看Spring到底会使用哪个构造器呢?
        	同样执行test方法测试,控制台打印:
        

        在这里插入图片描述

        	此时控制台报错,大致意思是Bean的实例化失败了,没有无参的构造器方法调用。
        	此时有个疑问,明明构造器中的参数都是Bean,为什么不能初始化,
        	一定要使用无参的构造器呢?是否是因为有两个构造器的原因?
        	此时我们再注释掉任意一个构造函数,
        	使测试类只有一个带参构造函数:
        
        	//    public ConstructorAutowiredTest() {
                 
                 
        	//    }
        	
        	    public ConstructorAutowiredTest(User user) {
                 
                 
        	        this.user = user;
        	    }
        	
        	//    public ConstructorAutowiredTest(User user, Role role) {
                 
                 
        	//        this.user = user;
        	//        this.role = role;
        	//    }
        
        	再次运行测试类,控制台打印:	
        

        在这里插入图片描述

        	如果是注释掉第二个构造函数,则结果是两个对象都有。
        	从这里我们可以看出,如果只有一个构造器,且参数为IOC容器中的Bean,
        	将会执行自动注入。这里又有一个疑问,这也太不科学了吧,
        	强制用户一定只能写一个构造器?
        	这时我们猜想@Autowired注解是否能解决这种问题?
        	来试试吧。我们将构造器全部解除注释,将第三个构造器打上@Autowired注解:
        
        		public ConstructorAutowiredTest() {
                 
                 
        		}
        		
        		public ConstructorAutowiredTest(User user) {
                 
                 
        		    this.user = user;
        		}
        		
        		@Autowired
        		public ConstructorAutowiredTest(User user, Role role) {
                 
                 
        		    this.user = user;
        		    this.role = role;
        		}
        

        在这里插入图片描述

        	不出所料,@Autowired注解可以解决这种问题,
        	此时Spring将使用有注解的构造函数进行Bean的初始化。
        	那么,如果有两个@Autowired注解呢?结果肯定是报错,
        	因为@Autowired的默认属性required是为true的,
        	也就是说两个required=true的构造器,Spring不知道使用哪一个。但如果是这样写的话:
        
        		public ConstructorAutowiredTest() {
                 
                 
        		}
        		
        		@Autowired(required = false)
        		public ConstructorAutowiredTest(User user) {
                 
                 
        		    this.user = user;
        		}
        		
        		@Autowired(required = false)
        		public ConstructorAutowiredTest(User user, Role role) {
                 
                 
        		    this.user = user;
        		    this.role = role;
        		}
        

        在这里插入图片描述

        	使用参数最多的那一个构造器来初始化Bean。
        	又如果两个有参构造器顺序调换又是怎样的呢?一个required为false一个为true,
        	结果又是怎样的呢?这里直接给出答案,顺序调换依然使用多参数构造器,
        	并且required只要有一个true就会报错。有兴趣的读者可以自己试试,
        	下面将深入源码分析构造器注入的过程,相信上述所有疑问都能得到解答。
        
        	setter注入
        
        		@Controller
        		public class FooController {
                 
                 
        		   
        		  private FooService fooService;
        		   
        		  //使用方式上同,略
        		  @Autowired
        		  public void setFooService(FooService fooService) {
                 
                 
        		      this.fooService = fooService;
        		  }
        		}
        

        在Spring3.x刚推出的时候,推荐使用注入的就是这种,笔者现在也基本没看到过这种
        注解方式,写起来麻烦,当初推荐Spring自然也有他的道理,这里我们引用一下Spring当时的原话:
        构造器注入参数太多了,显得很笨重,另外setter的方式能够让类在之后重新配置或者重新注入。

        构造器注入的好处
        
        	spring文档
        
        		构造器注入的方式,能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,
        		构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。
        
        	解释
        	
        		依赖不可变:其实说的就是final关键字,这里不再多解释了。
        	
        		依赖不为空(省去了我们对其检查):当要实例化FooController的时候,
        		由于自己实现了有参数的构造函数,所以不会调用默认构造函数,
        		那么就需要Spring容器传入所需要的参数,所以就两种情况:
        		1、有该类型的参数->传入,OK2:无该类型的参数->报错。所以保证不会为空
        
        		完全初始化的状态:这个可以跟上面的依赖不为空结合起来,
        		向构造器传参之前,要确保注入的内容不为空,
        		那么肯定要调用依赖组件的构造方法完成实例化。
        		而在Java类加载实例化的过程中,构造方法是最后一步
        		(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。)
        		所以返回来的都是初始化之后的状态。
        
        
        	总结
        
        	    保证依赖不可变(final关键字)
        	    保证依赖不为空(省去了我们对其检查)
        	    保证返回客户端(调用)的代码的时候是完全初始化的状态
        	    避免了循环依赖
        	    提升了代码的可复用性
        
        构造器注入的缺点	
        
        		当注入的依赖很多(5个以上)的时候,就会明显的发现代码显得很臃肿。	
        
        set注入的好处		
        
        		setter的方式能够让类在之后重新配置或者重新注入
        
        set注入的缺点	
        
        		写起来麻烦
        
        field注入的缺点	
        
        	对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。
        	而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在。
        	还值得一提另外一点是:使用field注入可能会导致循环依赖,即A里面注入BB里面又注入A
        
        	public class A {
                 
                 
        	    @Autowired
        	    private B b;
        	}
        	 
        	public class B {
                 
                 
        	    @Autowired
        	    private A a;
        	}	
        
        	如果使用构造器注入,在spring项目启动的时候,就会抛出:
        	BeanCurrentlyInCreationExceptionRequested bean is currently in creation: Is there an unresolvable circular reference?
        	从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错,
        	在使用那个bean的时候才会报错。
        
        

AOP切面

  1. 定义

    面向切面编程,是面向对象编程(OOP)的一种补充。当方法执行时,AOP可以劫持方法,在方法执行前或后添加额外的逻辑。AOP 框架有很多种,对应的实现方式也不同,Spring 中的 AOP 是通过动态代理实现的。

    在Spring AOP中,有 4 种类型通知(advices)的支持:
    • 前置通知
    • 后置通知
    • 异常通知
    • 环绕通知

  2. 通知类型

    类型 连接点 实现接口
    前置通知 该方法执行前运行 org.springframework.aop.MethodBeforeAdvice
    后置通知 通知方法会在目标方法返回或异常后调用 org.springframework.aop.AfterReturningAdvice
    引入通知 在不改变原有方法的基础上却可以增加新的方法 org.springframework.aop.IntroductionInterceptor
    异常通知 运行方法抛出异常后 org.springframework.aop.ThrowsAdvice
    环绕通知 通知方法会将目标方法封装起来, 环绕方法执行运行,结合以上这三个通知 org.aop.alliance.intercept.MethodInterceptor
  3. 操作术语

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值