Spring
前言
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
Spring特点
- 解耦合,简化开发
- 支持AOP,面向切面编程
- 方便程序测试
- 方便和其他框架结合
- 方便进行事务操作
- 降低了API开发难度
IOC
概述
什么是IOC
- 控制反转,把对象的创建,赋值和对象之间的调用过程,交给spring管理
- 使用IOC的目的是为了降低耦合度
IOC底层原理
- xml解析、工厂模式、反射
- 通过xml解析配置文件,获取bean的id值,和对象所属类class,然后创建工厂类factory,利用class的全限定名称,使用反射技术创建类的对象
- IOC容器实现的两种方式:BeanFactory、ApplicationContext(BeanFactory的子类),前者在开始的时候只会加载xml配置,不会创建对象,后者会在加载xml配置的时候创建所有的对象
- java创建对象的方式有哪些:构造方法、反射、序列化、克隆
- IOC的实现举例:servlet对象——由tomcat根据web.xml中的配置,创建servlet对象,tomcat也称之为容器,可以叫servlet容器。除此之外tomcat中存放的还有listener,filter对象
DI的两种实现方式
- 在spring的配置文件中,使用标签和属性完成,叫做基于xml的di实现,也叫set设值注入
- 在spring中的注解,完成赋值,叫做基于注解的di实现
IOC基于xml配置文件的
DI:依赖注入(dependency injection)是ioc的技术实现,只需要在程序中提供要使用的对象唯一标识的别名就可以,至于对象如何在容器中创建,赋值,查找都由容器内部实现。其底层使用的反射机制。
spring的ioc实现步骤
- 创建maven项目
- 加入maven依赖,spring:5.2.5版本,Junit依赖
- 创建类(接口和他的实现类)和没有使用框架一样,就是普通的类
- 创建xml配置文件,声明类的信息,这些类由spring创建和管理
- 测试spring创建的类
xml注入代码实现
-
set注入:设值注入,spring调用类的set方法,在set方法可以给属性赋值,80%都是使用的set注入
-
构造注入:spring调用类的有参数构造方法,创建对象,在构造方法中完成赋值
-
set注入之简单数据类型赋值:
<bean id="student" class="com.etekcity.domain.Student"> <property name="name" value="alex"></property> <property name="name" value="alex"> <value><![CDATA[<<alexz>>]]></value> </property> <!-- 值包含特殊符号可以使用转义,也可以使用CDATA格式 --> <property name="age" value="20"></property> <property name="age"> <null/> </property> <!-- 设值空值 --> </bean>
-
set注入之引用数据类型赋值
<bean id="student" class="com.etekcity.domain.Student"> <property name="name" value="alex"></property> <property name="age" value="20"></property> <property name="school" ref="school"></property> </bean> <bean id="school" class="com.etekcity.domain.School"> <property name="sname" value="一高"></property> <property name="address" value="渝北"></property> </bean>
-
构造注入方式
<bean id="student" class="com.etekcity.domain.Student"> <constructor-arg name="name" value="tom"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="school" ref="school"></constructor-arg> </bean> <bean id="school" class="com.etekcity.domain.School"> <property name="sname" value="一高"></property> <property name="address" value="渝北"></property> </bean>
-
集合注入方式
<bean id="student" class="com.etekcity.domain.Student"> <property name="name" value="alex"></property> <property name="age" value="20"></property> <property name="map"> <map> <entry key="mapKey" value="mapValue"></value> </map> <property> <!-- 集合属性注入 --> <property name="school" ref="school"></property> </bean> <bean id="school" class="com.etekcity.domain.School"> <property name="sname" value="一高"></property> <property name="address" value="渝北"></property> </bean>
-
自动注入:byName、byType
<!--引用类型的自动注入,spring框架根据某些规则可以给引用类型赋值,不需要你再给引用类型赋值,使用的规则常用的是byName,byType--> <bean id="student" class="com.etekcity.domain.Student" autowire="byName"> <property name="name" value="alex"></property> <property name="age" value="20"></property> <!-- 这里不用配置school引用类型, 通过byName自动注入,自动寻找bean的id和属性名称一样的bean byType,是指java类中引用类型的数据类型和spring配置文件<bean>的class属性值是同源关系,这样的bean能够赋值给引用类型 同源: java类中引用类型的数据类型和spring配置文件<bean>的class属性值是一样的 java类中引用类型的数据类型和spring配置文件<bean>的class属性值父子类关系 java类中引用类型的数据类型和spring配置文件<bean>的class属性值接口和实现类关系 --> </bean> <bean id="school" class="com.etekcity.domain.School"> <property name="sname" value="一高"></property> <property name="address" value="渝北"></property> </bean>
-
使用外部配置文件,创建properties配置文件,以jdbc配置为例
<context:property-placeholder location="classpath:spring.properties"/> <!-- xml中指定properties配置文件类路径 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.user}"></property> <property name="password" value="${jdbc.passwd}"></property> <property name="maxActive" value="${jdbc.maxActive}"></property> </bean>
多配置文件使用方式
使用多配置文件:项目比较大,多人协作开发时,单配置文件十分不方便
<!--
使用多配置文件时,主配置文件中不定义对象bean
语法: <import resource="path">
也是在类路径下寻找,在classes目录下,可以使用通配符,使用通配符时要注意,不能匹配到主配置文件名字,且其他的配置必须和主配置放在同一目录下
-->
<import resource="classpath:student.xml"></import>
<import resource="classpath:student-*.xml"></import>
注解注入代码实现
xml配置文件和注解使用
使用注解的步骤:加入maven依赖,spring-context,在你加入spring-context的同时,间接加入了spring-aop的依赖,使用注解必须使用spring-aop依赖;在类中加入spring的注解(多个不同功能的注解);在spring的配置文件中,加入一个组件扫描器,说明注解在你的项目中的位置
<context:component-scan base-package="com.etekcity.domain"/>
Spring中提供了创建对象的注解有:@Component、@Repository、@Service、@Controller
- @Component:创建对象的,等同标签的功能,属性value就是对象的名称,也就是bean的id值,在spring容器中唯一
- @Repository:用在持久层类的上面,放在到实现类上面,表示创建dao对象,能访问数据库
- @Service:用在业务层类上面,放在service的实现类上,创建service对象,service对象是做业务处理的,可以有事务等功能的
- @Controller:用在控制器的上面:放在控制器类的上面,创建控制器对象能够接受用户提交的参数,显示请求的处理结果
以上三个注解的使用语法和component一样的,都能创建对象,但是这三个注解还有额外的功能:给项目分层,赋予不同的角色
package com.etekcity.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
// 可以省略value,@Component("myStudent")
// 不指定对象名称,只用@Component,默认的对象名称是类名首字母小写
@Component(value = "myStudent")
public class Student {
// 简单类型赋值,使用注解赋值
@Value("alex")
private String name;
@Value("20")
private Integer age;
@Autowired
private School school;
}
Spring中注入属性值的注解有:@Value、@Autowried、@Qualifiler、@Resource(jdk自带的自动注入注解)
// 简单类型赋值,使用注解赋值
@Value("alex")
private String name;
@Value("20")
private Integer age;
/*
@Autowired,spring框架提供的注解,实现引用类型的赋值,spring中通过注解给引用类型赋值,使用的是自动注入的原理,支持byType,byName,@Autowired默认使用的是byType自动注入
*/
@Autowired
private School school;
/*
@Resource,是jdk自带的自动注入注解,默认用的byName,先使用byName自动注入,如果赋值失败,会使用byType自动注入
*/
@Resource(name = "school") // 只是用byName模式,需要指定值
private School school;
@Autowired
@Qualifiler(value="School1") // 根据名称注入,当接口有多个实现类时,使用注解表示使用哪个实现类对象
private School school;
使用纯注解开发模式
去掉xml配置文件:创建一个配置类,使用注解完成相关配置,示例如下:获取交给spring管理的bean
public class App {
public static void main( String[] args ){
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigClass.class);
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}
Bean作用域及生命周期
Spring中有两种类型的bean:普通类型的bean(xml中定义的类型和使用时返回的类型必须一致),工厂bean(实现FactoryBean接口,xml中定义的类型和使用时返回的类型可以不一致)
Spring中bean的作用域
- spring中默认使用的是单例(scope=“singleton”)模式,定义bean的时候可以通过属性scope=“protoType”,设置为多实例模式
- 当实例scope设置为多实例作用域时,并不会在加载配置的时候创建多个实例对象,会在每次调用getBean方法的时候创建
- 除此之外scope的值还有session,request,几乎不用
Spring中bean的生命周期
- 通过构造器(默认使用无参构造器)创建对象
- 对象赋值(依赖注入)
- 对象的前置处理器(对象实现BeanPostProcessor接口,重写前置方法)
- 初始化对象(需要配置初始化方法,通过属性设置 init-method=“对象的初始化方法”)
- 对象的后置处理器(对象实现BeanPostProcessor接口,重写后置方法)
- 使用对象
- 销毁对象(需要配置销毁方法,通过属性设置 destroy-method=“对象的销毁方法”)
AOP
AOP概述
AOP底层是采用的动态代理模式实现的,代理模式又分为,jdk动态代理,cglib动态代理。面向切面编程,基于动态代理,是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。
怎么面向切面编程,其步骤是
- 需要分析项目功能时,找出切面
- 合理的安排切面的执行时间
- 合理的安全的切面执行过程,在哪个类那个方法增加增强功能
AOP的技术实现
Spring框架实现:spring在内部实现了aop规范,能做aop的工作,主要在事务处理时使用aop,我们项目开发中很少使用spring的aop实现,因为spring的aop比较笨重
AspectJ:一个开源的专门做aop的框架,spring框架中集成了aspectJ框架。AspectJ框架实现aop有两种方式:使用xml配置文件、使用注解,通常使用注解方式
面向切面编程术语
- joinpoint:连接点,类中那些方法可以被增强,这些方法称为连接点
- pointcut:切入点,类中实际被增强的方法
- 目标对象:被增强方法所属的类
- advice:通知,实际被增强的逻辑代码
- aspect:切面,是一个动作,指把通知应用到切入点的过程
AspectJ切入点表达式
用于指定切入点,表达式的原型是
- execution(modifiers-pattern?(方法的修饰符) res-type-pattern(方法的返回类型)declaring-type-pattern?(包名类名)name-pattern(param-pattern)(方法名(参数类型和参数个数)throws-pattern(方法抛出的异常))
- 切入点表达式中带问号的是可选的,可以没有,简写为:execution(访问权限修饰符 方法返回值类型 方法声明(参数列表) 异常类型)
- 切入点表达式要匹配的对象就是目标方法的方法名,所以execution表达式中明显就是方法的签名,注意,表达式中黑色文字表示可以省略部分,各部分间用空格分开,在其中可以使用以下符号
* 0至多个任意字符
.. 用在方法参数中,表示任意多个参数,用在包名后,表示当前包及其子包路径
+ 用在类名后,表示当前类及其子类,用在接口后,表示当前接口及其实现类
AspectJ框架中5个注解
- @Before,目标方法调用之前执行切面功能
- @AfterReturing,目标方法调用且返回结果后执行切面功能
- @Around,目标方法调用前和调用后,执行切面功能
- @AfterThrowing,目标方法调用后有异常时后执行,可以监控目标方法,有异常时切面功能可以是发送短信,邮件这些
- @After,最终通知,在目标方法调用之后执行,不管是否有异常,总是会执行的,一般做清除操作
- 如果目标类没有实现接口,spring会默认调用cglib动态代理,如果有接口,又想使用cglib,则可以在主配置文件中把proxy-target-class值设置为true
<aop:aspectj-autoproxy proxy-target-class="true"/>
AspectJ代码实现举例
- 创建接口及其实现类
- xml配置开启AspectJ自动生成代理对象
<aop:aspectj-autoproxy proxy-target-class="true"/>
使用完全注解,设置配置类
@Configuration
@ComponentScan(basePackages = {"com.etekcity.cloud.domain"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigClass {
...
}
创建切面,使用注解配置
@Aspect // @Aspect: 是aspect框架中的注解,表示当前类是切面类,定义在类的上面
public class BeforeAspect {
/*
定义方法,方法是实现切面功能的
方法的定义要求:
1.公共方法public
2.方法没有返回值
3.方法名称自定义
4.方法可以有参数,也可以没有参数,如果有参数,有几个参数类型可以使用, JoinPoint
@Before(value="切入点表达式")
增强功能方法的参数:
JoinPoint:指实际被增强的业务方法
作用:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参
如果你的切面功能中需要用到方法的信息,就加入joinpoint,这个joinpoint参数的值是由框架赋予,
必须是第一个位置的参数
*/
@Before(value = "execution(public void *..SomeServiceImpl.doSome(..))")
public void beforeAspect(JoinPoint joinPoint){
System.out.println("方法的签名:" + joinPoint.getSignature());
System.out.println("方法的名称:" + joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
for(Object arg : args){
System.out.println(arg);
}
System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
}
}
多个相同切点的抽取,减少重复的切入点表达式,定义一个切入点方法,加上@Pointcut注解
/*
@Pointcut: 定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,可以复用的,可以用@Pointcut
属性:value,切入点表达式
*/
@Pointcut(value = "execution(public String *..SomeServiceImpl.doOther(..))")
private void pt(){}
// 使用
@After(value = "pt()")
public void afterAdvice(){
// 增强功能代码
}
多个增强类对同一个业务方法进行增强,设置增强类的优先级,在增强类上面设置优先级,使用注解@Order(数字类型值,数字越小,优先级越高)
@Aspect
@Order(1)
public class BeforeAspect {
...
}
JdbcTemplate
说明
- 了解spring自带的JdbcTemplate,操作数据库的方式就可以了,实际使用的都是框架,mybatis
- xml文件中配置DataSource对象,JdbcTemplate对象
- 创建Dao接口和实现类,实现类中注入JdbcTemplate对象,操作数据库,增删改查
- 创建Service类,注入Dao对象,调用Dao对象具体方法
- 批量操作:batchxxx,传入的集合参数
事务
事务概述
- 什么是事务:事务是指一组sql语句的集合操作,要么都执行成功,要么都执行失败,事务具有原子性
- 什么时候使用事务:当操作涉及到多个表或者是多个sql语句的操作,需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的
事务特性
- A,原子性:最小单元,不可再分割
- C,一致性:一个事务执行时,要么同时成功,要么同时失败
- I,隔离性:不同事务是相互隔离的,互不影响
- D,持久性:事务一旦结束(commit提交,rollback),不可以再回滚
Spring处理事务的模型,使用的步骤都是固定的,把事务使用的信息提供给Spring就可以了
事务管理器
- 事务管理器可以代替你完成commit,rollback,事务管理器是一个接口和他的众多实现类,接口:PlatformTransactionManager,定义了事务重要方法commit,rollback,实现类:Spring把每一种数据库访问技术对应的事务处理类都创建好了
- mybatis访问数据库—DataSourceTransactionManager
- hibernate访问数据库—HibernateTransactionManager
- 事务提交事务,回滚事务的时机:当业务方法执行成功,没有异常时,事务管理器commit、当业务方法抛出运行时异常,事务管理器rollback、当业务方法抛出非运行时异常,主要是受检异常,提交事务commit
Spring中事务技术实现
Spring中事务的技术实现有两种,分为编程式和声明式,编程式是自己写代码实现事务,一般不用,通常都是使用声明式,声明式由可以分为使用xm配置,和注解两种方式
注解方式
- 创建事务管理器对象
- 开启事务注解驱动,告诉Spring框架,我要使用注解的方式管理事务
- Spring给业务加入事务:在你的业务方法执行之前,先开启事务,在业务方法之后提交事务或者回滚事务,使用的是AspectJ的环绕通知
- 在方法上面加入注解@Transactional,这个注解也可以加载类上面,表示类中所有方法都使用事务
<!--使用spring的事务管理-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解驱动,告诉Spring使用注解管理事务,创建代理对象-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
/*
给buy方法增加事务的功能,@Transactional注解,buy方法要是public的
一下的注解参数是默认的,默认抛出运行时异常,回滚事务, @Transactional
如果抛出的异常类型在rollbackFor中,一定会回滚,不区分是否是运行一样
*/
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为,必须要事务
isolation = Isolation.DEFAULT, // 隔离级别
readOnly = false, // 只读
rollbackFor = {NullPointerException.class, NotEnoughException.class} // 表示发生指定的异常时一定回滚
)
@Transactional注解参数说明
- propagation:事务的传播行为,是针对多事务方法的调用,Spring中有7种事务传播行为,常用的是propagation_required、propagation_supports、propagation_requires_new
- isolation:事务隔离级别,有4个值
- timeout:事务的超时时间:表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚,单位是秒,整数值,默认是-1
- readOnly:是否只读,默认false
- rollbackFor:出现了哪些异常时进行回滚
- noRollbackFor:出现了哪些异常时不进行回滚
xml配置方式
xml配置文件方式,适合大型项目,有很多的类,方法,需要大量的配置事务,使用AspectJ框架功能,在spring配置文件中声明类,方法需要的事务,这种方式使业务和事务完全分离,实现步骤为:
- 加入依赖
- 声明事务管理器对象
- 配置通知
- 配置切入点和切面
<!--1.使用AspectJ框架的事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.声明业务方法的事务属性-->
<tx:advice id="advice" transaction-manager="transactionManager">
<!--事务属性-->
<tx:attributes>
<!--给具体的方法配置事务,name:完整的方法名称,不带有饱和类,方法可以使用通配符,*表示任意字符-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="com.etekcity.excep.NotEnoughException,java.lang.NullPointerException"
read-only="false"/>
</tx:attributes>
</tx:advice>
<!--上面配置的方法,没有指定那个包下的方法,如果需要指定包和类需要配置如下-->
<aop:config>
<!--
id:切入点表达式名称,唯一
expression:切入点表达式
-->
<aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))"/>
<!--关联方法-->
<aop:advisor advice-ref="advice" pointcut-ref="servicePointcut"></aop:advisor>
</aop:config>
使用全注解模式,设置配置类
@Configuration
@ComponentScan(basePackages = {"com.etekcity.cloud.domain"})
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启AspectJ框架,使用AOP
@EnableTransactionManagement // 开启事务
public class ConfigClass {
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://user");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
return druidDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
Spring 5新特性
概述
-
Spring 5框架最低支持Java 8 版本,运行时兼容Java 9
-
Spring 5框架自带了通用的日志,移除了log4j,采用了新版的log4j2
-
Spring 5框架核心容器支持@Nullable注解
- 用在属性,表示属性值可以为空
- 用在方法,表示方法返回值可以为空
- 用在方法参数,表示方法参数值可以为空
-
Spring 5框架核心容器支持函数式风格(也就是lambda表达式)
-
JUnit 5单元测试框架
@RunWith(SpringJUnit4ClassRunner.class) // junit 4 @ContextConfiguration(classes = ConfigClass.class) @ExtendWith(SpringExtension.class) // junit 5 @SpringJUnitConfig(locations = "xxx.xml") public class JUnitTest { }
Spring 5框架新功能
SpringWebflux介绍
- SpringWebflux是Spring 5新添加的模块,用于web开发的,功能和SpringMVC类似的,Webflux 使用的是当前一种比较流行的响应式编程框架
- 传统web框架,比如SpringMVC,这些基于Servlet容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在Servlet 3.1以后才支持,核心是基于Reactor的相关API实现的。
什么是异步非阻塞
- 异步和同步针对调用者:调用发送请求,如果等待对方回应之后才继续做其他事情,称之为同步,反之,如果发送请求后不等待对方响应就去做其他事情,称之为异步
- 阻塞和非阻塞针对被调用者:被调用着收到请求之后,如果被调用者执行完请求任务才给出响应,称之为阻塞,反之,如果被调用者接收到请求后,立即给出响应,然后才执行请求任务,称之为非阻塞
Webflux特点
- 第一是非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
- 第二是函数式编程:Spring 5 框架基于Java 8,Webflux 使用Java8函数式编程方式实现路由请求
响应式编程
- 响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便的表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
- 响应式编程用到的是观察者设计模式,用到的两个类是:Observer、Observable