控制反转(IOC)
IOC 是 Inverse of Control 的缩写,意思是控制反转. 是降低对象之间耦合关系的设计思想.
通过 IOC ,开发人员不需要关心对象的创建过程,该过程交给Spring IOC容器完成.Spring IOC容器通过反
射机制创建类的实例.
依赖注入(DI)
DI 是 Dependency Injection 的缩写,意思是 Spring IOC 容器 创建对象的时候,同时为这个对象注入它所依赖的属性的值. 这个对象与对象之间的关系也交给了 Spring IOC 容器 来维护.
依赖注入是 Spring IOC 容器在运行期间, 动态的将依赖关系注入到对象中.
所以依赖注入是控制反转的实现方式.
依赖注入和控制反转描述是从不同角度描述的同一件事情, 就是通过引入Spring IOC容器,利用依赖关系注入的方式, 实现对象与对象之间的解耦.(高内聚,低耦合)
Spring 体系结构
核心容器
由 spring-beans、spring-core、spring-context 和 spring-expression 4 个模块组成。
spring-context依赖于其他三个模块,所以在maven中 只需要导入spring-context即可使用spring。
spring-beans 和 spring-core 模块是 Spring 框架的核心模块
包含了 控制反转 (Inversion of Control, IOC) 和 依赖注入 (Dependency Injection, DI)
BeanFactory 接口是 Spring 框架中的核心接口, 它是工厂模式的具体实现。
BeanFactory 使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。
spring-context 模块:
构架于核心模块之上, 它扩展了 BeanFactory, 为它添加了 Bean 生命周期控制、 框架事件体系以及资源加载透明化等功能。 此外该模块还提供了许多企业级支持, 如邮件访问、远程访问、 任务调度等。
ApplicationContext(重点) 是该模块的核心接口,它是 BeanFactory 的超类, 与BeanFactory 不同,ApplicationContext 容器实例化后会自动对所有的单实例 Bean 进行实例化与依赖关系的装配, 使之处于待用状态。
ApplicationContext 接口的常用子类:
FileSystemXmlApplicationContext 从磁盘绝对路径加载配置文件初始化IOC容器
ClassPathXmlApplicationContext 从类路径加载配置文件初始化IOC容器
数据访问及集成
由 spring-jdbc(mybatis使用这个)、 spring-tx(事务)、 spring-orm、 spring-jms(Java Messaging Service) 和 spring-oxm 5 个模块组成.
spring-jdbc 模块是 Spring 提供的 JDBC 抽象框架的主要实现模块, 用于简化 Spring JDBC。
主要是提供 JDBC 模板方式、 关系数据库对象化方式、 SimpleJdbc 方式、 事务管理来简化 JDBC
编程,主要实现类是 JdbcTemplate 、 SimpleJdbcTemplate 以及
NamedParameterJdbcTemplate 。
spring-oxm 模块主要提供一个抽象层以支撑 OXM(OXM 是 Object-to-XML-Mapping 的缩写, 它
是一个 O/M-mapper, 将 java 对象映射成 XML 数据, 或者将 XML 数据映射成 java 对象)
现在大部分已经使用json了。
WEB
由 spring-web、 spring-webmvc、 spring-websocket 和 spring-webflux 4 个模块组成。
spring-web + spring-webmvc = springmvc
spring-web 模块为 Spring 提供了最基础 Web 支持, 主要建立于核心容器之上,通过 Servlet 或者 Listener 来初始化 IOC 容器, 也包含一些与 Web 相关的支持。
AOP
由 spring-aop、 spring-aspects 和 spring-instrument 3 个模块组成。
spring-aop 是 Spring 的另一个核心模块, 是 AOP 主要的实现模块。 作为继 OOP 后, 对程序员影响最大的编程思想之一, AOP 极大地开拓了人们对于编程的思路。 在Spring 中, 他是 以 JVM的动态代理技术 为基础, 然后 设计出了一系列的 AOP 横切实现 , 比如 前置通知、 返回通知、 异常通知 等, 同时,Pointcut 接口来匹配切入点, 可以使用现有的切入点来设计横切面, 也可以扩展相关方法根据需求进行切入。
spring-aspects 模块集成自 AspectJ 框架, 主要是为 Spring AOP 提供多种 AOP 实现方法。
spring入门程序
项目依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
</dependencies>
配置文件
//文档规范
<?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>
编写JavaBean
public class MessageService {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
装配JavaBean
在spring-config.xml 添加如下内容:
<bean id="messageService" class="com.imcode.spring.service.MessageService">
<property name="message" value="Hello Spring"/>
</bean>
配置baen
<bean>
配置需要让Spring IOC容器创建的对象
name :用于之后从spring容器获得实例时使用
class :需要创建实例的全限定类名
property:类中的成员
property name 成员的名称
property value 成员的值
scope:
singleton 单例
prototype 多例
lazy-init: true 按需创建对象 false 容器启动就创建对象 scope="singleton"时有效.
Spring IOC 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他
们的整个生命周期从创建到销毁。
Spring IOC 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring
Bean
通过配置,容器知道对哪些对象进行实例化,配置和组装。
配置信息可以通过 XML,Java注解或 Java 代码来表示。
对象的生命周期
Bean 是一个被实例化,组装,并通过 Spring IOC 容器所管理的JAVA对象。
这些 bean 是由给容器提供的配置信息创建的。
容器需要知道bean的如下信息:
对象实例化
<bean id="messageService" class="com.imcode.spring.MessageService"/>
该配置告诉容器使用 MessageService 类的无参构造方法实例化Bean
懒加载:
lazy-init=“false” 默认值,启动容器的时候就创建对象.
lazy-init=“true” ,使用对象的时候才创建对象.
该属性只对单例 scope=“singleton” 有效.
默认也是单例
示例:
<bean id="messageService" class="com.imcode.spring.service.MessageService"
scope="singleton" lazy-init="true">
多例是懒加载
对象的作用域
通过scope属性设置bean的作用域:
对象的初始化和销毁
在 MessageService 类中增加如下方法:
public void init() {
System.out.println("初始化对象...");
}
public void destroy() {
System.out.println("销毁对象...");
}
在spring-config.xml添加如下配置:
<bean id="messageService" class="com.imcode.spring.service.MessageService"
init-method="init"
destroy-method="destroy">
<property name="message" value="Hello Spring"/>
</bean>
init-method=“init”
destroy-method=“destroy”
这么配置会告诉spring在初始化和销毁时使用的方法
依赖注入(DI)
setXxx() 方法注入
<bean id="messageService" class="com.imcode.spring.service.MessageService">
<property name="message" value="Hello Spring"/>
</bean>
property name 的值不是实体类的属性名而是set方法中setXxx()的xxx
构造方法注入
第一种方式
<bean id="messageService" class="com.imcode.spring.service.MessageService">
<constructor-arg name="message" value="Hello"></constructor-arg>
</bean>
创建构造方法:
public MessageService(String message) {
this.message = message;
System.out.println("MessageService(String message)");
}
constructor-arg name属性值和构造方法参数的值相同
第二种方式:
根据构造方法的参数顺序:
<bean id="messageService" class="com.imcode.spring.service.MessageService">
<constructor-arg index="0" value="Hlll"></constructor-arg>
</bean>
注入对象
创建HouseService:
public class HouseService {
public void deleteById(Integer id){
System.out.println("删除成功 HouseId = " + id);
}
}
HouseController:
public class HouseController {
private HouseService houseService;
/**
* set 方法
* @param HouseService
*/
public void setHouseService(HouseService houseService) {
this.houseService = houseService;
}
public void doGet() {
Integer id = 100;// 模拟从request 获取到的id
houseService.deleteById(id);
}
}
setXxx() 方法注入
<bean name="houseService" class="com.imcode.spring.service.HouseService"></bean>
<bean name="houseController" class="com.imcode.spring.controller.HouseController">
<property name="houseService" ref="houseService"/>
</bean>
构造方法注入
<bean id="houseService" class="com.imcode.spring.service.HouseService"/>
<bean id="houseController" class="com.imcode.spring.controller.HouseController">
<!--<constructor-arg index="0" ref="houseService"/>-->
<!--<constructor-arg name="houseService" ref="houseService"/>-->
<constructor-arg type="com.imcode.spring.service.HouseService"
ref="houseService"/>
</bean>
注入集合
编写类
public class CollectionBean {
private Object[] arrayData = new Object[5];
private List<Object> listData;
private Set<Object> setData;
private Map<String, Object> mapData;
private Properties propsData;
}
配置文件
<bean name="collectionBean" class="com.imcode.spring.model.CollectionBean">
<property name="arrayData">
<array>
<value>
100
</value>
</array>
</property>
<property name="listData">
<list>
<value>200</value>
</list>
</property>
</bean>
使用注解
启用注解扫描
添加命名空间,让spring扫描含有注解类
<!-- 启动注解扫描 -->
<context:component-scan base-package="com.imcode.spring"/>
@Component
等价于:
<bean class=""/>
@Component(“name”) 等价于
<bean name="" class=""/>
提供3个 @Component 注解衍生注解(功能一样),分别用于数据访问层,服务层和控制层
@Repository
数据访问层
@Service
服务层
@Controller
控制层
注入简单数据
@Value
注入简单数据类型: @Value("${}")
注入对象
@Autowired
@Qualifier(“name”)
@Autowired 如果注入的是具体类,不会有问题
@Autowired 如果注入的是接口,该接口只有一个实现类,不会有问题
@Autowired 如果注入的是接口,该接口有一个以上的实现类,会有问题,IOC容器不知道应该注入哪个实现类了使用
@Autowired + @Qualifier(“名称”) 组合使用两个注解解决
@Resource
@Resource(name = “bean的名称”)
该注解是JDK提供的注解
@Resource("name") == @Autowired + @Qualifier("name")
@Resource == @Autowired
作用域
注解在类上:
多例:
@Scope(“prototype”)
单例:
@Scope(“singleton”)
初始化和销毁
@PostConstruct
@PreDestroy
SpringTest
引入spring-test依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.10.RELEASE</version>
<scope>test</scope>
</dependency>
测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class SpringTest {
@Autowired
private ProductService productService;
@Test
public void test(){
productService.deleteById(1L);
}
}
@ContextConfiguration("classpath:spring-config.xml")
等价于
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//从容器中获取java对象
MessageService service =
context.getBean("messageService",MessageService.class);
AOP
AOP 是 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
反射技术+代理模式
AOP 术语
- target :目标类,需要被代理的类。
- joinpoint :连接点:是指那些可能被拦截到的方法。
- pointCut : 切入点:已经被增强的连接点 。
- advice :通知/增强,增强代码。
- weaving :织入是指把增强( advice)应用到目标对象( target )来创建新的代理对象 proxy 的过程.
- proxy :代理类
- Aspect : 切面:是切入点 pointcut 和通知/增强 advice 的结合。
pointcut是从target的joinpoint的一部分需要增强的类
再和advice结合形成切面Aspect,
把增强的内容应用到target中来创建新的代理对象的过程是织入
Spring AOP 使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过动态代理方式向目标类织入增强代码。(JDK动态代理 CGLIB)
AspectJ 是一个基于Java语言的AOP框架, Spring2.0 开始, Spring AOP 引入对 Aspect的支持。
@AspectJ 是 AspectJ 1.5 新增功能,通过 JDK5 注解技术,允许直接在Bean类中定义切面,
Spring2.0 框架建议使用 AspectJ 方式来开发 AOP
快速入门
导入jar包
<!--单元测试开始-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.10.RELEASE</version>
<scope>test</scope>
</dependency>
<!--单元测试结束-->
<!--日志 log4j2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<!--spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
</dependencies>
日志配置
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>
目标类(target)
/**
* 目标类
*/
public class UserService implements IUserService{
public void getById() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void deleteById() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
增强类
//增强类
public class TimerAdvice {
public Object around(ProceedingJoinPoint joinPoint){
Object obj = null;
//手动执行目标方法
try {
long t1 = System.currentTimeMillis();
obj = joinPoint.proceed();
long t2 = System.currentTimeMillis();
System.out.println("时长"+ (t2-t1));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return obj;
}
}
配置:
<!--1 目标类-->
<bean name="userService" class="com.imcode.spring.service.UserService">
</bean>
<!--2 增强类-->
<bean name="timerAdvice" class="com.imcode.spring.advice.TimerAdvice"></bean>
<!--3 配置切面-->
<aop:config>
<!--切面形成开始-->
<!--引入增强-->
<aop:aspect ref="timerAdvice">
<!--配置切入点-->
<aop:pointcut id="timerPointCut" expression="execution(* com.imcode.spring.service.UserService.getById(..))"/>
<!--配置通知-->
<aop:around method="MyAround" pointcut-ref="timerPointCut"></aop:around>
</aop:aspect>
<!--切面形成结束-->
</aop:config>
自己写的增强类和配置设置的通知共同形成advice
.
通知是设置增强类在切入点进行增强的时机
切面的形成过程:
1 引入增强类(ref = " ")
2 配置切入点并命名
3 配置通知,通过2步的命名引入切入点。(需要增强类的具体方法)
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class UserServiceTest {
@Autowired
IUserService userService;
@Test
public void getById() {
//userService.getById();
userService.deleteById();
}
}
@Autowired中注入的userService 已经不是目标类的对象了,而是spring自动增强的代理类的对象
JDK动态代理和CGLIB动态代理的区别:
如果代理的目标类有接口,优先JDK
如果没有接口,只能使用CGLIB
因为,JDK的性能优于CGLIB
注解开发
<!-- 启用注解扫描 -->
<context:component-scan base-package="com.imcode.spring"/>
<aop:aspectj-autoproxy proxy-target-class="false"/>
@Component
@Aspect
切点表达式
execution()
execution(修饰符 返回值 包.类.方法名(参数) throws 异常)
execution( * com.imcode.spring.service.*Service.*(..))
方法参数
() 无参
(int) 一个整型参数
(int ,String) 一个整形参数,一个字符串参数
(..) 参数任意
(*) 一个参数任意类型
(*,*) 两个参数参数任意类型
Spring整合Mybatis
引入依赖
关键jar包:
<!-- mybatis 和 spring 整合依赖包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
DataSource交给ioc容器管理:
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/context/spring-context.xsd">
<!--启用注解扫描,扫描含有注解的类-->
<context:component-scan base-package="com.imcode.soufang"/>
<!--加载属性配置文件的内容-->
<context:property-placeholder location="classpath:application.properties"/>
<!--数据源交给spring容器管理-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="initialSize" value="${jdbc.initialSize}"></property>
</bean>
application.properties:
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://127.0.0.1:3306/i-soufang?useSSL=false&serverTimezone=Asia/Shanghai
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.initialSize=5
SqlSessionFactory 会话工厂交给spring容器管理
<!--SqlSessionFactory 会话工厂交给spring容器管理-->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置Mapper映射文件的位置-->
<property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
</bean>
手动实现Mapper接口
Mapper 接口实现类
Repository
public class HouseMapperImpl implements HouseMapper {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Override
public House getById(Integer id) {
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
House house = sqlSession.selectOne("com.imcode.soufang.mapper.HouseMapper.getById",id);
sqlSession.close();
return house;
}
@Override
public void deleteById(Integer id) {
}
}
单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class HouseMapperImplTest {
@Autowired
private HouseMapper houseMapper;
@Test
public void getById() {
House house = houseMapper.getById(1);
System.out.println(house);
}
}
SqlSession 会话交给spring容器管理
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"
scope="prototype">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
动态代理实现Mapper接口
在 spring-config.xml 文件中增加 spring 扫描 Mapper 接口的配置
该配置需要注入会话工厂
该配置需要指定mapper接口所在的包
<!--扫描Mapper接口的配置-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sqlSessionFactory会话工厂-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--配置mapper接口所在的位置-->
<property name="basePackage" value="com.imcode.soufang.mapper"/>
</bean>
使用动态代理实现 Mapper 接口的开发模式,程序员不需要编写接口实现类, spring 框架在扫描到mapper接口后,会使用 jdk 或 cglib 动态代理技术在运行期间帮我们生成 mapper 接口的代理实现类,并初始化代理实现类的对象到 spring 容器中。
Spring 事务管理
配置事务管理器
在 spring-config.xml 文件增加事务管理器的配置
mybatis 使用 jdbc 的事务管理器
<!--配置spring的 jdbc 的事务管理器-->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
增加服务层
/**
* 酒店服务类
*/
@Service
public class HouseService {
@Autowired
private HouseMapper houseMapper;
/**
* 根据酒店id删除酒店信息
* @param id
*/
public void deleteById(Integer id){
houseMapper.deleteById(id);
int i = 100/0;
}
public House getById(Integer id) {
return houseMapper.getById(id);
}
}
基于XML
在spring-config.xml 文件增加事务配置,测试事务是否起作用
<!-- 事务增强/通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--事务切面-->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.imcode.soufang.service.*Service.*
(..))"/>
</aop:config>
基于注解
<!-- aop注解生效 -->
<aop:aspectj-autoproxy/>
<!--启用基于注解的事务管理-->
<tx:annotation-driven transaction-manager="txManager"/>
在需要事务控制的方法或类上增加 @Transactional 注解
可以用在方法和类上
@Service
@Transactional
public class HouseService {
@Autowired
private HouseMapper houseMapper;
// @Transactional
public void deleteById(Integer id){
houseMapper.deleteById(id);
int i = 100/0;
}
@Transactional(readOnly = true)
public House getById(Integer id) {
return houseMapper.getById(id);
}
}
@Transactional 注解使用到类上,类中所有方法都受事务控制
@Transactional 注解使用到方法上,只对该方法进行事务控制
类和方法上都有 @Transactional ,方法上的注解配置覆盖类上的注解配置
事务管理细节
事务管理器
PlatformTransactionManager 用于执行具体的事务操作。事务管理器接口定义:
public interface PlatformTransactionManager{
//获取事务状态
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
//提交事务
void commit(TransactionStatus status)throws TransactionException;
//回滚事务
void rollback(TransactionStatus status)throws TransactionException;
}
传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时我们可以自己设置选项可以指定一个事务性方法的执行行为。
可选设置(常用传播行为):
TransactionDefinition.PROPAGATION_REQUIRED 增加、修改、删除
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起
REQUIRES_NEW的事务执行完毕后,继续执行挂起的事务
TransactionDefinition.PROPAGATION_SUPPORTS 查询 只读事务
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果当前存在事务,则把当前事务挂起
TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED 嵌套事务
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。