Spring 基础
# Spring 优势
方便解耦,简化开发(bean)
- 通过spring提供的
IOC容器 可以将对象间的依赖关系交由spring进行控制。 - IOC :使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
- 通过spring提供的
AOP编程支持- 通过Spring的AOP功能,方便的面向切面编程,许多不容易用传统OOP(面向对象编程)实现的功能都可以通过AOP实现。
声明式事务的支持
- 通过声明式灵活的进行事务管理,提高开发效率和质量。
方便程序的测试 (Spring-junit)
- 可以用非容器依赖的编程方式进行几乎所有的测试工作。
方便集成各种优秀框架(Spring-mybatis)
降低JavaEE API的使用难度(JDBC模板)
- Spirng对JavaEE API 进行了薄薄的封装层,提供给用户一个模板来使用。
Java 源码是经典学习范例
- Spring 的源代码设计精妙,值得学习。
# Spring 体系结构

# Spring 开发步骤
导入坐标。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency>创建Bean,Dao(Mapper)目录下的接口加实现类。
创建applicationContext.xml,并进行相关配置。
<bean id="user" class="com.diana.Dao_Mapper.impl.UserImpl"></bean>创建ApplicationContext对象,使用getBean方法得到对象。
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) app.getBean("user");
# Spring重点配置
<bean> 标签
* id属性: 在容器中Bean实例的唯一标识,不允许重复 * class属性: 要实例化的Bean的全限定名 * scope属性:Bean的作用范围,常用是Singleton(默认)和prototype * init-method属性: 初始化方法 * destroy-method属性: 销毁方法set方法注入 使用<property>标签 自己开发的模块 推荐使用set方法注入
name是属性名称
name="user", user是setUser 的set后面的部分的小写,即 setUser——User——user 一般来说即属性名 user普通数据类型-----value
<property name="username" value="张三"/>集合数据类型-----list,map,props,set,array
list
<property name="strList"> <list> <value>aaa</value> <value>bb</value> <value>ccc</value> </list> </property>map
<property name="brandMap"> <map> <entry key="key1" value-ref="brand1"/> <entry key="key2" value-ref="brand2"/> </map> </property>props
<property name="properties"> <props> <prop key="p1">ppp1</prop> <prop key="p2">ppp2</prop> <prop key="p3">ppp3</prop> </props> </property>引用数据类型-----ref
<property name="user" ref="user"><constructor-arg>标签-- 有参构造函数注入 不推荐
<constructor-arg name="user2" ref="user" /><import> 标签:导入其他的Spring的分文件
<import resource="applicationContext-insert.xml"/> <!--注入方法-->加载外部配置property ,使用el表达式来获取值 ---引入数据源
<context:property-placeholder location="classpath:jdbc.properties"/> <!-- el表达式 来获取配置文件信息--> <bean id="dataSource_c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="jdbcUrl" value="${jdbc.url}"/> </bean>组件扫描,配合注解开发使用
<context:component-scan base-package="com.diana"/>实例化方法
无参构造
<bean id="user" class="com.diana.Dao_Mapper.impl.UserImpl"/>工厂静态方法实例化
<bean id="user" class="com.diana.factory.StaticFactory" factory-method="getUser"/> //实例化bean 方式二 工厂静态方法实例化 public class StaticFactory { public static User getUser(){ return new UserImpl(); } }工厂实例方法实例化
<bean id="factory" class="com.diana.factory.DynamicFactory"/> <bean id="user" factory-bean="factory" factory-method="getUser"/> //实例化bean 方式三 工厂实例方法实例化 public class DynamicFactory { public User getUser(){ return new UserImpl(); } }使用FactoryBean实例化
<!--使用FactoryBean实例化--> <bean id="user" class="com.diana.factory.UserFactoryBean"/> //实例化bean 方式四 使用FactoryBean实例化 public class UserFactoryBean implements FactoryBean<User> { //代替原始实例工厂中创建对象的方法 public User getObject() throws Exception { return new UserImpl(); } //得到Bean的类型 public Class<?> getObjectType() { return User.class; } //是否单例 工厂一般都是单例 所有这个方法一般不写 默认单例 public boolean isSingleton() { return true; } }
# Spring注解开发
在Dao_Mapper--impl--UserImpl 中
在demo-- UserController ,config 中
XML配置与注解配置对比

# Spring 集成Junit
导入spring集成Junit的坐标和Junit的坐标。
- spring-context spring-test 两个版本要一致
- junit 版本要在4.12 及以上
使用@Runwith注解替换原来的运行期
使用@ContextConfiguration指定配置文件或配置类
使用@Autowired注入需要测试的对象
创建测试方法进行测试
@RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration("classpath:applicationContext.xml")//配置文件方式 @ContextConfiguration(classes = {SpringConfiguration.class})//注解类方式 public class SpringJuintTest { @Autowired private UserService userService; @Autowired private DataSource dataSource; @Test public void test1() throws SQLException { userService.save(); System.out.println(dataSource.getConnection()); } }
# Spring集成Web
放到了SpringMVC项目下
ApplicationContext应用上下文获取方式
在web.xml中配置ContextLoaderListener监听器 (导入spring-web坐标)
<!-- spring web监听器--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.6</version> </dependency><!--配置监听器 来自动加载Spring应用上下文--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>使用WebApplicationContextUtils获得应用上下文对象 ApplicationContext
//调用Spring封装好的实现 //得到servletContext 最大的域 ServletContext servletContext = this.getServletContext(); ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);从容器中得到userService对象
UserService userService = app.getBean(UserService.class); userService.save();
# Spring -AOP
Aop 简化共性功能,方便开发
# 一、AOP的基础知识
什么是AOP
- AOP 是面向切面编程,是通过 预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- 动态代理: 在不修改源码的基础上,对目标方法进行增强。作用:完成代码间的松耦合
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,提升开发效率。
- AOP 是面向切面编程,是通过 预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP的作用及其优势
作用: 在程序运行期间,在不修改源码的情况下,对方法进行功能增强。
优势:减少重复代码,提高开发效率,便于维护。
AOP 流程
- 一个目标方法和一个功能增强方法就被称为一个切面。
- 将目标方法和功能增强方法 在运行时放到一起,达到功能增强的目的,同时达到松耦合的目的。
- 目标方法(save,update,delete),功能增强方法(日志控制),源码分离,在配置文件中声明 运行时进行结合。

AOP的动态代理技术
JDK动态代理 要求 目标对象必须有一个接口
cglib动态代理 会生成一个目标对象的子类 从而实现动态代理

AOP相关概念
- Target(目标对象): 代理的目标对象。
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
- Joinpoint(连接点)---->(可以被增强的目标方法): 所谓连接点是值那些被拦截到的点(方法)。在Spring中,这些点值得是方法,因为spring只支持方法类型的连接点。
- Pointcut(切入点/切点)---->(你配置的,真正被增强的目标方法,是连接点的一部分):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
- Advice(通知/增强)---->(用来增强目标方法的增强方法): 所谓通知是指 拦截到Joinpoint之后 所要做的事情就是通知。
- Aspect(切面):是切入点和通知的结合
- Weaving(织入)---->(把切入点和通知结合的过程):是指把增强应用到目标对象来创建新的代理对象的过程,采用配置文件实现,配置的过程就是织入的过程。spring采用动态代理织入,而AspecttJ 采用编译期织入和类装载期织入
AOP开发明确事项
需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些切入点进行结合
AOP技术实现的内容(Spring帮你实现动态代理的过程)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
AOP底层代理方式
在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
有接口-----> jdk动态代理 无接口---->cglib动态代理
# 二、基于XML的AOP开发
开发步骤
导入坐标
<!-- aspectj aop 配置--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>创建目标接口和目标类(内部有切点)
创建切面类(内部有增强方法)
将目标类和切面类的对象创建权交给spring容器
<!-- 切面对象--> <bean id="aspect" class="com.diana.aop.MyAspect"/> <!--目标对象--> <bean id="target" class="com.diana.aop.impl.TargetImpl"/>在配置文件中配置织入关系
<!-- 配置织入: 告诉spring框架 哪些方法(切点)需要进行哪些增强(前置,后置,……)--> <aop:config> <!-- 声明切面--> <aop:aspect ref="aspect"> <!--切面:切点+通知--> <!--对save方法 使用before方法 进行前置增强--> <aop:before method="before1" pointcut="execution(public void com.diana.aop.impl.TargetImpl.save())"/> </aop:aspect> </aop:config>测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-aop.xml") //测试AOP public class test_aop { @Autowired private Target target; @Test //测试前置增强方法 public void test_before(){ target.save();//调用切点 } }
切点表达式的写法
表达式语法
execution([修饰符] 返回值类型 包名 . 类名 . 方法名(参数))访问修饰符可以省略
返回值类型、包名、类名、方法名可以使用※代表任意
包名与类名之间一个点代表当前包下的类,两个点 .. (不推荐,效率低)表示当前包及其子包下的类
参数列表可以使用两个点 表示任意个数,任意类型的参数列表
- 表示aop当前包下的Target类的method方法,无参数,返回值类型为空。
- 表示aop当前包下的Target类的所有方法,任意参数,返回值类型为空。
- 表示aop当前包下的所有类的所有方法,任意参数,返回值类型为任意。-------最常用
- 表示aop当前包及其子包下的所有类的所有方法,任意参数,返回值类型为任意。
- 表示任意包当前包及其子包下的所有类的所有方法,任意参数,返回值类型为任意。
切点表达式的书写技巧

示例
//表示任意_service的所有的find_ 方法 execution(* com.diana.*.*Service.find*(..)) //表示任意_service的所有的save方法 execution(* com.diana.*.*Service.save(..))AOP通知类型 环绕通知是重点

<!--切面:切点+通知--> <!--对save方法 使用before方法 进行前置增强--> <aop:before method="before1" pointcut="execution(public void com.diana.aop.impl.TargetImpl.save())"/> <!--对aop当前包及其子包下所有方法 使用before方法 进行前置增强--> <aop:before method="before1" pointcut="execution(* com.diana.aop..*.*(..))"/> <!-- 后置增强--> <aop:after-returning method="afterReturning" pointcut="execution(* com.diana.aop..*.*(..))"/> <!-- 环绕增强--> <aop:around method="around" pointcut="execution(* com.diana.aop..*.*(..))"/> <!-- 异常抛出增强--> <aop:after-throwing method="afterThrowing" pointcut="execution(* com.diana.aop..*.*(..))"/> <!-- 最终增强--> <aop:after method="after" pointcut="execution(* com.diana.aop..*.*(..))"/>切点表达式的抽取
<!-- 抽取切点表达式--> <aop:pointcut id="mypointcut" expression="execution(* com.diana.aop..*.*(..))"/> <!-- 使用抽取后的切点表达式--> <aop:before method="before1" pointcut-ref="mypointcut"/> <aop:around method="around" pointcut-ref="mypointcut"/>
# 三、基于注解的AOP开发
开发步骤
导入坐标
<!-- aspectj aop 配置--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>创建目标接口和目标类(内部有切点)
创建切面类(内部有增强方法)
将目标类和切面类的对象创建权交给spring容器(@Component)
在切面类中使用注解配置织入关系
@Component("aspect") @Aspect//标注当前类是一个切面类 public class MyAspect { //前置增强 @Before("execution(* com.diana.aop_ZJ..*.*(..))") @Before("myponitcut()") //抽取切点表达式后使用 @Before("MyAspect.myponitcut()")//抽取切点表达式后使用 public void before1(){ System.out.println("前置增强……"); } //后置增强 @AfterReturning("execution(* com.diana.aop_ZJ..*.*(..))") public void afterReturning(){ System.out.println("后置增强……"); } //环绕增强 @Around("execution(* com.diana.aop_ZJ..*.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { //Proceeding JoinPoint: 正在执行的连接点 切点 System.out.println("环绕前增强……"); //执行切点方法 Object proceed = pjp.proceed(); System.out.println("环绕后增强……"); return proceed; } //异常抛出后增强 @AfterThrowing("execution(* com.diana.aop_ZJ..*.*(..))") public void afterThrowing(){ System.out.println("抛出了异常……"); } //最终增强 不管有没有异常,都要执行 @After("execution(* com.diana.aop_ZJ..*.*(..))") public void after(){ System.out.println("最终增强"); } //定义切点表达式 @Pointcut("execution(* com.diana.aop_ZJ..*.*(..))") public void myponitcut(){} }在配置文件中开启组件扫描和AOP的自动代理
<!--配置组件扫描--> <!--配合注解开发使用--> <context:component-scan base-package="com.diana.aop_ZJ"/> <!-- aop 自动代理--> <aop:aspectj-autoproxy/>使用纯注解开发 不留配置文件 --解决自动代理问题即可
//启动AOP自动代理 代替 <aop:aspectj-autoproxy/> @EnableAspectJAutoProxy //<!--配置组件扫描--> <!--配合注解开发使用--> //<context:component-scan base-package="com.diana"/> @ComponentScan("com.diana")测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-aop_ZJ.xml") public class test_aop_ZJ { @Autowired Target target=new TargetImpl(); @Test public void test(){ target.save();//调用切点 } }
切点表达式的抽取
在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中引用。
//定义切点表达式 @Pointcut("execution(* com.diana.aop_ZJ..*.*(..))") public void myponitcut(){} // @Before("execution(* com.diana.aop_ZJ..*.*(..))") // @Before("myponitcut()") //抽取切点表达式后使用 @Before("MyAspect.myponitcut()")//抽取切点表达式后使用 public void before1(){ System.out.println("前置增强……"); }
# 四、AOP通知获得数据
- 项目在
my_case_demo2中,- 测试代码
test-test_aop_param - aop代码
aop-getParam
- 测试代码
- 获取切入点方法的参数
- JoinPoint :适用于前置,后置,最终通知,抛出异常后 通知
- ProceedJointPoint:适用于环绕通知, 可以修改参数,以到达某些目的
- 获取切入点方法返回值
- 后置
- 环绕通知
- 获取切入点方法运行异常信息 (了解)
- 抛出异常后通知
- 环绕通知
# Spring 事务控制
测试代码在Controller层 一共三个,一个xml配置,一个半注解,一个纯注解
# 一、编程式事务控制相关对象
PlatformTransactionManager(我们要告诉spring 用哪个实现类)该接口是Spring的事务管理器,它里面提供了常用的操作事务的方法。
- TransactionStatus getTransaction(TransactionDefination defination) 获取事务的状态信息
- void commit(TransactionStatus status) 提交事务
- void rollback(TransactionStatus status) 回滚事务
该接口对应不同的Dao层有不同的实现类。 jdbc,mybatis 是org.springframework.jdbc.datasource.DataSourceTransactionManager
TransactionDefinition(我们要告诉spring 传入哪些参数,下面方法需要用到的)TransactionDefinition它是事务的定义信息对象,里面如下方法int getIsolationLevel()获得事务的隔离级别int getPropogationBehavior()获得事务的传播行为int getTimeout()获得超时时间boolean isReadOnly()是否只读
事务隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。 ISOLATION_DEFAULT
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE
事务传播行为
- REQUIRED: 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
- SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW: 新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER: 以非事务方式运行,如果当前存在事务,抛出异常
- NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
超时和只读
- 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
- 是否只读:建议查询时设置为只读
设置事务回滚异常
error系,运行时异常,Spring事务 默认才回滚,其他都不回滚,例如 IOException
通过rollbackFor 设置除了默认回滚异常外的哪些异常时进行回滚
TransactionStatus(获取事务的运行状态)TransactionStatus 接口提供的是事务具体的运行状态
boolean hasSavepoint()是否存储回滚点boolean isCompleted()事务是否完成boolean isNewTransaction()是否是新事务boolean isRollbackOnly()事务是否回滚
# 二、基于XML的声明式事务控制
声明式事务控制
在spring配置文件中声明要处理处理事务。
作用:事务管理不侵入开发的组件。业务逻辑和事务管理分开来。
事务管理算是 增强方法,业务逻辑算是 目标方法 ,AOP思想
Spring声明式事务控制底层就是AOP
声明式事务控制的实现
谁是切点? ---service层 的transfer方法
谁是增强? --- 开启事务 --- 提交事务 --回滚 等等
xml配置
<!-- 配置一个平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource_c3p0"/> </bean> <!-- 通知 事务的增强--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 设置事务的属性信息--> <tx:attributes> <!-- 那些方法被增强--> <!-- isolation:事务隔离级别 propagation:事务传播行为 read-only:是否只读 timeout: 超时时间2s--> <tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" read-only="false" timeout="2"/> <tx:method name="*"/> <!-- 所有方法--> <tx:method name="select*"/> <!-- 所有以select开头的方法--> </tx:attributes> </tx:advice> <!-- 配置事务的aop织入--> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.diana.service..*.*(..))"/> <!-- 专门用来配置 事务的一个命令--> <!-- <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.diana.service..*.*(..))"/>--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/> </aop:config>
# 三、基于注解的声明式事务控制
开发步骤
service层注解
在service方法上加@Transactional(isolation = Isolation.REPEATABLE_READ),表示要对这个方法进行事务控制
可在类上直接配置@Transactional,表示整个类所有方法的默认事务控制
当方法和类上同时都有事务控制时,以方法上的为主,就近原则
// 声明事务的 service层 注解方式配置 @Service @Transactional(isolation = Isolation.REPEATABLE_READ) //整个类的所有方法的默认事务控制 public class AccountServiceImpl_ZJ implements AccountService { @Autowired private AccountDao accountDao; // 这个方法需要进行事务控制,并在里面配置参数 @Transactional(isolation = Isolation.DEFAULT) //就近原则,用这个 public void transfer(String outMan, String inMan, double money) { //开启事务 -aop 前置增强 accountDao.out(outMan,money); int i = 1/0; accountDao.in(inMan,money); //提交事务 -aop 后置增强 } }xml中剩余配置
- 把原配置中的 增强方法 声明和 织入过程 用@Transactional()来代替
- 添加了一个包扫描和事务的注解驱动配置
<!-- jdbc模板数据 别人的东西 一般在xml中配置--> <!--加载外部配置文件property--> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- el表达式 来获取配置文件信息--> <bean id="dataSource_c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="jdbcUrl" value="${jdbc.url}"/> </bean> <!-- 在spring 容器中产生 jdbc模板对象 同时注入数据源--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource_c3p0"/> </bean> <!-- 配置一个平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource_c3p0"/> </bean> <!-- 事务的注解驱动--> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 包扫描--> <context:component-scan base-package="com.diana"/>纯注解开发
数据源,jdbc模板,平台事务管理器
//存入数据源 @Bean("dataSource")//Spring 会将当前方法的返回值以指定名称存储到Spring容器中 public DataSource get_DateSource_c3p0(User user) throws Exception { System.out.println(user);//保证容器内部有这个Bean引用类型 , 只要传入形参 spring会自动找到 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource.setDriverClass(driver); comboPooledDataSource.setJdbcUrl(url); comboPooledDataSource.setUser(username); comboPooledDataSource.setPassword(password); return comboPooledDataSource; } @Bean("jdbcTemplate") //存入jdbc模板 public JdbcTemplate jdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate=new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } @Bean//向容器中存入 事务管理器 public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager=new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }注解驱动,包扫描
//开启事务 @EnableTransactionManagement //<!--配置组件扫描--> <!--配合注解开发使用--> @ComponentScan("com.diana")
