Spring源码分析
spring介绍
什么是spring?
百度百科的介绍
spring官方网址:
我们经常说的spring其实指的是 spring framework (spring 框架)
为什么学习spring?
好处
耦合和内聚介绍
耦合性(coupling),也叫耦合度,是对模块间关联程度的度量。
在软件工程中,耦合指的就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类与架构之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当适当做一件好事。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
spring体系结构
spring核心概念介绍
ioc(核心中的核心):inverse of control,控制反转。对象的创建权力由程序反转给spring框架。
aop:aspect oriented programming,面向切面编程。在不修改目标对象的源代码情况下,增强ioc容器中bean的功能。
di:dependency injection,依赖注入。在spring框架负责创建bean对象时,动态的将依赖对象注入到bean组件中!!
spring容器:指的就是ioc容器。
spring ioc原理分析
什么是ioc容器?
所谓的ioc容器就是指的是spring中bean工厂里面的map存储结构(存储了bean的实例)。
spring框架中的工厂有哪些?
applicationcontext接口()
实现了beanfactory接口
实现applicationcontext接口的工厂,可以获取到容器中具体的bean对象
beanfactory工厂(是spring架构早期的创建bean对象的工厂接口)
实现beanfactory接口的工厂也可以获取到bean对象
其实通过源码分析,不管是beanfactory还是applicationcontext,其实最终的底层beanfactory都是defaultlistablebeanfactory
applicationcontext和beanfactory的区别?
创建bean对象的时机不同:
beanfactory采取延迟加载,第一次getbean时才会初始化bean。
applicationcontext是加载完applicationcontext.xml时,就创建具体的bean对象的实例。(只对beandefition中描述为时单例的bean,才进行饿汉堡式加载)
如何创建web环境中的ioc容器?
创建方式
- applicationcontext接口常用实现类
classpathxmlapplicationcontext:
它是从类的根路径下加载配置文件 推荐使用这种
filesystemxmlapplicationcontext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
annotationconfigapplicationcontext:
当我们使用注解配置容器对象时,需要使用此类来创建spring容器。它用来读取注解。
- java应用中创建ioc容器:(了解)
applicationcontext context=new classpathxmlapplicationcontext(xml路径);
- web应用中创建ioc容器:(重点)
web.xml中配置contextloaderlistener接口,并配置contextconfiglocation参数
web容器启动之后加载web.xml,此时加载contextloaderlistener监听器(实现了servletcontextlistener接口,该接口的描述请见下面的《三类八种监听器》)
contextloaderlistener监听器会在web容器启动的时候,出发contextinitialized()方法
contextinitialized()方法会调用initwebapplicationcontext()方法,该方法负责创建spring容器(defaultlistablebeanfactory)
【web三类八种监听器】
监听域对象的生命周期
servletcontextlistener:
创建:服务器启动
销毁:服务器正常关闭
spring contextloaderlistener(服务器启动时负责加载spring配置文件)
httpsessionlistener
创建:第一次访问request.gethttpsession()
销毁:调用invalidate();非法关闭;过期
servletrequestlistener
创建:每一次访问
销毁:相应结束
监听域对象的属性:(添加、删除、替换)
servletcontextattributelistener
httpsessionattributelistener
servletrequestattributelistener
监听httpsession中javabean的改变:
httpsessionbindinglistener(httpsession和javabean对象的绑定和解绑)
httpsessionactivationlistener(httpsession的序列化,活化,纯化)
源码分析
参考资料中的源码中的工程《spring-sourcecode》
1.web服务器(tomcat)启动会加载web.xml(启动contextloaderlistener监听器):
2.创建web环境中的spring容器
3.contextloader类中创建spring容器并初始化容器中的bean实例
4.configureandrefreshwebapplicationcontext方法中调用初始化bean的refresh方法
图示
该图主要是分析上面第三步骤中【创建spring容器】的图示
ioc容器如何创建bean对象?
源码分析
源码来源于abstractapplicationcontext类:
@override public void refresh() throws beansexception, illegalstateexception { synchronized (this.startupshutdownmonitor) { // prepare this context for refreshing. preparerefresh(); //1.创建真正的spring容器(defaultlistablebeanfactory) //2.加载beandefition(描述要初始化的bean的信息) //3.将beandefition注册到beandefitionregistry // tell the subclass to refresh the internal bean factory. configurablelistablebeanfactory beanfactory = obtainfreshbeanfactory(); // prepare the bean factory for use in this context. preparebeanfactory(beanfactory); try { // allows post-processing of the bean factory in context subclasses. postprocessbeanfactory(beanfactory); //执行实现了beanfactorypostprocessor接口的bean //比如propertyplaceholderconfigurer(context:property-placeholer)就是此处被调用的,替换掉beandefition中的占位符(${})中的内容 // invoke factory processors registered as beans in the context. invokebeanfactorypostprocessors(beanfactory); //注册beanpostprocessor(后置处理器) //比如容器自动装载了一个autowiredannotationbeanpostprocessor后置处理器(实现@autowired注解功能) // register bean processors that intercept bean creation. registerbeanpostprocessors(beanfactory); // initialize message source for this context. initmessagesource(); // initialize event multicaster for this context. initapplicationeventmulticaster(); // initialize other special beans in specific context subclasses. onrefresh(); // check for listener beans and register them. registerlisteners(); //初始化非懒加载方式的单例bean实例 // instantiate all remaining (non-lazy-init) singletons. finishbeanfactoryinitialization(beanfactory); // last step: publish corresponding event. finishrefresh(); } catch (beansexception ex) { if (logger.iswarnenabled()) { logger.warn("exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // destroy already created singletons to avoid dangling resources. destroybeans(); // reset 'active' flag. cancelrefresh(ex); // propagate exception to caller. throw ex; } finally { // reset common introspection caches in spring's core, since we // might not ever need metadata for singleton beans anymore... resetcommoncaches(); } } }
图示
spring ioc基于xml的使用
创建工程
环境准备
- maven
- jdk
- spring
- eclipse
工程搭建
pom文件
<project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <groupid>com.kkb</groupid> <artifactid>spring</artifactid> <version>0.0.1-snapshot</version> <dependencies> <!-- spring 核心组件中的4个依赖 --> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-core</artifactid> <version>5.0.7.release</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>5.0.7.release</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-beans</artifactid> <version>5.0.7.release</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-expression</artifactid> <version>5.0.7.release</version> </dependency> <!-- 单元测试junit --> <dependency> <groupid>junit</groupid> <artifactid>junit</artifactid> <version>4.12</version> </dependency> </dependencies> <build> <plugins> <!-- 配置maven的jdk编译级别 --> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-compiler-plugin</artifactid> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>utf-8</encoding> </configuration> </plugin> </plugins> </build> </project>
spring配置文件(只编写配置文件头)
<?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"> </beans>
具体实现
在spring的xml配置文件中配置一个bean标签,该标签最终会被加载为一个beandefition对象(描述对象信息)
思路:
编写userservice接口的实现类
将userservice实现类交给spring ioc容器管理
从spring ioc容器中获取userservice实现类
编写接口:userservice
编写实现类:userserviceimpl
编写xml文件:applicationcontext.xml
编写单元测试代码:testspring
bean标签详解
bean标签作用:
用于配置对象让spring来创建
默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
bean标签属性:
id:给对象在容器中提供一个唯一标识。用于获取对象。
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope:指定对象的作用范围。
singleton:默认值,单例的(在整个容器中只有一个对象)。
prototype:多例的。
request:web项目中,spring创建一个bean的对象,将对象存入到request域中。
session:web项目中,spring创建一个bean的对象,将对象存入到session域中。
global session:web项目中,应用在portlet环境,如果没有portlet环境那么globalsession相当于session。
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。比如datasource的配置中一般需要指定destroy-method="close"。
bean的作用范围:
单例对象:scope="singleton"
一个应用只有一个对象的实例。它的作用范围就是整个应用
生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope="prototype"
每次访问对象时,都会重新创建对象实例。
生命周期:
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被java的垃圾回收期回收了。
实例化bean的三种方式
第一种:使用默认无参构造函数(重点)
在默认情况下:它会根据默认无参构造函数来创建类对象。
如果bean中没有默认无参构造函数,将会创建失败
<bean id="userservice" class="com.chenyanbin.spring.service.userserviceimpl">
第二种:静态工厂(了解)
/** * 模拟一个静态工厂,创建业务层实现类 */ public class staticfactory { public static userservice createuserservice(){ return new userserviceimpl(); } }
<!--此种方法时: 使用staticfactory类中的静态方法createuserservice创建对象,并存入spring容器 id属性:指定bean的id,用于从容器中获取 class属性:指定静态工厂的全限定类名 factory-method属性:指定生产对象的静态方法 --> <bean id="userservice" class="com.chenyanbin.spring.factory.staticfactory" factory-method="createuserservice"></bean>
第三种:实例工厂(了解)
/** * 模拟一个实例工厂,创建业务层实现类 * 此工厂创建对象,必须现有工厂实例对象,再调用方法 */ public class instancefactory { public userservice createuserservice(){ return new userserviceimpl(); } }
<!-- 此种方式是: 先把工厂的创建交给spring来管理。 然后在使用工厂的bean来调用里面的方法 factory-ben属性:用于指定实例工厂bean的id。 factory-method属性:用于指定实例工厂中创建对象的方法。 --> <bean id="instancefactory" class="com.chenyanbin.factory.instancefactory"></bean> <bean id="userservice" factory-bean="instancefactory" factory-method="createuserservice"></bean>
spring di(依赖注入)介绍
概述
什么是依赖?
依赖指的就是bean实例中的属性
属性分为:简单类型(8种基本类型和string类型)的属性、pojo类型的属性、集合数组类型的属性。
什么是依赖注入
依赖注入:dependency injection。它是spring框架核心ioc的具体实现。
为什么要进行依赖注入?
我们的程序在编写时,通过控制反转,把对象的创建交给了spring,但是代码中不可能出现没有依赖的情况。
ioc解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用spring之后,就让spring来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
依赖注入的方式(基于xml)
构造函数注入
顾名思义,就是使用类中的构造函数,给成员变量赋值。
注意,赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。
具体代码如下:
public class userserviceimpl implements userservice { private int id; private string name; public userserviceimpl(int id, string name) { this.id = id; this.name = name; } @override public void saveuser() { system.out.println("保存用户:id为"+id+",name为"+name+" service实现"); } }
<!--使用构造函数的方式,给service中的属性传值要求:类中需要提供一个对应参数列表的构造函数。 涉及的标签:constructor-arg index:指定参数在构造函数参数列表的索引位置 name:指定参数在构造函数中的名称 value:它能赋的值是基本数据类型和string类型 ref:他能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean --> <bean id="userservice" class="com.chenyanbin.spring.service.userserviceimpl"> <constructor-arg name="id" value="1"></constructor-arg> <constructor-arg name="name" value="zhangsan"></constructor-arg> </bean>
set方法注入(重点)
set方法注入又分为手动装配方式注入和自动装配方式注入。
手动装配方式(xml方式):bean标签的子标签property,需要在类中指定set方法。
自动装配方式(注解方式):@autowired注解、@resource注解。
@autowired:一部分功能是查询实例,从spring容器中根据类型(java类)获取对应的实例。另一部分功能就是赋值,将找到的实例,装配和另一个实例的属性值。(注意事项:一个java类型在同一个spring容器中,只能有一个实例)
@resource:一部分功能是查询实例,从spring容器中根据bean的名称(bean标签的名称)获取对应的实例。另一部分功能就是赋值,将找到的实例,装配给另一个实例的属性值。
使用p名称空间注入数据(本质上还是调用set方法)
1.步骤一:需要先引入p名称空间
在schema的名称空间中加入该行:
xmlns:p="http://www.springframework.org/schema/p"
2.步骤二:使用p名称空间的语法
p:属性名="" p:属性名-ref=""
3.步骤三:测试
<bean id="person" class="com.chenyanbin.spring.demo.person" p:pname="隔壁老王" p:car2-ref="car2" /> <bean id="car2" class="com.chenyanbin.spring.demo.car2" />
依赖注入不同类型的属性(基于xml)
简单类型(value)
<bean id="userservice" class="com.chenyanbin.spring.service.userserviceimpl"> <property name="id" value="1"></property> <property name="name" value="zhangsan"></property> </bean>
引用类型(ref)
ref就是reference的缩写,是引用的意思
<bean id="userservice" class="com.chenyanbin.spring.service.userserviceimpl"> <property name="userdao" ref="userdao"></property> </bean> <bean id="userdao" class="com.chenyanbin.spring.dao.userdaoimpl"></bean>
集合类型(数组)
1.如果是数组或者list集合,注入配置文件的方式是一样的
<bean id="collectionbean" class="com.chenyanbin.demo5.collectionbean"> <property name="arrs"> <list> <!--如果集合内是简单类型,使用value子标签,如果是pojo类型,则使用bean标签--> <value>张三</value> <value>李四</value> <value>王五</value> </list> </property> </bean>
2.如果是set集合,注入的配置文件方式如下:
<property name="sets"> <set> <!--如果集合内是简单类型,使用value子标签,如果是pojo类型,则使用bean标签--> <value>哈哈</value> <value>喜喜</value> </set> </property>
3.如果是map集合,注入的配置方式如下
<property name="map"> <map> <entry key="老王" value="18" /> <entry key="王五" value="19" /> </map> </property>
4.如果是properties集合的方式,注入的配置如下:
<property name="pro"> <props> <prop key="username">root</prop> <prop key="userpassword">123</prop> </props> </property>
spring ioc和di基于注解使用
学习基于注解的ioc配置,大家脑海里首先得有一个认识,即注解配置和xml配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。
关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式都需要掌握。
在讲解注解配置时,采用上一章节的案例,把spring的xml配置内容改为使用注解逐步实现。
ioc注解使用
第一步:spring配置文件中,配置context:component-scan标签
第二步:类上面加上注解@component,或者它的衍生注解@controller、@service、@repository
常用注解
ioc注解(创建对象)
相当于:<bean id="" class=""></bean>
@component注解
作用: 把资源让spring来管理。相当于在xml中配置一个bean。 属性: value:指定bean的id。 如果不指定value属性,默认bean的id时当前类的类型。首字母小写。
@controller、@service、@repository注解
他们三个注解都是针对@component的衍生注解 他们的作用及属性都是一摸一样的。他们只不过是提供了更加明确的语义化。 @controller:一般用于表现层的注解。 @service:一般用于业务层的注解。 @repository:一般用于持久层的注解。 细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。
di注解(依赖注入)
相当于:<property name="" ref="">
@autowired
默认按类型装配(bytype)
这个注解是spring自身的
默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@autowired(required=false)
如何我们想使用名称装配可以结合@qualifier注解进行使用
@qualifier
在自动按照类型注入的基础之上,再按照bean的id注入。
它在给字段注入时不能独立使用,必须和@autowire一起使用;但是给方法参数注入时,可以单独使用。
@resource
默认按照名称(byname)进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查询,当找不到域名称匹配的bean时才按照类型进行装配。
但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
推荐使用@resource注解,因为这个注解是属于j2ee的,减少了与spring的耦合。这样代码看起来就比较优雅。
相当于:<property name="" value="">
@value
给基本类型和string类型注入值
可以使用占位符获取属性文件中的值。
@value("${name}") //name是properties文件中的key
改变作用范围
@scope
相当于:<bean id="" class="" scope="">
作用:
指定bean的作用范围
属性:
value:指定范围的值
取值:singleton prototype request session globalsession
和生命周期相关
相当于:<bean id="" class="" init-method="" destroy-method="">
@postconstruct和@predestroy
关于注解和xml的选择问题
注解的优势:
配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
xml的优势:
修改时,不用改源码。不涉及重新编译和部署。
spring管理bean方式的比较
spring的纯注解配置
到这里,基于注解的ioc配置已经完成,但是大家都发现了一个问题:我们依然离不开spring的xml配置文件,那么能不能不写这个applicationcontext.xml,所有配置都用注解来实现呢?
需要注意以下,我们选择那种配置的原则是简化开发和配置方便,而非追求某种技术。
待改造的问题
想一想能不能将以下这些bean的配置都从xml中去掉,并且最终将xml也去掉。
如果可以,那么我们就可以脱离xml配置了。
- 注解扫描配置(能不能去掉)
<!--开启注解并扫描指定包中带有注解的类--> <context:component-scan base-package="com.chenyanbin.spring.service" />
- 非自定义的bean配置(比如:sqlsessionfactory和basicdatasource配置)
<bean id="sqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean"> <property name="datasource" value="datasource"></property> </bean>
- 去掉xml后,如何创建applicationcontext
之前创建applicationcontext都是通过读取xml文件进行创建的。 applicationcontext context=new classpathxmlapplicationcontext("beans.xml");
新的注解
@configuration
介绍:
从spring3.0,@configuration用于定义配置类,可替换xml配置文件
相当于<beans>根标签
配置类内部包含有一个或多个被@bean注解的方法,这些方法将会被annotationconfigapplicationcontext或annotationconfigwebapplicationcontext类进行扫描,并用于构建bean定义,初始化spring容器。
属性:
value:用于指定配置类的字节码
示例代码:
@configuration public class springconfiguration { //spring容器初始化时,会调用配置类的无参构造函数 public springconfiguration(){ system.out.println(“容器启动初始化。。。”); } }
@bean
介绍:
@bean标注在方法上(返回某个实例的方法),等价于spring配置文件中的<bean>
作用:
注册bean对象
主要用来配置非自定义的bean,比如druiddatasource、sqlsessionfactory
属性:
name:给当前@bean注解方法创建的对象指定一个名称(即bean的id)。
如果不指定,默认与标注的方法名相同
@bean注解默认作用域为单例singleton作用域,可通过@scope("prototype")设置为原型作用域;
示例代码:
public class springconfiguration { //spring容器初始化时,会调用配置类的无参构造函数 public springconfiguration(){ system.out.println(“容器启动初始化。。。”); } @bean @scope(“prototype”) public userservice userservice(){ return new userserviceimpl(1,“张三”); } }
@componentscan
介绍:
相当于context:component-scan标签
组件扫描器,扫描@component、@controller、@service、@repository注解的类。
该注解是编写在类上面的,一般配合@configuration注解一起使用。
属性:
basepackages:用于指定要扫描的包
value:和basepackages作用一样
示例代码:
bean类(service类):
@service public class userserviceimpl implements userservice { @override public void saveuser() { system.out.println("保存用户 service实现"); } }
配置类:
@configuration @componentscan(basepackages="com.kkb.spring.service") public class springconfiguration { public springconfiguration() { system.out.println("容器初始化..."); } // @bean // @scope("prototype") // public userservice userservice() { // return new userserviceimpl(1,"张三"); // } }
@propertysource
介绍
加载properties配置文件
编写在类上面
相当于 context:property-placeholder 标签
属性
value[]:用于指定properties文件路径,如果在类路径下,需要写上classpath
示例代码
配置类:
@configuration @propertysource(“classpath:jdbc.properties”) public class jdbcconfig { @value("${jdbc.driver}") private string driver; @value("${jdbc.url}") private string url; @value("${jdbc.username}") private string username; @value("${jdbc.password}") private string password; /** *创建一个数据源,并存入 spring 容器中 *@return */ @bean(name="datasource") public datasource createdatasource() { try { combopooleddatasource ds = new combopooleddatasource(); ds.setdriverclass(driver); ds.setjdbcurl(url); ds.setuser(username); ds.setpassword(password); return ds; } catch (exception e) { throw new runtimeexception(e); } } }
properties文件:
jdbc.driver=com.mysql.jdbc.driver jdbc.url=jdbc:mysql:///spring jdbc.username=root jdbc.password=root
问题:
当系统中有多个配置类时怎么办?想一想之前使用xml配置的时候时如何解决该问题的。
@import
介绍
用来组合多个配置类
相当于spring配置文件中的import标签
在引入其他配置类时,可以不用再写@configuration注解。当前,写上也没问题。
属性
value:用来指定其他配置类的字节码文件
示例代码
@configuration @componentscan(basepackages = "com.kkb.spring") @import({ jdbcconfig.class}) public class springconfiguration { } @configuration @propertysource("classpath:jdbc.properties") public class jdbcconfig{ }
通过注解获取容器
- java应用(annotationconfigapplicationcontext)
applicationcontext context = new annotationconfigapplicationcontext(springconfiguration.class); userservice service = context.getbean(userservice.class); service.saveuser();
- web应用(annotationconfigwebapplicationcontext)
<web-app> <context-param> <param-name>contextclass</param-name> <param-value> org.springframework.web.context. support.annotationconfigwebapplicationcontext </param-value> </context-param> <context-param> <param-name>contextconfiglocation</param-name> <param-value> com.kkb.spring.test.springconfiguration </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.contextloaderlistener </listener-class> </listener> </web-app>
spring分模块开发
分模块开发场景描述
表现层:spring配置文件,只想管理表现层的bean
业务层:spring配置文件,只想管理业务层的bean,并且进行事务控制
持久层:spring配置文件,只想管理持久层的bean,并且还有需要管理数据源的bean
为了方便管理项目中不同层的bean对象,一般都是将一个spring配置文件,分解为多个spring配置文件。
分解之后的spring配置文件如何一起被加载呢?
一种就是同时指定多个配置文件的地址一起加载
另一种就是:定义一个import.xml文件,通过import标签将其他多个spring配置文件导入到该文件中,tomcat启动时只需要加载import.xml就可以。
ioc和di总结
@autowired注解,它是如何生效的?autowiredbeanpostprocessor类
ioc和di使用
xml配置方式
注解+xml配置方式
@component
@controller
@service
@repository
@autowired
@resource
纯注解方式
@configuration
@bean
@componentscan
@propertysource
@import
spring整合junit
单元测试问题
在测试类中,每个测试方法都有以下两行代码:
applicationcontext context = new classpathxmlapplicationcontext("applicationcontext.xml"); userservice service1 = context.getbean(userservice.class);
我们使用单元测试要测试的是业务问题,以上两端代码明显不是业务代码。
但是这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
解决思路分析
针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建spring容器,我们就无需手动创建了,问题也就解决了。
但紧接的问题就是juit它本身不认识spring,更无法帮助创建spring容器了,不过好在junit给我们暴露一个注解(@runwith),可以让我们替换掉它的运行器。
这时,我们需要依靠spring框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需告诉它配置文件在哪就行了。
具体实现
添加依赖
添加spring-test包即可
通过@runwith注解,指定spring的运行器
spring的运行器是springjunit4classrunner
通过@contextconfiguration注解,指定spring运行器需要的配置文件路径
通过@autowired注解给测试类中的变量注入数据
spring aop原理分析
aop介绍
什么是aop?
在软件业,aop为aspect oriented programming的缩写,意为:面向切面编程
aop是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
aop最早由aop联盟的组织提出的,制定了一套规范,spring将aop思想引入到框架中,必须遵守aop联盟的规范
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
aop是oop的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容,是函数式编程的一种衍生范式
利用aop可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
aop的作用及优势是什么?
作用:
aop采用横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
在程序运行期间,不修改源码对已有方法进行增强。
将业务逻辑和系统处理的代码(关闭连接、事务管理、操作日志记录)解耦。
优势:
- 减少重复代码
- 提高开发效率
- 维护方便
aop相关术语介绍
- joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持类型的连接点
- pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义
- advice(通知/增强):所谓通知是指拦截到joinpoint之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- introduction(引介):引介时一种特殊的通知在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或field
- target(目标类):代理的目标对象
- weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程
- proxy(代理):一个类被aop织入增强后,就产生一个结果代理类
- aspect(切面):是切入点和通知的结合,在以后的开发中编写和配置的
aop实现指aspectj(了解)
aspectj是一个java实现的aop框架,它能够对java代码进行aop编译(一般在编译期进行),让java代码具有aspectj的aop功能(当然需要特殊的编译器)
可以这样说aspectj是目前实现aop框架中最成熟,功能最丰富的语言,更幸运的是,aspectj与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用非常容易。
了解aspectj应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程。
对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往时通过动态代理技术完成的。如java jdk的动态代理(proxy,底层通过反射实现)或者cglib的动态代理(底层通过继承实现),spring aop采用的就是基于运行时增强的代理技术。
aspectj采用的就是静态织入的方式。aspectj主要采用的是编译期织入,这个期间使用aspectj的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
spring aop实现原理分析
spring aop是通过动态代理技术实现的,而动态代理是基于反射设计的。
动态代理技术的实现方式有两种:基于接口的jdk动态代理和基于继承的cglib动态代理。
jdk动态代理
目标对象必须实现接口
使用proxy类来生成代理对象的一些代码如下
/** * 使用jdk的方式生成代理对象 * @author administrator */ public class myproxyutils { public static userservice getproxy(final userservice service) { // 使用proxy类生成代理对象 userservice proxy = (userservice) proxy.newproxyinstance( service.getclass().getclassloader(), service.getclass().getinterfaces(), new invocationhandler() { // 代理对象方法一执行,invoke方法就会执行一次 public object invoke(object proxy, method method, object[] args) throws throwable { if("save".equals(method.getname())){ system.out.println("记录日志..."); // 开启事务 } // 提交事务 // 让service类的save或者update方法正常的执行下去 return method.invoke(service, args); } }); // 返回代理对象 return proxy; } }
cglib动态代理
目标对象不需要实现接口
底层是通过继承目标对象产生代理子对象(代理子对象中继承了目标对象的方法,并可以对该方法进行增强)
public static userservice getproxy(){ // 创建cglib核心的类 enhancer enhancer = new enhancer(); // 设置父类 enhancer.setsuperclass(userserviceimpl.class); // 设置回调函数 enhancer.setcallback(new methodinterceptor() { @override public object intercept(object obj, method method, object[] args, methodproxy methodproxy) throws throwable { if("save".equals(method.getname())){ // 记录日志 system.out.println("记录日志了..."); } return methodproxy.invokesuper(obj, args); } }); // 生成代理对象 userservice proxy = (userservice) enhancer.create(); return proxy; }
spring aop使用
spring aop开发需要明确的事情
a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公司代码抽取出来,制作成通知。(开发阶段最后在做):aop编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:aop编程人员来做。
b、运行阶段(spring框架完成的)
spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
编写目标类
编写接口和实现类(目标对象)
userservice接口
userserviceimpl实现类
配置目标类,将目标类交给spring ioc容器管理
基于aspectj的xml实现
实现步骤
pom.xml
<!-- 基于aspectj的aop依赖 --> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-aspects</artifactid> <version>5.0.7.release</version> </dependency> <dependency> <groupid>aopalliance</groupid> <artifactid>aopalliance</artifactid> <version>1.0</version> </dependency>
编写通知(增强类,一个普通的类)
配置通知,将通知类交给spring ioc容器管理
配置aop切面
切入点表达式
execution([修饰符] 返回值 包名.类名.方法名(参数))
execution:必须有
修饰符:可省略
返回值类型:必须要,但是可以使用*通配符
包名:
多级包之间使用.分割
包名可以使用*代替,多级包名可以使用多个*代替
如果想省略中间的报名可以使用..
类名:
可以使用*代替
也可以写成*daoimpl
方法名:
也可以使用*代替
也可以写成add*
参数:
参数使用*代替
如果有多个参数,可以使用..代替
通知类型
通知类型(五种):前置通知、后置通知、最终通知、环绕通知、异常抛出通知。
前置通知:
执行时机:目标对象方法之前执行通知
配置文件:<aop:before method="before" pointcut-ref="mypointcut" />
应用场景:方法开始时可以进行校验
后置通知:
执行时机:目标对象方法之后执行通知,有异常则不执行了
配置文件:<aop:after-returning method="afterreturning" pointcut-ref="mypointcut" />
应用场景:可以修改方法的返回值
最终通知:
执行时机:目标对象方法之后执行通知,有没有异常都会执行
配置文件:<aop:after method="after" pointcut-ref="mypointcut" />
应用场景:例如像释放资源
环绕通知:
执行时机:目标对象方法之前和之后都会执行。
配置文件:<aop:around method="around" pointcut-ref="mypointcut" />
应用场景:事务、统计代码执行时机
异常抛出通知:
执行时机:在抛出异常后通知
配置文件:<aop:after-throwing method="afterthrowing" pointcut-ref="mypointcut" />
应用场景:包装异常
基于aspectj的注解实现
实现步骤
编写切面类(注意不是通知类,因为该类中可以指定切入点)
配置切面类
<context:component-scan back-package="com.chenyanbin.spring" />
开启aop自动代理
环绕通知注解配置
@around
作用:
把当前方法看成是环绕通知属性。
value:
用于指定切入点表达式,还可以指定切入点表达式的引用。
定义通用切入点
使用@pointcut注解在切面类中定义一个通用的切入点,其他通知可以引用该切入点
不使用xml的配置方式
@configuration
@componentscan(basepackages="com.chenyanbin")
@enableaspectjautoproxy
public class springconfiguration{
}
spring 应用之spring jdbc实现
jdbctemplate类的入门使用
pom.xml
-
- mysql数据库的驱动包
- spring-jdbc.jar
- spring-tx.jar
编写测试代码
@test public void run1(){ // 创建连接池,先使用spring框架内置的连接池 drivermanagerdatasource datasource = new drivermanagerdatasource(); datasource.setdriverclassname("com.mysql.jdbc.driver"); datasource.seturl("jdbc:mysql:///spring "); datasource.setusername("root"); datasource.setpassword("root"); // 创建模板类 jdbctemplate jdbctemplate = new jdbctemplate(datasource); // 完成数据的添加 jdbctemplate.update("insert into t_account values (null,?,?)", "测试",10000); }
spring管理jdbctemplate
步骤一:spring管理内置的连接池
<bean id="datasource" class="org.springframework.jdbc.datasource.drivermanagerdatasource"> <property name="driverclassname" value="com.mysql.jdbc.driver"/> <property name="url" value="jdbc:mysql:///spring_day03"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean>
步骤二:spring管理模板类
<bean id="jdbctemplate" class="org.springframework.jdbc.core.jdbctemplate"> <property name="datasource" ref="datasource"/> </bean>
步骤三:编写测试程序
@runwith(springjunit4classrunner.class) @contextconfiguration("classpath:applicationcontext.xml") public class demo2{ @resource(name="jdbctemplate") private jdbctemplate jdbctemplate; @test public void run(){ jdbctemplate.update("insert into t_account values (null,?,?)", "测试2",10000); } }
spring管理第三方datasource
管理dbcp连接池 * 先引入dbcp的2个jar包 * com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar * com.springsource.org.apache.commons.pool-1.5.3.jar 如果是maven环境,需要填写gav坐标 * 编写配置文件 <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource"> <property name="driverclassname" value="com.mysql.jdbc.driver"/> <property name="url" value="jdbc:mysql:///spring "/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> 管理c3p0连接池 * 先引入c3p0的jar包 * com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar * 编写配置文件 <bean id="datasource" class="com.mchange.v2.c3p0.combopooleddatasource"> <property name="driverclass" value="com.mysql.jdbc.driver"/> <property name="jdbcurl" value="jdbc:mysql:///spring"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean>
使用jdbctemplate完成增删改查操作
增删改查的操作 @runwith(springjunit4classrunner.class) @contextconfiguration("classpath:applicationcontext.xml") public class springdemo { @resource(name="jdbctemplate") private jdbctemplate jdbctemplate; @test // 插入操作 public void demo1(){ jdbctemplate.update("insert into account values (null,?,?)", "冠希",10000d); } @test // 修改操作 public void demo2(){ jdbctemplate.update("update account set name=?,money =? where id = ?", "思雨",10000d,5); } @test // 删除操作 public void demo3(){ jdbctemplate.update("delete from account where id = ?", 5); } @test // 查询一条记录 public void demo4(){ account account = jdbctemplate.queryforobject("select * from account where id = ?", new beanmapper(), 1); system.out.println(account); } @test // 查询所有记录 public void demo5(){ list<account> list = jdbctemplate.query("select * from t_account", new beanmapper()); for (account account : list) { system.out.println(account); } } } class beanmapper implements rowmapper<account>{ public account maprow(resultset rs, int arg1) throws sqlexception { account account = new account(); account.setid(rs.getint("id")); account.setname(rs.getstring("name")); account.setmoney(rs.getdouble("money")); return account; } }
spring dao开发之jdbcdaosupport
1. 步骤一:创建web工程,引入需要的jar包 * ioc的6个包 * aop的4个包 * c3p0的1个包 * mysql的驱动包 * jdbc的2个包 * 整合junit测试包 2. 步骤二:引入配置文件 * 引入配置文件 * 引入log4j.properties * 引入applicationcontext.xml <bean id="datasource" class="com.mchange.v2.c3p0.combopooleddatasource"> <property name="driverclass" value="com.mysql.jdbc.driver"/> <property name="jdbcurl" value="jdbc:mysql:///spring "/> <property name="user" value="root"/> <property name="password" value="root"/> </bean> 3. 步骤三:创建对应的包结构和类 * com.kkb.demo1 * accountservice * accountservlceimpl * accountdao * accountdaoimpl 4. 步骤四:引入spring的配置文件,将类配置到spring中 <bean id="accountservice" class="com.kkb.demo1.accountserviceimpl"> </bean> <bean id="accountdao" class="com.kkb.demo1.accountdaoimpl"> </bean> 5. 步骤五:在业务层注入dao ,在dao中注入jdbc模板(强调:简化开发,以后dao可以继承jdbcdaosupport类) <bean id="accountservice" class="com.kkb.demo1.accountserviceimpl"> <property name="accountdao" ref="accountdao"/> </bean> <bean id="accountdao" class="com.kkb.demo1.accountdaoimpl"> <property name="datasource" ref="datasource"/> </bean> 6. 步骤六:编写dao和service中的方法 public class accountdaoimpl extends jdbcdaosupport implements accountdao { public void outmoney(string out, double money) { this.getjdbctemplate().update("update t_account set money = money = ? where name = ?", money,out); } public void inmoney(string in, double money) { this.getjdbctemplate().update("update t_account set money = money + ? where name = ?", money,in); } } 7. 步骤七:编写测试程序. @runwith(springjunit4classrunner.class) @contextconfiguration("classpath:applicationcontext.xml") public class demo1 { @resource(name="accountservice") private accountservice accountservice; @test public void run1(){ accountservice.pay("冠希", "美美", 1000); } }
spring 应用之事务支持
事务回顾
事务:指的是逻辑上一组操作,组成这个事务的各个执行单元,要么一起成功,要么一起失败!
事务特性
- 原子性
- 一致性
- 隔离性
- 持久性
如果不考虑隔离性,引发安全性问题
- 读问题
- 脏读
- 不可重复读
- 虚读
如何解决安全性问题
读问题解决,设置数据库隔离级别
spring 框架的事务管理相关的类和api
- platformtransactionmanager接口:平台事务管理器(真正管理事务的类)。该接口有具体的实现类,根据不同持久层框架,需要选择不同的实现类!
- transactiondefinition接口:事务定义信息(事务的隔离级别,传播行为,超时,只读)
- transactionstatus接口:事务的状态
- 总结:上述对象之间的关系,平台事务管理器真正管理事务对象,根据事务定义的信息 transactiondefition 进行事务管理,在管理事务中产生一些状态,将状态记录到 transactionstatus中
- plaformtransactionmanager接口中实现类和常用的方法
- 接口的实现类
- 如果使用的 spring的jdbc模板或者mybatis框架,需要选择 datasourcetransactionmanager实现类
- 如果使用的是hibernate框架,需要选择hibernatetransactionmanager实现类
- 该接口的常用方法
- void commit(transactionstatus status)
- transactionstatus gettransaction(transactiondefinition definition)
- void rollback(transactionstatus status)
- 接口的实现类
- transactiondefinition
-
- 事务隔离级别的常量
-
- static int isolation_default --采用数据库的默认隔离级别
- static int isolation_read_uncommitted
- static int isolation_read_committed
- static int isolation_repeatable_read
- static int isolation_serializable
-
- 事务的传播行为常量(不用设置,使用默认值)
- 先解释什么是事务的传播行为:解决的是业务层之间的方法调用
- propagation_required(默认值) --a中有事务,使用a中的事务,如果没有,b就会开启一个新的事务,将a包含进来.(保证a,b在同一个事务中),默认值
- propagation_supports --a中有事务,使用a中的事务。如果a中没有事务,那么b也不使用事务
- propagation_mandatory --a中有事务,使用a中事务,如果a没有事务,抛出异常
- propagation_requires_new --a中有事务,将a中的事务挂起,b创建一个新的事务。(保证a,b没有在一个事务中)
- propagation_not_supported --a中有事务,将a中的事务挂起
- propagation_never --a中有事务,抛出异常、
- propagation_nested --嵌套事务,当a执行之后,就会在这个位置设置一个保存点,如果b没有问题,执行通过,如果b出现异常,运行客户根据需求回滚(选择回滚到保存点或者是最初始状态)
- 事务隔离级别的常量
spring 框架事务管理的分类
- spring 的编程式事务管理(不推荐使用)
- 通过手动编写代码的方式完成事务的管理(不推荐)
- spring的声明式事务管理(底层采用aop的技术)
- 通过一段配置的方式完成事务的管理
编程式事务管理(了解)
1. 说明:spring为了简化事务管理的代码:提供了模板类 transactiontemplate,所以手动编程的方式来管理事务,只需要使用该模板类即可!! 2. 手动编程方式的具体步骤如下: 1. 步骤一:配置一个事务管理器,spring使用platformtransactionmanager接口来管理事务,所以咱们需要使用到他的实现类!! <!-- 配置事务管理器 --> <bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource"/> </bean> 2. 步骤二:配置事务管理的模板 <!-- 配置事务管理的模板 --> <bean id="transactiontemplate" class="org.springframework.transaction.support.transactiontemplate"> <property name="transactionmanager" ref="transactionmanager"/> </bean> 3. 步骤三:在需要进行事务管理的类中,注入事务管理的模板 <bean id="accountservice" class="com.itheima.demo1.accountserviceimpl"> <property name="accountdao" ref="accountdao"/> <property name="transactiontemplate" ref="transactiontemplate"/> </bean> 4. 步骤四:在业务层使用模板管理事务: // 注入事务模板对象 private transactiontemplate transactiontemplate; public void settransactiontemplate(transactiontemplate transactiontemplate) { this.transactiontemplate = transactiontemplate; } public void pay(final string out, final string in, final double money) { transactiontemplate.execute(new transactioncallbackwithoutresult() { protected void dointransactionwithoutresult(transactionstatus status) { // 扣钱 accountdao.outmoney(out, money); int a = 10/0; // 加钱 accountdao.inmoney(in, money); } }); }
声明式事务管理
声明式事务管理又分成两种方式
-
- 基于aspectj的xml方式(重点掌握)
- 基于aspectj的注解方式(重点掌握)
事务管理之基于aspectj的xml方式(重点掌握)
准备转账环境
业务层:
accountservice
accountserviceimpl
持久层
accountdao
accountdaoimpl
spring配置
单元测试代码:
配置事务管理的aop
平台事务管理器:datasourcetransactionmanager
事务通知:<tx:advice id="" transaction-manager="" />
aop配置
<aop:config>
<aop:advisor advice-ref="" pointcut="" />
</aop:config>
事务管理之基于aspectj的注解方式(重点掌握)
service类上或者方法上加注解:
类上加@transactional:表示该类中所有方法都被事务管理
方法上加@transactional:表示只有改方法被事务管理
开始事务注解: