【Spring复习】Spring框架基础概要----第一篇

本文篇幅较长,可以点击目录索引定位查看:

一、Spring的七大功能模块

二、Spring中常用功能详解

1、IOC:将对象的创建交由Spring容器来管理

2、DI:依赖注入,在创建时给容器中的对象注入属性值

3、AOP:面向切面编程,底层使用动态代理增强方法,提高代码复用性

4、声明式事务管理

5、全注解开发配置以及Junit的整合

6、xml配置文件约束和jar包依赖

参考及推荐


一、Spring的七大功能模块

【Spring复习】Spring框架基础概要----第一篇_第1张图片 图1  Spring框架模块图

 

【Spring复习】Spring框架基础概要----第一篇_第2张图片 图2  Spring框架模块图

 

Spring是基于Java平台的,如上面的两张图片所示,Spring为应用程序提供了全面的基础设施。也正是因为Spring专注于基础设施,这才使得程序猿们能够更好的致力于应用开发而不用过多的去关心底层框架的实现。总的来说,Spring是分层的Java  SE/EE应用一站式轻量级开源框架,以IOC(控制反转)和AOP(面向切面编程)为内核,有效的对程序进行了解耦,同时也简化了开发和维护。

下面先对Spring框架的七大功能模块进行讲解:

1、核心容器(Spring  Core):

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

2、Spring上下文(Spring  Context):

Spring上下文是一个配置文件,它继承BeanFactory(或者说Spring核心)类,向Spring框架提供上下文信息。Spring上下文包括企业服务,如:JDNI、EJB、电子邮件、国际化、校验和调度功能。

3、面向切面编程(Spring  AOP):

Spring集成了所有的AOP功能。通过事务管理可以使任意Spring管理的对象AOP化。Spring提供了用标准Java语言编写的AOP框架,它的大部分内容都是基于AOP联盟的API开发的。它的应用程序抛开了EJB的复杂性,但拥有传统EJB的关键功能。Spring  AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring  AOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中。

4、Spring  Dao模块

DAO(Data  Access  Object)的思想是:将业务逻辑代码与数据库交互代码分离,降低两者的耦合。通过DAO模式可以使程序结构变得更为清晰,代码更为简洁。DAO模块提供了JDBC的抽象层,简化了数据库厂商提供的异常错误(不再从SQLException继承大量代码),大幅度减少代码的编写,并且提供了声明事务和编程式事务的支持。

5、Spring  ORM映射模块

Spring  ORM模块提供了对现有ORM框架的支持,各种流行的ORM框架已经做的非常成熟,并且拥有大规模市场,Spring没有必要开发新的ORM工具,它对Hibernate提供了完美的整合功能,同时也支持其他ORM工具。注意这里Spring是提供各类的接口(Support),目前比较流行的下层数据库封闭映射框架,如:MyBatis和Hibernate等。

6、Spring  Web模块

此模块建立在Spring  Context基础上,它提供了Servlet监听器的Context和Web应用的上下文。对现有的JSF、Struts等,提供了集成。Struts是建立在MVC这种公认好的模式之上的,Struts在M、V和C上都有所涉及,但它主要是提供了一个好的控制器和一套定制的标签库上,也就是它的着力点在C和V上,因此,它天生就有MVC所带来的一系列优点,比如:层次结构分明,高可用性、增加了程序的健壮性和可伸缩性,便于开发与设计分工,提供集中统一的权限控制、校验、国际化和日志等等。

7、Spring  MVC框架(Spring  WebMVC)

Spring  WebMVC模块建立在Spring核心功能之上,这使它能拥有Spring框架的所有特性,能够适应多种多视图、模板技术、国际化和验证服务,实现控制逻辑和业务逻辑的清晰分离。说说MVC在JSP的作用,这里引入了“控制器”这个概念,控制器一般由Servlet来担任,客户端的请求不再直接送给一个处理业务逻辑的JSP页面,而是送给这个控制器,再由控制器根据具体的请求调用不同的事务逻辑,并将处理结果返回到合适的页面。因此,这个Servlet控制器为应用程序提供了一个进行前-后端处理的中枢。一方面为输入数据的验证、身份认证、日志及实现国际化编程提供了一个合适的切入点;另一方面也提供了将业务逻辑从JSP文件剥离的可能。业务逻辑从JSP页面分离后,JSP文件蜕变成一个单纯完成显示任务的东西,这就是常说的View。而独立出来的事务逻辑变成人们常说的Model,再加上控制器Control本身,就构成了MVC模式。实践证明,MVC模式为大型程序的开发及维护提供了巨大的便利。 

结合上面的七大功能模块,对Spring框架的优点大致总结如下:

(1)Spring框架能有效的组织中间层对象。Spring框架能够有效地将现有的框架例如Struts2和Hibernate框架组织起来;

(2)Spring框架实现了真正意义上的面向接口编程,可实现组件之间的高度解耦,而面向接口编程是一种良好的编程习惯;

(3)Spring所秉承的设计思想就是让使用Spring创建的那些应用都尽可能少的依赖于它的API。在Spring应用中大多数业务对象都不依赖于Spring

(4)使用Spring构建的应用程序易于进行单元测试;

(5)Spring提高了代码的可重用性,它尽可能避免在程序中使用硬编码。Spring可以将应用程序中的某些代码抽象出来,然后在其他应用程序中使用这些代码;

(6)Spring为数据存取提供了一个一致的框架,简化了底层数据库的访问方式。


二、Spring中常用功能详解

1、IOC:将对象的创建交由Spring容器来管理

解耦:降低程序间的依赖关系,实际开发中应该做到编译期不依赖,运行时才依赖。

解耦思路:(1)使用反射来创建对象,避免使用new;

                    (2)通过读取配置文件,来获取要创建的对象的全限定类名。

单例和多例:

(1)单例对象:

创建:容器建立时立即加载;

销毁:随着容器中的销毁而销毁。

(2)多例对象:

创建:需要使用时再加载,延时加载;

销毁:销毁不由Spring负责,交给JVM垃圾回收机制,长时间不使用自动销毁。

IOC有两种方式将对象加入容器中去:

【1】通过applicationContext.xml文件进行配置,将对象加入容器中

(1) 默认无参构造创建的对象,也可设置为有参构造
	对象在容器中的唯一标识
	class="org.apache.commons.dbutils.QueryRunner" 	--->对象类的全限定类名
	scope="prototype"/>	--->对象类型,默认为 singleton 单例模式 ; 设置 prototype 为多例模式

(2) 通过工厂类的静态方法获取目标对象
	对象在容器中的唯一标识
	class="com.zju.factory.StaticFactory"	--->工厂类的全限定类名
	factory-method="createAccountService"/>	--->工厂类返回目标对象的静态方法
		
(3) 通过工厂的非静态方法获取目标对象
	对象在容器中的唯一标识
	factory-bean="instancFactory"	--->工厂类对象在容器中的id , 前提是工厂类对象已经加载进容器
	factory-method="createAccountService">	--->工厂类返回目标对象的非静态方法

【2】通过在类上标记注解,将对象加入容器中

四种注解方式:    @Component("id")	        括号内为对象在容器中的唯一标识

		@Controller("id")	特用于标识	表现层
		@Service("id")		特用于标识 	业务层
		@Repository("id")	特用于标识	持久层

当使用注解交给容器指定类对象时,需要额外声明:

(1) xml配置 : 
	需要在配置文件xml中加入
	
	
		// 不要对Controller进行扫描,留给springmvc容器扫描
		
	

(2) 配置类配置 : 
	需要告知spring在创建容器时要扫描的包
	@ComponentScan("com.zju")

2、DI:依赖注入,在创建时给容器中的对象注入属性值

【1】通过applicationContext.xml配置依赖注入

(1)	构造函数注入	--->	在创建对象时通过有参构造函数注入对应数据
	
	//	属性类型为对象时 , 用ref关键字代替value关键字给属性注入该对象在容器中的id
	
		
		
		
		
		
(2) set方法注入		--->	在通过无参构造创建对象后,随之通过set方法给属性赋值(*/常用)
	
	//	通过xml配置的set方法注入 , 类中必须有对应属性的setXxx方法	 
	
		
		
		
	
		
	//	配置一个日期对象
	
		1900+108,2008年
		0-11月,1月
		第27天
	
		
	//	复杂注入	--->	单列集合List/数组/set等 , 双列集合map/properties标签可以串用
	
		
			
				myList1
				myList2
				myList3
			
		

		
			
				
				
				
			
		

		
			
				myPro1
				myPro2
				myPro3
			
		

		
			
				mySet1
				mySet2
				mySet3
			
		

		
			
				myStrs1
				myStrs2
				myStrs3
			
		
	

【2】通过注解配置依赖注入

(1)	构造函数注入	--->	在构造方法上标记注解@Autowired , 使Spring容器自动赋值 , 如:
	@Autowired
	public AccountServiceImpl(IAccountDao accountDao){
		this.accountDao=accountDao;
	}
		
(2)	set方法注入	--->	在变量上标记注解@Autowired,Spring底层自动创建set方法注入属性值,如:
	@Autowired
	private IAccountDao accountDao;

两种注入方法的区别:

1.	@Autowired	--->	Spring提供的注解
	*默认根据类型在容器中查找对象,没有对象会报错,找到超过一个对象会根据变量名查找id,找不到会报错
		
		
2.	@Autowired + @Qualifier	--->	两个注解配合使用,相当于	@Resource(jdk自带注解)
	(1)	@Autowired 
		@Qualifier("accountDao")
		*直接指定了注入的对象id , 忽略变量名 , 直接根据id查找 , 找不到就报错 
			
	(2)	@Autowired 
		@Qualifier()
		*两个注解配合使用必须指定注入对象的id , 否则报错
			
	(3)	@Autowired(required=false)
		@Qualifier("accountDao222")
		*给Autowired设置required为false值后 , 根据id注入对象找不到不会报错 , 会注入一个 null
			
	(4)	@Qualifier("id")的单独使用
		*标记在方法的参数列表中的参数前时可以单独使用 , 默认按变量名查找对象(可以不加注解), 找不到就报错
	
	
3.	@Resource	--->	jdk自带注解
	(1)	@Resource不指定id
		*首先根据变量名查找对象id , 找不到再根据对象类型找 , 找不到或找到超过一个对象会报错
			
	(2)	@Resource("accountDao")指定id
		*直接根据id查找对象 , 找不到直接报错 , 不进行类型查找 , 并且忽略变量名

3、AOP:面向切面编程,底层使用动态代理增强方法,提高代码复用性

【1】两种动态代理,Spring底层根据被代理对象自动选择代理方式

方式一:JDK动态代理  Proxy

public IAccountService getAccountService() {
					return (IAccountService) Proxy.newProxyInstance(// 参数列表:
					accountService.getClass().getClassLoader(),	// 被代理对象的类加载器
					accountService.getClass().getInterfaces(),	// 被代理类实现的接口
					new InvocationHandler() {	// 代理过程的匿名内部类
					
		@Override			// 代理对象的引用,当前执行的方法,当前执行方法所需的参数
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			// 执行操作
			rtValue = method.invoke(accountService, args);
				   
		}
	});
}

特点和原理:
        这种代理方式本质上是创造了一个代理类 , 该类实现了被代理类实现的所有接口 , 与被代理类是兄弟关系含有被代理类所有"由接口定义"的方法 , 当需要被代理对象执行接口方法时 , 代理对象就可以做出反应 , 对该方法进行增强。

注意点和局限性:
        此代理只能代理实现了接口的类对象 , 更具体一点 , 是因为该代理对象只能对接口中定义的方法做出反应,如果是被代理对象中自定义(不是实现自接口)的方法被调用 , 代理对象也无法做出响应。

方式二:CGLB动态代理   Enhancer

public IAccountService getAccountService() {
					return (IAccountService)Enhancer.create(// 参数列表
					accountService.getClass(),	// 被代理对象的字节码对象
					new MethodInterceptor() {
      
	@Override
	public Object intercept(Object proxy,	// 代理对象的引用
							Method method,	// 当前执行方法
							Object[] args,	// 当前执行方法所需的参数
							MethodProxy methodProxy) // 当前执行方法的代理对象
							throws Throwable {
			// 执行操作
			rtValue = method.invoke(accountService, args);
		}
	});
}

特点和原理:
        这种代理方式本质上是创造了被代理类的子类对象作为代理类对象 , 代理对象继承了被代理对象的所有方法可以对被代理类所有方法的调用做出反应 , 并对其进行增强。

注意点和局限性:
        因为是通过继承的方式创建代理对象 , 所以这种代理方式不能适用于被 final 关键字修饰的类 , 因为final 修饰的类无法被继承。

【2】基于xml的AOP配置


	
		
		
		
	
						
		
		
			
			
			
			
			
			
			
			
			
			
			
		
	

【3】基于注解的AOP配置

准备:	
	在xml配置文件中开启 spring 对注解 AOP 的支持
		
		
	// 在通知类上标记注解 , 将类对象加入容器并注明是通知类
	@Component("txManager")
	@Aspect 
		
	// 声明切入点
	@Pointcut("execution(* com.zju.service.impl.*.*(..))")
	private void service() {
	}
	
	@Before("service()")	// 标记前置通知
	@AfterReturning("service()")	// 标记后置通知
	@AfterThrowing("service()")		// 标记异常通知
	@After("service()")		// 标记最终通知
	
	@Around("service()")	// 标记环绕通知
	public Object transfer(ProceedingJoinPoint pjp) {
	// ProceedingJoinPoint为设置切入点的接口,依赖于org.aspectj.aspectjweaver
			/*前置通知*/
			
			// 设置切入点位置 , 并执行原方法
			rtValue = pjp.proceed(pjp.getArgs());
			
			/*后置通知*/
		
	}

注意点:
        在Spring 的注解AOP配置中 , 有一个未处理的异常:
        @After()标记的最终通知会在后置通知和异常通知之前执行 。因此,在使用注解配置AOP时,尽量使用 环绕通知 和 后置通知。

4、声明式事务管理

service层控制事务的准备::
        创建一个ConnectionUtils工具类 , 通过ThreadLocal将线程和数据库连接对象绑定,并且在dao层操作数据库时 , 从ConnectionUtils获取connection连接对象 , 保证service层调用多个dao层方法时 , 使用的是同一个connection连接对象 ,从而达到在service层控制事务的目的。

/**
	 * 管理事务的工具类
	 */
	@Component("txManager")
	@Aspect
	public class TransactionManager {
		///声明切入点: service.impl包下所有类的所有方法
		@Pointcut("execution(* com.zju.service.impl.*.*(..))")
		private void service() {
		}
		/**
		 * 环绕通知
		 */
		@Around("service()")//对切入点方法进行环绕通知增强
		public Object transfer(ProceedingJoinPoint pjp) {
			///1.定义返回值
			Object rtValue = null;
			try {
				///2.调用开启事务的方法
				beginTransaction();
	
				//3.执行service层的操作
				rtValue = pjp.proceed(pjp.getArgs());
	
				///4.调用提交事务是方法
				commit();
				///5.返回结果
				return rtValue;
			} catch (Throwable t) {//只能通过Throwable捕捉异常,否则抓取不到
				///6.调用回滚事务的方法
				rollback();
				throw new RuntimeException(t);
			} finally {
				///7.调用释放连接的方法
				release();
			}
		}
	}

声明式事务管理也是基于AOP , 由底层的动态代理实现 , Spring将事务中的重复动作抽取封装 , 我们使用时只需要关注service层的业务本身 , 将其作为切入点配置给事务管理器即可。

【1】applicationContext.xml配置的声明式事务管理

(1)	配置事务管理器
		
	作用原理:	关键点--->连接-->connection对象
		 配置事务管理器需要注入一个数据源 , 该数据源就是持久层操作数据库所使用的数据源 ,因此 , 在service层调用多个dao层方法时 , 可以通过事务
	管理器将多个dao层方法使用的连接控制为同一个 , 以此实现事务在service层的控制
			 			  
	
		//注入数据源
	
		PS:根据持久层框架的不同,所选用的事务管理器实现类也有所不同
			当选用Spring自带的jdbc组件或 Mybatis 操作持久层时,使用:
				org.springframework.jdbc.datasource.DataSourceTransactionManager
			当选用其他持久层框架如 Hibernate 操作持久层时,使用:
				org.springframework.orm.hibernate5.HibernateTransactionManager
			
					
(2)	配置事务的通知
	
		
		
			//作用于增删改业务
			//作用于查询业务
		
	
			
	PS:关于标签的配置:
		name:	开启事务的方法
				*代表所有方法 , find*表示以find开头的方法,泛指查询方法
				所有查询方法以find开头 , 方便区分增删改 和 查询 方法 , 用于配置不同事务属性
						
		propagation:	事务的传播行为  --->  一个事务的执行结果对其他事务的影响程度
							当事务管理器操作事务时 , service层调用的多个dao层的方法被视为同一事务,但在实际业务中 , service层的方法操作
						一系列业务时 , 还可能会调用其他service层方法 , 和本service层方法并不属于同一事务 , 那么就需要设定service层方法
						执行事务的传播行为,当本事务执行失败时 , 决定是否对调用本方法以及本方法调用的其他事务进行影响
						
					(以下暂将service层方法称之为业务)
					REQUIRED:(默认值,增删改常用)	如果当前执行方法没有事务,就创建一个事务,有则加入该事务
						///我回滚传播其他业务也回滚 , 其他业务回滚传播给我我也回滚
					SUPPORTS:(查询常用) 支持当前事务,如果当前没有事务,就以非事务方式执行
						///当事务被其他业务创建 , 大家互相传播回滚 , 如果其他业务都没有创建事务 , 互不影响
					NESTED:(不影响大局的子业务) 存在事务,则在嵌套事务内执行。如果当前没有事务,新建事务
						///其他业务回滚传播给我我就回滚 , 我出现异常回滚不传播给其他业务
					REQUERS_NEW: 新建事务,如果当前在事务中,把当前事务挂起。	
						///我之前的事务为一个整体 , 自我开始创建一个新的事务作为一个整体 , 与旧事务互不干扰
					MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
					NEVER: 以非事务方式运行,如果当前存在事务,抛出异常
					NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
							
		read-only:	//事务是否只读
					增删改配置为	false
					查询配置为		true
				
		isolation:	//事务的隔离级别
					默认为数据库的隔离级别
					
		timeout:	//超时时间
					默认值为:-1 , 永不超时。
							
		rollback-for:	//仅当产生该异常时回滚 , 其他都不回滚
						默认全部回滚
				
		no-rollback-for:	//仅当产生该异常时不回滚 , 其他都回滚
							默认全部回滚
									
(3)	将切入点和事务关联起来
	
		
		
		
		///切面
	

【2】基于注解的声明式事务管理

(1)配置事务管理器
	
		 // 注入数据源
	
	
(2) 开启对注解事务的支持	--->	// 使用@Transactional注解在service层标记,取代xml中的(2)和(3)
	
		
(3)	在service层添加注解
	// 配置在类上时对全类的方法起作用 , 配置在方法上对单个方法作用 , 作用范围小则优先级高
		@Service("accountService")
		@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
		public class AccountServiceImpl implements IAccountService {
					
			@Override
			@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
			public void transfer(String sourceName, String targetName, Float money) {
						
			}	
		}					
	// 配置的属性:	同标签

5、全注解开发配置以及Junit的整合

【1】、主配置类,(替换xml文件)

        @Configuration	// 标明本类为配置类
	@ComponentScan("com.itheima")	// 告知spring在创建容器时要扫描的包
	@PropertySource("classpath:jdbcConfig.properties")	// 导入外置配置文件,供 @value 注解调用
	@Import({JdbcConfig.class,TransactionConfig.class}) // 导入jdbc配置类 , 事务管理配置类
	@EnableAspectJAutoProxy	// 开启对注解AOP的支持,即通知注解(仅使用Spring的事务管理时不需要开启)
	@EnableTransactionManagement	// 开启对注解事务的支持
	public class SpringConfiguration {

	}	

【2】、子配置类

   /**
	 * Jdbc配置类
	 */
	public class JdbcConfig {
		// 已导入外置配置文件 , 使用EL表达式自动赋值
		@Value("${jdbc.driver}")
		private String driver;	
		@Value("${jdbc.url}")
		private String url;
		@Value("${jdbc.username}")
		private String username;
		@Value("${jdbc.password}")
		private String password;
		
		/**
		 * 创建数据源对象
		 */
		@Bean("dataSource")
		public DataSource createDataSource() {
			try {
				DriverManagerDataSource ds = new DriverManagerDataSource();
				ds.setDriverClassName(driver); // 数据库驱动
				ds.setUrl(url); // 数据库资源路径
				ds.setUsername(username); // 用户名
				ds.setPassword(password); // 密码
				return ds;
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		
		/**
		 * 创建jdbcTemplate对象
		 */
		@Bean("jdbcTemplate")	// @Bean注解获取返回对象存入容器 , 默认id为方法名(首字母小写)
		@Scope("prototype")	// 默认为单例,设置为多例
		public JdbcTemplate createJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
			// @Qualifier单独使用 , 按指定Id注入对象给参数
			return new JdbcTemplate(dataSource);
		}
	}
	
	/**
	 * 事务管理配置类
	 */
	public class TransactionConfig {

		/**
		 * 创建事务管理器对象
		 */
		@Bean("transactionManager")
		public PlatformTransactionManager creatTransactionManager(DataSource dataSource) {
			return new DataSourceTransactionManager(dataSource);
		}
	}

【3】Junit整合

@RunWith(SpringJUnit4ClassRunner.class)	// 替换Junit中的运行器,创建Spring容器
	@ContextConfiguration(locations = "classpath:bean.xml")	// 定位配置xml的位置
	// @ContextConfiguration(classes = SpringConfiguration.class)--->定位配置类的位置
	public class AccountTest {

		@Autowired	// 自动注入业务层对象
		private IAccountService accountService;

		/**
		 * 测试转账方法
		 */
		@Test
		public void testTransfer() {
			accountService.transfer("ccc", "aaa", 200f);
		}
	}

6、xml配置文件约束和jar包依赖

【1】约束头信息


	
	


(1)	xmlns="http://www.springframework.org/schema/beans"
	基本的给容器配置Bean对象的约束
	
(2)	xmlns:context="http://www.springframework.org/schema/context"
	使用注解给容器配置类 的约束 , 使用时需要注明扫描的包(包括子包)
	///告知spring在创建容器时要扫描的包
	
	
(3)	xmlns:aop="http://www.springframework.org/schema/aop"
	配置切面的约束
	
(4)	xmlns:tx="http://www.springframework.org/schema/tx"
	声明式事务管理的约束

【2】依赖jar包信息【Maven工程】

(1)	Spring核心容器
	
		org.springframework
		spring-context
		5.0.2.RELEASE
	

(2)	Spring整合Junit的jar包
	
		org.springframework
		spring-test
		5.0.2.RELEASE
	

(3)	环绕通知设置切入点 , 解析execution表达式的接口依赖 ,
	
		org.aspectj
		aspectjweaver
		1.9.1
	

(4)	作用于子类的动态代理依赖
	
		cglib
		cglib
		2.2.2
	

(5)	Spring事务管理的依赖
	
		org.springframework
		spring-tx
		5.0.2.RELEASE
	
	
(5)	Spring操作数据库的依赖jar包,包含JdbcTemplate和spring自带的数据源(连接池)
	
		org.springframework
		spring-jdbc
		5.0.2.RELEASE
	
	
(6)	Spring默认持久层框架DBUtils依赖jar包
	
		commons-dbutils
		commons-dbutils
		1.4
	

参考及推荐:

说明:本篇博客文章内容基本上出自于下面所列博客中,觉得写的比较好,为了加强记忆,自己又码了遍。

1、Spring框架基础概要

2、Spring框架基本概念,Spring框架模块详解

3、Spring的7大功能模块的作用

学习不是单打独斗,如果你也是做Java开发,可以加我微信,一起分享学习经验!

本人微信号:pengcheng941206

你可能感兴趣的:(Spring)