Spring学习笔记
1、初识Spring
2、Spring入门
2.1 什么是IOC
Inversion Of Control:控制反转,也就是将对象的创建权反转交给Spring
IOC底层原理
这里以dao层举例,最开始的方式就是写一个UserDao接口,然后写一个UserDaoImpl和UserDaoPPImpl进行实现,这时需要UserDaoIMpl对象,创建对象的时候就是 UserDao userDao = new UserDaoImpl(); 这样假设后面需求发生变化,需要的是UserDaoPPImpl的对象,我们就需要修改源码为 UserDao userDao = new UserDaoPPImpl(); 如果有一百的地方这样写了,就需要修改一百次,这是因为接口与实现类之间有耦合。
这里就演变出了工厂模式,我们将获取对象的功能单独抽出来做成一个工具类,有以下代码
class Beanfactory { public static UserDao getUserDao() { return new UserDaoImpl(); } }
这样就可以在需要改成获取UserDaoPPImpl() 时,只修改一处代码。但这样虽然解除了接口与实现类之间的耦合,但接口与工厂之间有了耦合。
于是,Spring中使用了 工厂 + 反射 + 配置文件 的方式降低耦合,具体就是创建applicationContext.xml对一些Bean进行注册,然后在工厂方法中,解析配置文件,获取想要创建实例的类对象,然后利用反射创建实例,代码如下:
applicationContext.xml文件 <bean name="userDAO" class="com.itheima.spring.demo1.UserDAOImpl" ></bean> 工厂 class BeanFactory{ public static Object getBean(String name) { // 解析XML文件,根据name找到指定类 ... // 反射 Class clazz = Class.forName(name); return clazz.newInstance(); } }
2.2 IOC与DI(依赖注入)
DI(Dependency Injection):依赖注入,前提是必须有IOC环境,Spring管理这个类的时候将类依赖的属性注入进来。
其实不管是控制反转还是依赖注入,他们都可以这样理解:当某个Java实例(调用者)需要另一个Java实例(被调用者)时,在传统的程序设计过程中,通常有调用者来创建被调用者的实例。但是在依赖注入/控制反转模式下,创建被调用者的工作不再是有调用者来完成,而是由Spring容器来完成,然后注入调用者。
依赖注入通常有如下两种:
1、 设置注入:IoC容器使用属性的setter方法来注入被依赖的实例。
2、 构造注入:IoC容器使用构造器来注入被依赖的实例。
前提条件:Java类的定义按照JavaBean的规范进行创建
介绍JavaBean的博客:Java Bean 简介及其应用 参考博客:Spring读书笔记-----Spring核心机制:依赖注入
设置注入:set方法进行依赖注入的代码示例
首先是一个按照JavaBean标准定义的Car类
1 public class Car { 2 private String name; 3 private int price; 4 5 public Car() { 6 } 7 8 public Car(String name, int price) { 9 this.name = name; 10 this.price = price; 11 } 12 13 public String getName() { 14 return name; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public int getPrice() { 22 return price; 23 } 24 25 public void setPrice(int price) { 26 this.price = price; 27 } 28 29 @Override 30 public String toString() { 31 return "Car{" + 32 "name='" + name + '\'' + 33 ", price=" + price + 34 '}'; 35 } 36 }然后对其在spring-config.xml文件中进行配置,这里就用到了<property>标签,Spring会在调用无参数的构造器、创建默认的Bean实例后,调用相应的setter方法为程序注入属性值。<property…/>定义的属性值将不再有该Bean来主动设置、管理,而是接受Spring的注入。
<bean id="car" class="com.xidian.demo.Car"> <property name="name" value="奥迪"></property>
<property name="price" value="400000"></property> </bean>测试输出结果
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); Car car = context.getBean("car", Car.class); System.out.println(car);
设置注入代码示例2:属性有自定义的对象类型
首先我们创建轮胎类,然后在前面提到的Car类中添加一个轮胎对象的属性,并重写toString()方法
public class Tire { private String name; private int price; public Tire() { } public Tire(String name, int price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } @Override public String toString() { return "Tire{" + "name='" + name + '\'' + ", price=" + price + '}'; } }private Tire tires; public Tire getTires() { return tires; } public void setTires(Tire tires) { this.tires = tires; }对其在spring-config.xml文件中进行配置,这里就用了<ref>这个标签
<bean id="tire" class="com.panlei.demo.Tire"> <property name="name" value="朝阳"></property> <property name="price" value="3000"></property> </bean> <bean id="car" class="com.panlei.demo.Car"> <property name="name" value="奥迪"></property> <property name="price" value="400000"></property> <property name="tires" ref="tire"></property> </bean>最终测试结果
构造注入
Car中的构造方法
public Car(String name, int price, Tire tires) { this.name = name; this.price = price; this.tires = tires; }spring-config.xml中的配置,这里就可以不用无参构造方法了,直接使用有参构造
<bean id="car" class="com.panlei.demo.Car"> <constructor-arg name="name" value="奔驰"></constructor-arg>
<constructor-arg name="price" value="400000"></constructor-arg>
<constructor-arg name="tires" ref="tire"></constructor-arg> </bean>测试结果:
2.3 Spring工厂类
BeanFactory :老版本的工厂类
BeanFactory:调用getBean的时候,才会生成类的实例。
ApplicationContext :新版本的工厂类
ApplicationContext:加载配置文件的时候,就会将Spring管理的类都实例化。
ApplicationContext有两个实现类
ClassPathXmlApplicationContext :加载类路径下的配置文件
FileSystemXmlApplicationContext :加载文件系统下的配置文件
2.4 Spring的Bean的作用范围
scope :Bean的作用范围
singleton :默认的,Spring会采用单例模式创建这个对象。
prototype :多例模式。(Struts2和Spring整合一定会用到)
request :应用在web项目中,Spring创建这个类以后,将这个类存入到request范围中。
session :应用在web项目中,Spring创建这个类以后,将这个类存入到session范围中。
globalsession :应用在web项目中,必须在porlet环境下使用。但是如果没有这种环境,相对于session。
<bean id="car" class="com.test.spring.demo2" scope="prototype" init-method="setup" destroy-method="destroy"/>
3、Spring的Bean管理(注解方式)
前面介绍的Spring的依赖注入是Spring的Bean管理的一种方式,是基于XML的方式,还有另外一种方式,就是注解方式
3.1 注解方式简单使用
1、在spring-config.xml中引入相关约束
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>2、编写测试类,仍以上面的Car类作为示例
3、在xml中配置注解的组件扫描
<context:component-scan base-package="com.panlei.demo"/>4、在相关类上添加注解。使用注解,可以没有set方法,需要将注解添加到属性处,有set方法就添加到set方法上
package com.panlei.demo; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component("car") // 相当于<bean id="car" class="com.panlei.demo.Car"/> public class Car { private String name; private int price; public Car() { } public Car(String name, int price) { this.name = name; this.price = price; } public String getName() { return name; } @Value("奥拓") public void setName(String name) { this.name = name; } public int getPrice() { return price; } @Value("10000") public void setPrice(int price) { this.price = price; } @Override public String toString() { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}'; } }
5、测试输出
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); Car car = context.getBean("car", Car.class); System.out.println(car);
3.2 IOC注解详解
@Component:组件,作用在类上
Spring提供了@Component的三种衍生注解,为了更好的区分不同的层,让标注类本身的用途清晰:
@Controller:WEB层
@Service:业务层
@Repository:持久层
属性注入的注解:
@Value:用于注入普通类型
@Autowired:自动装配,会按照注解的类型进行装配
若想要@Autowired按照名称进行注入,需结合 @Qualifier 强制使用名称注入
@Resource:相当于@Autowired与@Qualifier一起使用
Bean作用范围的注解:
@Scope:
singleton:单例
prototype:多例
Bean生命周期的配置:
@PostConstruct:相当于 init-method
@PreDestroy: 相当于destroy-method
XML方式与注解方式的比较:
XML:结构清晰
注解:开发方便,便于思考的连续性
实际开发中还有一种XML与注解整合的开发方式:也就是Bean在XML中进行配置,但是属性使用注解进行注入,需要在xml文件中声明,就不需要声明组件扫描
<context:annotation-config></context:annotation-config>
4、AOP概述
4.1 AOP介绍
AOP(Aspect Oriented Programming,面向切面的编程)。利用AOP对业务逻辑各个部分进行隔离,降低耦合度
4.2 AOP底层原理简单了解
利用了代理机制:Spring 的 AOP 的底层用到两种代理机制
JDK 的动态代理 :针对实现了接口的类产生代理。
Cglib 的动态代理 :针对没有实现接口的类产生代理,应用的是底层字节码增强技术 应用的是底层字节码增强技术,生成当前类的子类对象 .
4.3 AOP相关术语及知识
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
Target(目标对象):代理的目标对象
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程. spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面): 是切入点和通知(引介)的结合
4.4 AOP开发入门(基于AspectJ的XML方式)
SpringAOP与AspectJ的关系
AOP面向切面编程是一种编程思想,是对OOP面向对象编程的一种补充。对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。
而AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。
使用时需要引入相关的jar包:
引入AOP联盟的jar包,这是AOP的规范
引入AspectJ的jar包,这是对上面AOP规范的实现
引入整合Spring和AspectJ的jar包
引入Spring的AOP jar包,因为上面Spring和AspectJ的整合jar包用到了Spring的AOP jar包
1、Spring配置文件中关于AOP的配置
2、编写接口及其实现类,并在配置文件中进行<bean>配置
![]()
3、编写自己的切面类,并在配置文件中进行<bean>配置
4、针对具体类中的方法进行具体的AOP的切入点的配置
通知类型
前置通知:在目标方法执行之前执行
后置通知:在目标方法执行之后执行,可以获取被代理方法的返回值,注意参数名字的一致性
切面类中的方法可以有返回值,其返回值是如何处理的?待解决
环绕通知:注意需要调用被代理的方法
异常抛出通知:可以接收异常信息
最终通知:
切入点表达式
execution( 表达式 )
表达式 : [方法访问修饰符 ] 方法返回值 包名 .类名 .方法名 (方法的参数)
public * com.panlei.spring.dao.*.*(..)
* com.panlei.spring.dao.*.*(..) // 包下面的所有类所有方法
* com.panlei.spring.dao.UserDao+.save(..) // + 代表当前类及其子类的save方法
* com.panlei.spring.dao..*.*(..) // 这个包以及其子包下面的所有类所有方法
4.5 基于AspectJ的注解的AOP开发
继续使用上面的项目,我们还是编写好目标类以及切面类,并在spring的配置文件中进行相应的bean配置,然后根IOC注解开发类似,我们需要在spring配置文件中配置一开启注解AOP开发的语句
然后就可以在切面类上进行注解开发,使用的语法仍然是切入点表达式,如下图为前置通知的注解
同样,也有后置通知,也可以接收被代理类的返回值
环绕通知
异常抛出通知
最终通知
另外,针对每一个方法配置其切入点太麻烦,我们可以单独配置切入点,然后直接引用方法名即可
5、JDBC模板
以C3P0作为数据库连接池
1、引入jar包,在Spring配置文件中配置C3P0数据库连接池和JDBC模板,其中数据库的配置使用单独的配置文件,然后在Spring配置文件中引入该配置文件
2、简单使用其进行数据库操作
查询测试
6、Spring的事务管理
6.1 Spring事务管理的相关API
PlatformTransactionManager:平台事务管理器,是一个接口类,Spring用于管理事务的真正对象,有两个实现类
DataSourceTransactionManager :底层使用JDBC管理事务HibernateTransactionManager :底层使用Hibernate管理事务
TransactionDefinition :事务定义信息,用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读等
TransactionStatus:事务的状态,用于记录在事务管理过程中,事务的状态的对象
它们之间的关系:
Spring进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中,产生各种状态,将这些状态的信息记录到事务状态的对象中。
6.2 Spring的事务传播行为
Spring中提供了七种事务的传播行为
6.3 Spring的编程式事务管理
我们以一个事务讲解常用的银行转账的案例作为例子
1、首先先搭建好一个转账的环境,也就是定义好Service层与Dao层,并在配置文件中配置bean以及数据库连接池
这里注意一点,由于我们需要JdbcTemplate进行数据相关的增删改查的操作,但如果给每一个Dao中都定义一个JdbcTemplate的话,其实获取的都是同一个对象,Spring考虑到了这种需求,就将其写为JdbcDaoSupport这个类,我们只需要继承这个类,这个类中就有一个JdbcTemplate对象,调用get方法获取即可,在配置Dao的时候要设置一个dataSource参数,用来初始化JdbcTemplate对象。这样就不需要在配置文件中配置JdbcTemplate了。
2、配置平台事务管理器以及事务管理的模板类,并在Service中注入事务管理的模板
3、在Service层编写事务管理的代码
4、运行测试
当我们故意在转账操作之间加入异常时,会发现不会出现一边已经扣钱,另一边还没有加钱的情况。
6.4 Spring的声明式事务管理(基于XML方式)
声明式事务管理是底层基于AOP的,也就是需要引入aop的jar包以及在配置文件中配置一些aop
1、仍然使用上一节的例子,配置好Service与Dao、数据库连接池以及平台事务管理器
2、配置事务的增强:因为事务的管理是一个固定的模式,也就是切面的增强是固定的,所以切面类不需要自己写,只需要进行配置即可。配置的信息主要是事务的配置,<tx:advice>中主要事务的一些配置,主要的配置信息在<tx:method>中,name表示的是应用事务的方法,里面可以使用通配符,比如save*表示save开头的方法,还可以定义传播行为等信息。然后就是aop的配置,用<aop:advisor>将一个<tx:advice>与切入点联合起来。
3、运行测试
当我们故意在转账操作之间加入异常时,会发现不会出现一边已经扣钱,另一边还没有加钱的情况。
6.5 Spring的声明式事务管理(基于注解的方式)
1、仍然使用上一节的例子,配置好Service与Dao、数据库连接池以及平台事务管理器
2、开启注解事务
3、在具体的service代码中添加事务注解,本例中指的是AccountServiceImpl。在注解中可以添加事务的各种定义信息
4、运行测试
当我们故意在转账操作之间加入异常时,会发现不会出现一边已经扣钱,另一边还没有加钱的情况。