Spring
1 概述
1.1 spring是什么
spring是分层的JavaSE/EE应用full-stack轻量级开源框架,以IoC(反转控制)和AOP(面向切面编程)为内核,提供了展现层springMVC和持久层JDBC一级业务层事务管理等众多的企业级应用技术。
1.2 spring优势
方便解耦,简化开发
AOP编程的支持
声明式事务的支持
方便程序的测试
方便集成各种优秀框架
降低javaEE API的使用难度
1.3 体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PMsruvE-1602773282742)(D:\Study\笔记\图\image-20200910171406191.png)]
2 IoC
2.1 程序的耦合
耦合:程序间的依赖关系
包括:类之间的依赖,方法间的依赖
解耦:降低程序间的依赖关系
实际开发中应该做到:编译期不依赖,运行时才依赖
解耦的思路:
第一步,用反射来创建对象,避免使用new关键字
第二部,通过读取配置文件,来获取要创建对象的全限定类名
2.2 概念
控制反转:把创建对象的权利交给框架,是框架的重要特征,IoC并非面向对象编程专用术语,包括依赖注入和依赖查找
2.3 作用
削减计算机程序的耦合(解除代码中的依赖关系)
3 Bean
把对象的创建交给spring来管理
3.1 三种创建方式
<!--创建bean的三种方式-->
<!--
第一种方式:使用默认构造函数创建
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--
第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
-->
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
<!--
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
-->
<bean id="instanceFactory" class="com.itheima.factory.StaticFactory"></bean>
3.2 bean的作用范围
<!--
bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值:常用前两个
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
-->
<bean id = "accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
global-session效果图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsQmlAx1-1602773282743)(D:\Study\笔记\图\image-20200914190118765.png)]
3.3 bean的生命周期
<!--
bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着
死亡:当对象长时间不用,且没有别的对象引用时,有java的垃圾回收器回收
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy"></bean>
4 Spring中的依赖注入
依赖注入:
Dependency Injection
IoC的作用:
降低程序间的耦合(依赖关系)
依赖关系的管理:
以后都交给了Spring来维护
在当前类中需要用到其他类的对象,由Spring为我们提供,我们只需要在配置文件中说明依赖关系的维护称之为依赖注入
依赖注入:
能注入的数据有三类:
基本类型和String
其他bean类型(在配置文件中或注解配置过的bean)
复杂类型/集合类型
注入的方式有三种:
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供
4.1 构造函数注入
<!--
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性
type:用于指定要注入的数据类型,该数据类型也是构造函数中的某个或者某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始的
name:用于指定给构造函数中指定名称的构造函数赋值
====================以上三个用于指定给构造函数中哪个参数赋值====================
value:用于提供基本类型和String的数据
ref:用于其他的bean类型数据,它指的就是在springIoC核心容器中出现过的bean对象
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:改变了bean对象的实例化方式,使我们在创建对象的时候后,如果不用到这些数据,也必须提供
实际操作时除了无可避免必须使用该方法,其余时间均不用
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="birth"></constructor-arg>
</bean>
<bean id="birth" class="java.util.Date"></bean>
4.2 set方法注入
<!--
涉及的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String的数据
ref:用于其他的bean类型数据,它指的就是在springIoC核心容器中出现过的bean对象
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:如果有某个成员必须有值,则获取对象有可能是set方法没有执行
-->
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="name" value="test"></property>
<property name="age" value="20"></property>
<property name="birthday" ref="birth"></property>
</bean>
<!--
复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签有:
list array set
用于给Map集合结构注入的标签有:
map props
结构相同,标签可以互换
-->
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>A</value>
<value>B</value>
<value>C</value>
</array>
</property>
<property name="myList">
<list>
<value>A</value>
<value>B</value>
<value>C</value>
</list>
</property>
<property name="mySet">
<set>
<value>A</value>
<value>B</value>
<value>C</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="A" value="a"></entry>
<entry key="B" value="b"></entry>
<entry key="C" value="c"></entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="A">a</prop>
<prop key="B">b</prop>
</props>
</property>
</bean>
5 Spring中的注解
5.1 spring中的注解
5.1.1 用于创建对象的
他们的作用就和在xml配置文件中编写一个标签实现的功能是一样的
@Component:
作用:用于吧当前对象存入spring容器中
属性:
value:用于指定bean的id,不写时,默认值是当前类名且首字母改小写
@Controller:一般用在表现层
@Service:一般用在业务层
@Repository:一般用在持久层
以上三个注解的作用和属性与Components是一模一样的
他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
5.1.2 用于注入数据的
他们的作用就和在xml配置文件中的bean标签中写一个标签作用是一样的
@Autowired:
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
如果IoC容器中没有任何bean的类型和要注入的变量类型匹配,则报错
如果IoC容器中有多个类型匹配时,先根据类型来,如果类型相同再根据变量名来找,否则报错
出现位置:
可以是变量上,也可以是方法上
细节:
在使用注解注入时,set方法就不是必须的了
@Qualifier:
作用:在按照类中注入的基础之上再按照名称注入。
它在给类成员注入时不能单独使用,但是给方法参数注入时可以
@Resource:
作用:直接按照bean的id注入,它可以独立使用
属性:name: 用于指定bean的id
以上三个注解都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现
另外,集合类型的注入只能通过XML来实现。
@Value:
作用:用于注入基本类型和String类型的数据
属性:
value:用于指定数据的值。它可以使用spring中的SpEL(SpringEL表达式)
SpEL的写法:$(表达式)
5.1.3 用于改变范围的
他们的作用就和在bean标签中使用scope属性实现的功能是一样的
@Scope:
作用:用于指定bean的作用范围
属性:value:指定范围的取值。
常用取值:singleton(默认) prototype
5.1.4 和生命周期相关的(了解)
他们的作用就和在bean标签中使用init-method和destroy-method的作用是一样的
@PreDestroy
作用:用于指定销毁方法
@PostConstruct
作用:用于指定初始化
5.2 spring中的新注解
Configuration
作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
ComponentScan
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
我们使用此注解就等同于在xml中配置了:
<context:component-scan base-package=“com.itheima”></context:component-scan>
Bean
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:
name:用于指定bean的id。当不写时,默认值是当前方法的名称
细节:
当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
查找的方式和Autowired注解的作用是一样的
Import
作用:用于导入其他的配置类
属性:
value:用于指定其他配置类的字节码。
当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
PropertySource
作用:用于指定properties文件的位置
属性:
value:指定文件的名称和路径。
5.3 Spring整合junit的配置
1、导入spring整合junit的jar(坐标)
2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
@Runwith
3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
@ContextConfiguration
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在地位置
当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
6 AOP
6.1 AOP术语和细节
连接点:业务层中所有的方法
切入点:被动态代理invoke增强的方法
Advice(通知):
//整个invoke方法在执行就是环绕通知
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction(); //前置通知
//2.执行操作
rtValue = method.invoke(accountService, args); //在环绕通知中有明确的切入点调用
//3.提交事务
txManager.commit(); //后置通知
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback(); //异常通知
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release(); //最终通知
}
}
Introduction(引介):一种特殊的通知,在不修改代码的前提下,在运行期为类动态的添加一些方法或者Field
Target(目标对象):代理的目标对象
weaving(织入):是指吧增强应用到目标对象来创建新的代理对象的过程
6.2 Spring中基于xml的AOP配置步骤
6.2.1 基础配置
<!--
1、把通知Bean也交给spring来管理
2、使用aop:config标签表名开始AOP的配置
3、使用aop:aspect标签表明开始配置切面
id属性:给切面提供一个唯一标志
ref属性:指定通知类bean的id
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
前置通知为aop:before
method属性:用于指定logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值:
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包,但是有几级包就需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符*表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法,例如本案例:
* com.itheima.service.impl.*.*(..)
-->
<!-- 配置Logger类-->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!-- 配置AOP-->
<aop:config>
<!-- 配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置-->
<aop:before method="printLog"
pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
6.2.2 各种通知配置
<!-- 配置AOP-->
<aop:config>
<!--
配置切入点表达式,id属性用于指定表达式的唯一表示,expression属性用于指定表达式内容
此标签写在aop:aspect标签内部,只能当前切面使用。
它还可以写在aop:aspect标签前面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!-- 配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知,在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--配置后置通知,在切入点方法正常执行之后执行,它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知,在切入点方法产生异常之后执行,它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知,无论切入点方法是否正常执行,它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!--配置环绕通知,详细的注释看Logger类中-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
6.2.3 关于环绕通知的问题
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理中的环绕通知有明确的切入点方法调用,而我们的代码中没有
* 解决:
* Spring框架为我们提供了一个接口,ProceedingJoinPoint。
* 该接口有一个方法proceed(),此方法就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,
* 在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
* @return
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("前置通知 Logger类中的aroundPrintLog方法开始记录日志了...");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("后置通知 Logger类中的aroundPrintLog方法开始记录日志了...");
return rtValue;
} catch (Throwable throwable) {
System.out.println("异常通知 Logger类中的aroundPrintLog方法开始记录日志了...");
throw new RuntimeException(throwable);
} finally {
System.out.println("最终通知 Logger类中的aroundPrintLog方法开始记录日志了...");
}
}
6.3 Spring中基于注解的配置
6.3.1 xml中的配置
<!--配置Spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置Spring开启注解Aop的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
6.3.2 各种注解及作用
注解:
@Aspect
作用:表示当前类是一个切面类
@Pointcut
作用:声明当前方法表示切入点表达式
属性:表达式的内容,如"execution(* com.itheima.service.impl..(…))"
@Before
作用:声明当前方法为前置通知
@AfterReturning
作用:声明当前方法为后置通知
@AfterThrowing
作用:声明当前方法为异常通知
@After
作用:声明当前方法为最终通知
@Around
作用:声明当前方法为环绕通知
以上五种注解属性均为切入点表达式,可使用@Pointcut声明的方法,但调用时方法必须加()
使用纯注解方法:在配置类上声明@EnableAspectJAutoProxy
6.4 Spring中基于XML的声明式事务控制配置步骤
<!--
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
-->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--
配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--
配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>