一、为什么使用Spring(优点)
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
- Spring是一个开源免费的框架 , 容器 .
- Spring是一个轻量级的框架 , 非侵入式的 .
- 控制反转 IOC
- 面向切面 AOP
- 对事物的支持
- 对框架的支持
Spring用到的设计模式
- 工厂模式:
BeanFactory
就是简单工厂模式的体现,用来创建对象的实例; - 单例模式:
Bean
默认为单例模式。 - 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中
listener
的实现–ApplicationListener
。
二、Spring详解
Spring模块
Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。
- Spring核心容器: 该层基本上是Spring的核心。它包含以下模块:
Spring Core
、Spring Bean
、SpEL(Spring Expression Language)
、Spring Context
。- Spring核心容器-
Spring Core
:核心容器- 提供Spring框架的基本功能。
- 核心容器的主要组件是
BeanFactory
,它是工厂模式的实现。 BeanFactory
使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。- 核心类库,提供IOC服务。
- Spring上下文-
Spring Context
:Spring上下文- Spring上下文是一个配置文件,向Spring框架提供上下文信息。
- Spring上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、检验和调度功能。
- 提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等)。
- Spring核心容器-
- 数据访问/集成: 该层提供与数据库交互的支持。它包含:
JDBC(Java DataBase Connectivity)
、ORM(Object Relational Mapping)
、OXM(Object XML Mappers)
、JMS(Java Messaging Service)
、Transaction
。- Spring DAO:
- 为JDBC DAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。
- 异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。
- Spring DAO的面向JDBC的异常遵从通用的DAO异常层次结构。
- 对JDBC的抽象,简化了数据访问异常的处理。
- Spring ORM:
- Spring支持常用的
Hibernate
,Mybatis
,jdao
等框架的支持 - Spring本身并不对
ORM
进行实现,仅对常见的ORM
框架进行封装,并对其进行管理。 - Spring 的
ORM
模块提供对常用的ORM框架的管理和辅助支持 - 利用
ORM
封装包,可以混合使用所有Spring`供的特性进行对象/关系映射,如简单声明性事务管理。 - 所有这些都遵从Spring的通用事务和DAO异常层次结构。
- 对现有的ORM框架的支持。
- Spring支持常用的
- Spring DAO:
- Web–该层提供了创建Web应用程序的支持。它包含以下模块:
Web
、Web–Servlet
、Web–Socket
、Web–Portlet
。- Spring Web:Web上下文模块
- Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文,所以,Spring框架支持与Jakarta Struts的集成。
- Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- 提供了基本的面向Web的综合特性,例如多方文件上传。
- Spring MVC:MVC框架是一个全功能的构建Web应用程序的MVC实现。
- 通过策略接口,MVC框架变成为高度可配置的,MVC容纳了大量视图技术,其中包括JSP、Velocity、Tiles、iText和POI。
- 提供面向Web应用的Model-View-Controller实现。
- Spring Web:Web上下文模块
- AOP-该层支持面向切面编程
- Spring AOP:
- 通过配置管理特性,Spring AOP模块直接将面向切面的编程功能集成到了Spring框架中,可以将一些通用任务,如安全、事务、日志等集中进行管理,提高了复用性和管理的便捷性。
Instrumentation-该层为类检测和类加载器实现提供支持。
- 通过配置管理特性,Spring AOP模块直接将面向切面的编程功能集成到了Spring框架中,可以将一些通用任务,如安全、事务、日志等集中进行管理,提高了复用性和管理的便捷性。
- Spring AOP:
- Test-该层为使用
JUnit
和TestNG
进行测试提供支持。 - 几个杂项模块:
- Messaging–该模块为STOMP提供支持。它还支持注解编程模型,该模型用于从
WebSocket
客户端路由和处理STOMP
消息。 Aspects
–该模块为与AspectJ
的集成提供支持。
- Messaging–该模块为STOMP提供支持。它还支持注解编程模型,该模型用于从
Spring 提供了以下5种标准的事件
- 上下文更新事件(ContextRefreshedEvent):在调用
ConfigurableApplicationContext
接口中的refresh()
方法时被触发。 - 上下文开始事件(ContextStartedEvent):当容器调用
ConfigurableApplicationContext
的Start()
方法开始/重新开始容器时触发该事件。 - 上下文停止事件(ContextStoppedEvent):当容器调用
ConfigurableApplicationContext
的Stop()
方法停止容器时触发该事件。 - 上下文关闭事件(ContextClosedEvent):当
ApplicationContext
被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。 - 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(
request
)结束触发该事件。如果一个bean实现了ApplicationListener
接口,当一个ApplicationEvent
被发布以后,bean会自动被通知。
三、IOC(控制反转)
传统开发中,需要调用对象的时候,需要调用者手动来创建被调用者的实例,即对象是由调用者new
出来的。但是在Spring框架中,创建对象的工作不再由调用者来完成,而是交给IOC容器来创建,再推送给调用者,整个流程完成反转,所以是控制反转
- 将对象的创建权限交给了Spring,为了降低耦合度
- IOC底层原理:(工厂设计模式+反射+XML配置文件)
- IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
1.Spring 提供IOC容器实现两种方式(两个接口)
- BeanFactory:IOC容器基本实现,是Spring内部使用的接口,不建议开发人员使用
- 加载配置文件时,不会创建对象,只有在获取对象才去创建对象
- ApplicationContext:是BeanFactory的子接口,提供了更多更强大的接口,推荐开发人员使用
- 加载配置文件时,就会创建对象 FileSystemXmlApplicationContext(实现类) 盘路径
- ClassPathXmlApplicationContext(实现类) 类路径
2.IOC加载流程及Bean生命周期
IOC加载流程
- 多例Bean在容器启动时不实例化,必须要在
getBean
调用的时候才实例化。 loadBeanDefinitions
采⽤了模板模式,具体加载BeanDefinition
的逻辑由各个⼦类完成。- 单例
Bean
的初始化以及依赖注入一般都在容器的初始化阶段进行,除非设置了懒加载lazy-inint
为true
的单例bean
就是在第一次调用getBean()
方法的会后进行初始化和依赖注入。
初始化过程:BeanDefinition
的资源定位、解析、注册。
IOC加载流程图
bean生命周期详细流程图
通过上图了解
- 通过
BeanDefinitionReader
读取配置文件,获取bean
的定义信息解析成BeanDefinition
,并注入到BeanDefinition
实例中。 - 后置增强器
BeanFactoryPostProcessor
:实现BeanFactoryPostProcessor
接口可以在对象创建之前修改BeanDefinition
,也就是修改bean
的定义信息。/** * 扩展方法--后置增强器(可修改bean的定义信息) */ @Component public class ExtBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { //BeanDefinition studentService = beanFactory.getBeanDefinition("studentService"); System.out.println("扩展方法--可进行修改beanDefinition的定义信息"); } }
- 此 后置增强器 可存在多个,多次实现
BeanFactoryPostProcessor
即可
- 此 后置增强器 可存在多个,多次实现
BeanFactory
根据BeanDefinition
的定义信息创建实例化和初始化Bean
,进入Bean
的生命周期
Bean生命周期
Bean
的生命周期粗略概括主要为:实例化—>初始化—>使用—>销毁
细化后Bean`的生命周期
实例化
InstantiationAwareBeanPostProcessor
接口
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
// 实例化前置
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("postProcessBeforeInstantiation被调用了----在对象实例化之前调用-----beanName:" + beanName);
// 默认什么都不做,返回null
return null;
}
// 实例化后置
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInstantiation被调用了---------beanName:" + beanName);
//默认返回true,什么也不做,继续下一步
return true;
}
// 属性修改
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
System.out.println("postProcessPropertyValues被调用了---------beanName:"+beanName);
// 此方法可对bean中的属性值进行、添加、修改、删除操作;
// 对属性值进行修改,如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用,
return pvs;
}
}
1. 实例化前置
- 方法:
postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
- 在对象实例化之前对
bean
对象的class信息进行修改或者扩展,以达到我们想要的功能 - 底层是动态代理AOP技术实现的
- 是
bean
生命周期中最先执行的方法 - 返回值:
- 返回非空:
- 返回值是Object类型,意味着可以返回任何类型的值,由于此时目标对象还未实例化,所以该返回值可以用来代替原本该生成对象的目标对象的实例。
- 如果返回了非空的值,需要用到这个bean的时候,拿到的就现在返回的对象,不会去走第二步去实例化对象了;
- 返回空(null)值:
- 默认也是返回null值的,那么就直接返回,接下来会调用doCreateBean方法来实例化对象
- 返回非空:
2、实例化对象:
- 方法:
doCreateBean()
- 该方法创建实例,用反射技术创建
- 注意:这个时候只是将对象实例化了,对象内的属性还未设置
3、实例化后置
- 方法:
postProcessAfterInstantiation(Object bean, String beanName)
- 在目标对象实例化之后调用(
doCreateBean()
调用之后) - 对象已实例化但对象内的属性还未设置,都是null。
- 此方法的返回值决定要不要调用
postProcessPropertyValues
(getDependencyCheck()
与此方法共同决定) - 返回值:
- false :此方法返回
false
,且不需要check
,那么postProcessPropertyValues
就会被忽略不执行 - true : 如果返回
true
,postProcessPropertyValues
就会被执行
- false :此方法返回
4、属性修改
- 方法 :
postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
- 此方法可对属性值进行修改,修改范围包括添加、修改、删除操作
- 实例化后置
postProcessAfterInstantiation
() 方法返回false
,那么该方法不会被调用;
初始化
1、给用户属性赋值
- 方法:
populateBean()
- 用户属性指的是自定义的bean对象属性。如: User、Student、Teacher 、UserService、IndexService 这类的对象都是自定义bean对象,
- 此步骤主要给这类属性进行赋值操作
2、给容器属性赋值
- 容器属性是容器自带的属性,这些属性是spring本来就有的
- 它们都是 Aware 接口的实现类,主要有以下实现类,按执行顺序排列:
- 每个方法的作用为如下代码示例
@Component public class AllAwareInterface implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware, EmbeddedValueResolverAware, ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware, ApplicationContextAware, ServletContextAware, LoadTimeWeaverAware, ImportAware { /** * 让Bean对Name有知觉 * 简单的返回我们当前的beanName * 官方指导为:这个接口更多的使用在spring的框架代码中,实际开发环境应该不建议使用 */ @Override public void setBeanName(String name) { System.out.println("1 我是 BeanNameAware 的 setBeanName 方法 ---参数:name,内容:" + name); } /** * 获取Bean的类装载器 */ @Override public void setBeanClassLoader(ClassLoader classLoader) { System.out.println("2 我是 BeanClassLoaderAware 的 setBeanClassLoader 方法"); } /** * 获取bean工厂 * beanFactory可以不依赖注入方式,随意的读取IOC容器里面的对象 * 不过beanFactory本身还是要注入的 */ @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { // 注意: 如果使用 @Configuration 注解的话,setBeanFactory方法会执行2次, System.out.println("3 我是 BeanFactoryAware 的 setBeanFactory 方法"); } /** * 在工程启动时可以获得application.properties 、xml、yml 的配置文件配置的属性值 */ @Override public void setEnvironment(Environment environment) { System.out.println("4 我是 EnvironmentAware 的 setEnvironment 方法"); } /** * 设置嵌入值解析器 * 通常我们使用@Value注解来获取properties 和 yml 文件中的值,每个类中都要使用@Value, * 实现EmbeddedValueResolverAware接口后就方便多了。用法和@Value一致,需要用${}包裹住; */ @Override public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) { System.out.println(stringValueResolver.resolveStringValue("${logging.file}")); System.out.println("5 我是 EmbeddedValueResolverAware 的 setEmbeddedValueResolver 方法"); } /** * Spring ResourceLoader为我们提供了一个统一的getResource()方法来通过资源路径检索外部资源。 * 从而将资源或文件(例如文本文件、XML文件、属性文件或图像文件)加载到Spring应用程序上下文中的不同实现 ,其实说白了,就是用来加载外部资源的; * 参数:ResourceLoader:这个参数其实是ApplicationContext(spring 的上下文对象)。 * 并且我们可以指定不同的前缀来创建路径以从不同位置加载资源: * 前缀 示例 说明 * classpath: 从类路径加载classpath:com/myapp/config.xm1 从类路径加载 * file: file:///data/config.xml 从文件系统作为 URL 加载 * http: https ://myserver/logo.png 从 URL 加载 * (none) /data/config.xml 取决于底层的 ApplicationContext * */ @Override public void setResourceLoader(ResourceLoader resourceLoader) { // 可直接强转为 ApplicationContext ApplicationContext context = (ApplicationContext) resourceLoader; System.out.println("6 我是 ResourceLoaderAware 的 setResourceLoader 方法"); } /** * 事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。 * 用户注册后,不再是显示调用其他的业务 Service,而是发布一个用户注册事件。 * 那么在这里是发布事件,那就肯定有监听事件的接口, * 这个接口叫做 ApplicationListener <E extends ApplicationEvent > ,只要实现 ApplicationListener 接口就可以接受发布的事件了, * 具体示例在代码块完结后 */ @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { System.out.println("7 我是 ApplicationEventPublisherAware 的 setApplicationEventPublisher 方法"); } /** * 国际化消息通知操作 */ @Override public void setMessageSource(MessageSource messageSource) { System.out.println("8 我是 MessageSourceAware 的 setMessageSource 方法"); } /** * 通过实现ServletContextAware接口可获取servletContext,也就是servlet的上下文; * * ServletContext : * WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。S * ervletConfig对象中维护了ServletContext对象的引用 * 开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。 * 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。 * ServletContext对象通常也被称之为context域对象。 */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("9 我是 ApplicationContextAware 的 setApplicationContext 方法"); } @Override public void setServletContext(ServletContext servletContext) { System.out.println("10 我是 ServletContextAware 的 setServletContext 方法"); } /** * 简称LTW,LTW是AOP的一种实现方式,此方法是为了获取Aop织入的对象,使用的织入方式是:类加载期织入, * 一般的aop都是运行期织入,就是在运行的时候才进行织入切面方法,但是LTW是在类加载前就被织入了,也就是class文件在jvm加载之前进行织入切面方法 * 只有在使用 @EnableLoadTimeWeaving 或者存在 LoadTimeWeaver 实现的 Bean 时才会调用,顺序也很靠后; */ @Override public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { //LoadTimeWeaver 简称LTW,LTW是AOP的一种实现方式,此方法是为了获取Aop织入的对象,使用的织入方式是:类加载期织入, // 一般的aop都是运行期织入,就是在运行的时候才进行织入切面方法,但是LTW是在类加载前就被织入了,也就是class文件在jvm加载之前进行织入切面方法 // 只有在使用 @EnableLoadTimeWeaving 或者存在 LoadTimeWeaver 实现的 Bean 时才会调用,顺序也很靠后 System.out.println("11 我是 LoadTimeWeaverAware 的 setLoadTimeWeaver 方法"); } /** * 只有被其他配置类 @Import(XX.class) 时才会调用 * 这个调用对 XX.class 中的所有 @Bean 来说顺序是第 1 的。 */ @Override public void setImportMetadata(AnnotationMetadata annotationMetadata) { System.out.println("12 我是 ImportAware 的 setImportMetadata 方法"); } }
setApplicationEventPublisher()
示例- 先创建一个实体类
StringEvent.java
,用来存储发布的事件内容//事件监听对象 public class StringEvent extends ApplicationEvent { private String str; // 构造函数 public StringEvent(Object source) { super(source); str = source.toString(); } // 获取字符串 public String getStr(){ return str; } }
- 创建一个发布事件的类:
ExtApplicationEventPublisherAware.java
,实现ApplicationEventPublisherAware
接口增加发布事件的功能/** * 发布事件 */ @Component public class ExtApplicationEventPublisherAware implements ApplicationEventPublisherAware { @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { System.out.println("发布事件,事件对象为 StringEvent ,内容为 :1234"); StringEvent stringEvent = new StringEvent("1234"); // 发布事件 ,发布后会在 ApplicationListener.onApplicationEvent()方法进行捕获; applicationEventPublisher.publishEvent(stringEvent); // 发布事件 } }
- 创建一个事件监听器:
EventListener.java
,用来监听所有发布的事件;//事件监听器 @Component public class EventListener implements ApplicationListener<StringEvent> { @Override public void onApplicationEvent(StringEvent o) { System.out.println("监听到事件,内容:"+o.getStr()); } }
- 运行结果示例(启动Spring项目):
发布事件,事件对象为 StringEvent ,内容为 :1234 监听到事件,内容:1234
- 先创建一个实体类
4.IOC操作 Bean 管理(Spring创建对象)
基于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">
<!--配置User对象创建-->
<bean id="user" class="com.spring5.User"></bean>
</beans>
- 在
applicationContext.xm
l配置文件中,使用bean
标签,标签中添加对应属性就可实现对象创建- id属性:给创建的对象的名称,不可添加特殊符号(唯一标识,不可重复) class 属性:需要创建类的全路径
- name属性:和Id属性作用相同,可以添加特殊符号
- 注入属性
- set方法:在bean标签中添加如下标签即可set方法注入属性
<property name="属性名称" value=‘属性值’/>
- 名称空间注入:
- 在xml头部
beans
标签中添加一个名称空间,直接把属性写在bean
标签内即可,不用再写property
标签
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="book" class="com.spring5.Book" p:bname="WeiSanJin" p:bauthor="WeiSanJin"></bean> </beans>
- 在xml头部
- 有参构造:
- 在bean标签中添加如下标签即可完成有参构造
<constructor-arg name='属性名称' value=‘属性值’/>
- 注入null值:使用 null 标签
<constructor-arg name='属性名称' > <null/> </constructor-arg>
- 注入特殊符号:
- 使用转义字符代替特殊符号 如:< 小于号 转义为 <;
- 使用value标签:
<value> ![CDATA[属性值]] </value>
- 注入外部bean属性:第一步先创建自己的bean,第二部创建外部bean
- 此处必须在自己的bean中存在外部bean属性用于接收外部bean
- 在自己的bean中注入外部bean:
<constructor-arg name='属性名称' ref=‘外部 bean id’/>
- property 中可以嵌套bean:被嵌套的bean中还可以写入property 标签:
<property name='属性名称'> <bean id='xxxx' class='xxxx'> <property name="属性名称" value=‘属性值’/> <property name="属性名称" value=‘属性值’/> </bean> </property >
集合
类属性注入<!--1. 集合类型属性注入--> <bean id="stu" class="com.spring5.collectionytpe.Stu"> <!--数组类型属性注入 --> <property name="courses"> <array> <value>Java课程</value> <value>数据库课程</value> </array> </property> <!--list类型属性注入 --> <property name="list"> <list> <value>张三</value> <value>小三</value> </list> //如果集合里的存储的是其它bean对象,就需要引用,同时也需要配置引用的bean <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> <!--map类型属性注入 --> <property name="maps"> <map> <entry key="Java" value="java"></entry> <entry key="PHP" value="php"></entry> </map> </property> <!--set类型属性注入 --> <property name="sets"> <set> <value>Mysql</value> <value>Redis</value> </set> </property> </bean> <!-- 创建多个course对象--> <bean id="course1" class="com.spring5.collectionytpe.Course"> <property name="cname" value="String"></property> </bean> <bean id="course2" class="com.spring5.collectionytpe.Course"> <property name="cname" value="String"></property> </bean>
util
命名空间<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!--1 提前list集合类型属性注入--> <util:list id="bookList"> <value>三国演义</value> <value>水浒传</value> <value>西游记</value> <value>红楼梦</value> </util:list> <!--2 提前list集合类型属性使用--> <bean id="book" class="com.spring5.collectionytpe.Book"> <property name="list" ref="bookList"></property> </bean> </beans>
基于注解方式
!!!!!!!!!!!!重点注意!!!!!!!!!!!!
使用基于注解方式注入bean时,必选完成以下两种方式其中一种!
xml配置注解支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context">
<!--容器创建时要扫描的包-->
<context:component-scan base-package="org.example.*"/>
</beans>
配置类配置注解支持
//指定当前类为配置类
@Configuration
//容器创建时要扫描的包
@ComponentScan(basePackages = "org.example.*")
public ApplicationConfig {
}
@Configuration
、@Component
、@Controller
、@Service
、@Repository
~
具体解释请查看上方SpringIoc常用注解
四、DI(依赖注入)
1.概述
Dependency Injection即为依赖注入,简称DI 。
创建对象的过程中Spring可以依据配置对象的属性进行设置,这个过程称之为依赖注入,即 DI
- 简单来说,在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
- 形象来说,组件之间依赖关系由容器在运行期决定的,即由容器动态的将某个依赖关系注入到组件之中。
- 依赖:
- 应用程序依赖于IOC容器
- 应用程序依赖于IOC容器提供的对象所需外部资源
- 注入:
- 容器的IOC是被注入的对象(我们项目中所需要的对象、资源、数据等等)
- 动态的向某个对象提供它所需要的其他对象
- 需要外部的资源既可以注入到IOC容器中,并由IOC容器来实现注入对象的控制反转
2.注入方式
2.1.Setter方法注入
- 定义一个
JavaBean
并赋予其Setter
方法//Lombok@Data注解提供了Setter方法 public class User { private Integer id; private String password; private String sex; private Integer age; private Date bornDate; private String[] hobbys; private Set<String> phones; private List<String> names; private Map<String, String> countries; private Properties files; }
- 注入各种数据(Spring底层对Date日期类型做了处理,默认处理格式为“yyyy/MM/dd”)
<bean id="User" class="com.mylifes1110.bean.User"> <!--注入基本数据类型--> <property name="id" value="1"/> <property name="password" value="123456"/> <property name="sex" value="male"/> <property name="age" value="18"/> <!--注入日期类型--> <property name="bornDate" value="1999/09/09"/> <!--注入数组类型--> <property name="hobbys"> <array> <value>Run</value> <value>Jump</value> <value>Climb</value> </array> </property> <!--注入List集合类型--> <property name="names"> <list> <value>Ziph</value> <value>Join</value> <value>Marry</value> </list> </property> <!--注入Set集合类型--> <property name="phones"> <set> <value>110</value> <value>119</value> <value>120</value> </set> </property> <!--注入Properties类型--> <property name="files"> <props> <prop key="first">One</prop> <prop key="second">Two</prop> <prop key="third">Three</prop> </props> </property> <!--注入Map集合类型--> <property name="countries"> <map> <entry key="CHINA" value="中国"/> <entry key="USA" value="美国"/> <entry key="UK" value="英国"/> </map> </property> </bean>
2.2.构造方法注入
创建对象时,Spring工厂会通过构造方法为对象的属性赋值。由于某些框架或者项目中并没有为JavaBean
提供Setter
方法,我们就可以利用其构造方法来注入
- 定义一个
JavaBean
对象,为其提供构造方法public class Student { private Integer id; private String name; private String sex; private Integer age; //Constructors public Student(Integer id , String name , String sex , Integer age){ this.id = id; this.name = name; this.sex = sex; this.age = age; } }
- 构造方法注入
<!--构造注入--> <bean id="u3" class="com.mylifes1110.bean.Student"> <!-- 除标签名称有变化,其他均和Set注入一致 --> <constructor-arg name="id" value="1234" /> <constructor-arg name="name" value="tom" /> <constructor-arg name="age" value="20" /> <constructor-arg name="sex" value="male" /> </bean>
2.3.注入自建类型数据
将Bean当做参数注入即可
2.4.自动注入
xml自动注入
- 基于名称自动注入值
<bean id="UserDao" class="com.mylifes1110.dao.impl.UserDaoImpl"/> <!--为UserServiceImpl中的属性基于名称自动注入值--> <bean id="userService" class="com.mylifes1110.service.impl.userServiceImpl" autowire="byName"/> </beans>
- 基于类型自动注入值
根据实现的接口来判断并自动注入值,如果实现此接口的实现类太多,它会在很多实现此接口的实现类中选择名字相同的实现类进行注入。(现根据判断,如果不成功,则根据名称注入)<bean id="userDao" class="com.mylifes1110.dao.UserDaoImpl" /> <!--为UserServiceImpl中的属性基于类型自动注入值--> <bean id="userService" class="com.mylifes1110.service.impl.UserServiceImpl" autowire="byType"/> </beans>
注解自动注入
注解名称 | 描述 |
---|---|
@Autowired | 基于类型自动注入 |
@Resource | 基于名称自动注入 |
@Qualifier(“userDAO”) | 限定要自动注入的bean的id,一般和@Autowired联用 |
@Value | 注入简单类型数据 (jdk8种基本数据类型+String类型) |
- 使用基于类型自动注入,将Dao层注入到Service层
@Service public class UserServiceImpl implements UserService { @Autowired //注入类型为UserDao的bean @Qualifier("userDao") //如果有多个类型为UserDao的bean,可以用此注解从中指定一个 private UserDao userDao; }
- 使用基于名称自动注入,将Dao层注入到Serivce层
@Service public class UserServiceImpl implements UserService { @Resource("userDao") //注入id=“userDao”的bean private UserDao userDao; }
- 使用注入简单类型数据注解来完成简单注入JavaBean
public class User{ @Value("1") //注入数字 private Integer id; @Value("Ziph") //注入String private String name; }
五、Spring AOP
什么是AOP:
AOP是一种思想,一种编程方式。编写一段代码在合适的时机找到切入点然后执行。不直接修改原来的代码,而是在原代码执行的前后执行一段额外的代码。
优点:
- 可以降低模块之间的耦合性
- 提供代码的复用性
- 提高代码的维护性
- 集中管理非业务代码,便于维护
- 业务代码不受非业务代码影响,逻辑更加清晰
Spring引入了一套AOP顶级API-- AOP联盟,用来定义和使用AOP。底层根据配置生成JDK或CGLIB动态代理对象
1.AOP联盟
spring-aop.jar
中 org.aopalliance.aop
包和intercept
包下 是 AOP联盟提供的AOP标准API
Advice.java
:顶级通知类,实现可以是任何类型的通知。例如:拦截器Interceptor.java
:通用拦截器,包含需要植入的自定义切面内容,代理类通过连接点来调用拦截器执行增强逻辑,继承Advice
Invocation.java
:表示调用,可以被拦截的连接点,静态连接点,继承Joinpoint
,和Interceport
结合使用Joinpoint.java
:运行时连接点,将被切面的对象和拦截器连接起来,包含了被切面对象信息和拦截链,proceed
方法通过传入Interceport
实现链式调用拦截器MethodInterceptor.java
:方法拦截器,方法被调用时拦截,用户实现invoke方法自定义切面内容,实现拦截行为,继承Interceport
MethodInvocation.java
:方法连接点,方法被调用时携带被切面对象信息和拦截链,执行切面自定义切面内容
2.SpringAOP核心概念
-
SpringAOP在AOP联盟规则之上新增了几个类,丰富了AOP定义及使用概念,如下:
Advisor
:包含通知(拦截器),Spring内部使用的AOP顶级接口,还需要包含一个aop适用判断的过滤器,考虑到通用性,过滤规则由其子接口定义,例如IntroductionAdvisor
和PointcutAdvisor
,过滤器用于判断bean是否需要被代理Pointcut
: 切点,属于过滤器的一种实现,匹配过滤哪些类哪些方法需要被切面处理,包含一个ClassFilter
和一个MethodMatcher
,使用PointcutAdvisor
定义时需要ClassFilter
:限制切入点或引入点与给定目标类集的匹配的筛选器,属于过滤器的一种实现。过滤筛选合适的类,有些类不需要被处理MethodMatcher
:方法匹配器,定义方法匹配规则,属于过滤器的一种实现,哪些方法需要使用AOP
-
AOP术语
名称 翻译 说明 Joinpoint 连接点 是在应用程序执行过程中能够插入切面的一个点,这个点可以是调用方法时,抛出异常时,甚至修改一个字段时,切面代码可以利用这些点插入到应用程序的正常流程中,并增加新的行为 Pointcut 切点 需要去添加代码的“地方”。 描述了切面织入的位置。 切点的定义会匹配通知所要织入的一个或多个连接点,可以指定明确的类和方法,也可以使用正则表达式匹配符合条件的类和方法来指定这些切点。 Target 目标 指代理的目标对象 Advice 通知 向切点处动态添加的那部分代码。 描述了切面要做的工作。 Aspect 切面 切点+通知 Introduction 引入 向现有类添加新方法或属性。 Weaving 织入 把增加代码应用到目标上,生成代理对象的过程 Proxy 代理 指生成代理的对象 -
通知分类
通知 翻译 说明 before 前置通知 通知方法在目标方法调用之前执行 after 后置通知 通知方法在目标方法返回或异常后调用 after-returning 返回后通知 通知方法在目标方法返回后调用 after-throwing 抛出异常通知 通知方法在目标方法异常后调用 around 后置通知 通知方法在目标方法封装起来 -
织入时期
时期 说明 编译期 切面在目标类编译时被织入 类加载期 切面在目标类加载到JVM时被织入 运行期 切面在应用运行的某个时期被织入,通常在织入切面时,AOP容器会为目标对象动态创建一个代理对象(Spring AOP就是以这种方式织入切面的) 关系图
3.使用AOP的方式
准备: 创建接口,存在一个睡觉的方法,并实现该接口。
//接口
public interface Sleepable {
public void sleep();
}
//实现类
//接口实现类,“Me”可以睡觉,“Me”就实现可以睡觉的接口
public class Me implements Sleepable{
public void sleep() {
System.out.println("\n睡觉!不休息哪里有力气学习!\n");
}
}
Me关注于睡觉的逻辑,但是睡觉需要其他功能辅助,比如睡前脱衣服,起床脱衣服,这里开始就需要AOP替“Me”完成!解耦!
睡觉前要脱衣服:切入点之前执行。
起床后要穿衣服:切入点之后执行。
3.1.经典的基于代理的AOP
此方式可以利用反射机制获得该方法的一些信息,必须的参数等
创建一个类编写增强或辅助代码并实现如下接口
public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
//MethodBeforeAdvice接口,在目标方法调用前调用
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("睡觉前要脱衣服!");
}
//AfterReturningAdvice接口,在目标方法被调用之后调用
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("起床后要穿衣服!");
}
}
将此类加入到SpringIoc容器管理,并配置切入点
<?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="me" class="org.example.SpringAopTest.Me"/>
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="org.example.SpringAopTest.SleepHelper"/>
<!-- 定义切入点位置 -->
<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*sleep"/>
</bean>
<!-- 使切入点与通知相关联,完成切面配置 -->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="sleepHelper"/>
<property name="pointcut" ref="sleepPointcut"/>
</bean>
<!-- 设置代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的对象,有睡觉能力 -->
<property name="target" ref="me"/>
<!-- 使用切面 -->
<property name="interceptorNames" value="sleepHelperAdvisor"/>
<!-- 代理接口,睡觉接口 -->
<property name="proxyInterfaces" value="org.example.SpringAopTest.Sleepable"/>
</bean>
</beans>
运行测试
通过AOP代理的方式执行Me的sleep()方法,会把执行前、执行后的操作执行,实现了AOP的效果
public class Test {
public static void main(String[] args){
@SuppressWarnings("resource")
//如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
//ApplicationContext appCtx = new ClassPathXmlApplicationContext("src/main/resources/application.xml");
ApplicationContext appCtx = new FileSystemXmlApplicationContext("src/main/resources/application.xml");
Sleepable me = (Sleepable)appCtx.getBean("proxy");
me.sleep();
}
}
执行结果:
睡觉前要脱衣服!
睡觉!不休息哪里有力气学习!
起床后要穿衣服!
可以通过org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator简化配置
- 将配置文件中设置代理的代码去掉,加上:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
- 在
Test
中,直接获取me
对象,执行sleep
方法,就可以实现同样的功能! - 通过自动匹配,切面会自动匹配符合切入点的bean,会被自动代理,实现功能!
3.2…通过AspectJ提供的注解实现AOP
@Aspect
public class SleepHelper{
public SleepHelper(){
}
@Pointcut("execution(* *.sleep())")
public void sleeppoint(){}
@Before("sleeppoint()")
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
@AfterReturning("sleeppoint()")
public void afterSleep(){
System.out.println("睡醒了要穿衣服!");
}
}
在方法中,可以加上JoinPoint参数以进行相关操作
//当抛出异常时被调用
public void doThrowing(JoinPoint point, Throwable ex)
{
System.out.println("doThrowing::method "
+ point.getTarget().getClass().getName() + "."
+ point.getSignature().getName() + " throw exception");
System.out.println(ex.getMessage());
}
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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy />
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="org.example.SpringAopTest.SleepHelper"></bean>
<!-- 定义被代理者 -->
<bean id="me" class="org.example.SpringAopTest.Me"></bean>
</beans>
运行测试
通过AOP代理的方式执行Me的sleep()方法,会把执行前、执行后的操作执行,实现了AOP的效果
public class Test {
public static void main(String[] args){
@SuppressWarnings("resource")
//如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
//ApplicationContext appCtx = new ClassPathXmlApplicationContext("src/main/resources/application.xml");
ApplicationContext appCtx = new FileSystemXmlApplicationContext("src/main/resources/application.xml");
Sleepable me = (Sleepable)appCtx.getBean("me");
me.sleep();
}
}
执行结果:
睡觉前要脱衣服!
睡觉!不休息哪里有力气学习!
起床后要穿衣服!
3.3.使用Spring来定义纯粹的POJO切面(通过<aop:fonfig> 标签配置)
这种方式的优点就是在代码中不体现任何AOP相关配置,纯粹使用xml配置。
SleepHelper类
public class SleepHelper{
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
public void afterSleep(){
System.out.println("睡醒了要穿衣服!");
}
}
配置文件:
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="org.example.SpringAopTest.SleepHelper"/>
<!-- 定义被代理者 -->
<bean id="me" class="org.example.SpringAopTest.Me"/>
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
<aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
</aop:aspect>
</aop:config>
</beans>
配置的另一种写法
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="org.example.SpringAopTest.SleepHelper"/>
<!-- 定义被代理者 -->
<bean id="me" class="org.example.SpringAopTest.Me"/>
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
<aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
<aop:after pointcut-ref="sleepHelpers" method="afterSleep" />
</aop:aspect>
</aop:config>
</beans>
六、Spring声明式事务
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
-
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
-
将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
事务四个属性ACID
-
原子性(atomicity):事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
-
一致性(consistency):一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
-
隔离性(isolation):可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
-
持久性(durability):事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
Spirng配置事务
xml配置方式
xml文件配置
注意头文件的约束导入 : tx
头文件配置
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
配置事务管理器
事务管理器:
-
无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
-
就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
向IOC容器注册事务管理器
<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>
配置AOP织入事务
导入AOP头文件
xmlns:aop="http://www.springframework.org/schema/aop"
向IOC注册代理
<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
基于注解的声明式事务
使用 @EnableTransactionManagement 注解开启事务
@Configuration
@EnableTransactionManagement//开启基于注解的声明式事务
public class SpringTxConfig {
}
spring的事务传播机制
- PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。
- PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
- PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。
- PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
Spring中的隔离级别
- ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
- ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。
- ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。
- ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。
- ISOLATION_SERIALIZABLE:所有事务逐个依次执行。