介绍 -- 重点
1)概述
2)作用、特点
3)组成
20多个子部分、子模块
每个部分/模块具备不同的功能
主要的相关组成
A、核心容器:core container --- spring-core,spring 框架的基础
spring-beans 是实现对象管理的(工厂)
spring-context 是上下文环境
spring 框架实现的管理的内容(配置文件)
B、spring-aop:面向切面编程思想的实现
C、spring-aspects:整合包,整合其他框架
D、spring-jdbc:相当于mybatis的持久层框架
E、spring-orm:整合包,整合其他持久层框架
F、spring-webmvc:表现层模块,实现表现层功能
G、spring-test:整合包,把单元测试整合
4)spring的官网
Rod Johnson 创始人
通常说的Spring是Spring Framework
5.2.15 和 5.3.8 稳定版 同时维护
5)核心思想 --- IOC 思想 -- 重点
思想的实现涉及到 core container 核心容器,涉及到其中相关子模块
介绍 --- 什么是 IOC
A、IOC --- Inversion of control 控制反转/控制倒置、控制倒转
是指在程序中把对象的管理工作交由第三方容器负责
IOC 实现
A、spring 配置文件 -- 名称任意的,官方使用 applicationContext.xml,保存位置一般类路径中
<!-- 创建对象的配置-->
<!--servlet-->
<!-- id属性标识:唯一性,通过id可以找到类,找到对象-->
<!-- class:设置类的全限定名-->
<!-- 综合起来表示要创建(由容器创建)哪个类的对象,由id对对象进行标识-->
<bean id="helloServlet" class="com.bh.servlet.HelloServlet">
<!-- 把 service 对象给 servlet 对象注入进去-->
<property name="helloService" ref="helloService"></property>
</bean>
<!-- service-->
<bean id="helloService" class="com.bh.service.HelloService"></bean>
// 先有容器 -- 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取对象
HelloServlet hs = (HelloServlet) context.getBean("helloServlet");
HelloService helloService = (HelloService) context.getBean("helloService");
// 测试是否可以正常获取对象
System.out.println(hs);
System.out.println(helloService);
// 测试方法
hs.save();
依赖注入 -- DI
A、servlet 对象需要 service 对象,容器负责把自己创建出来的 service 对象给 servlet 传递过去,这个操作被称为 DI -- 依赖注入
B、Dependency Injection,所谓的依赖注入是指存在依赖关系的两个对象,容器负责把内层对象(被依赖的对象)给外层对象实现注入(传递)
IOC 和 DI 有无关系?如果有,是什么关系?
有关系;IOC 是阐述控制反转概念,侧重于说明容器管理对象,没有指明是哪些关系;DI 是一种动作是一种实现,侧重于进行容器的依赖注入操作,指明是实现依赖的注入管理;实际上 IOC 和 DI 就是一个概念的不同的说明
为什么使用 IOC? 解耦
IOC 的底层原理
利用 dom4j 模拟spring ioc实现
// map对象
private static Map<String,Object> map = new HashMap<>();
static {
try {
// 静态代码块 类加载时读取配置文件,并创建对象
// xml 文档的解析
// 解析对象
SAXReader reader =new SAXReader();
// 读取配置文件
Document document = reader.read("src/main/resources/applicationContext.xml");
// 从文件中解析获取内容 -- 根标签
Element rootElement = document.getRootElement();
// 在获取其他标签
List<Element> beans = rootElement.elements("bean");
// 遍历集合
for (Element element : beans) {
// 获取属性的值
String idValue = element.attributeValue("id");//helloServlet helloService
String classValue = element.attributeValue("class");// com.bh.servlet.HelloServlet com.bh.service.HelloService
// 反射方式造对象 -- 放入map
Class cla = Class.forName(classValue);
// 通过反射造对象
Object o = cla.newInstance();
map.put(idValue,o);// helloServlet+对象
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static Object getBean(String name) {
return map.get(name);
容器读取何种配置,就可以创建对象
三种:无参构造配置方式、静态工厂的配置方式、动态工厂的配置方式
1)无参构造配置
<!-- 无参构造的配置方式,实现对象实例化-->
<!-- cla.newInstance() 就是执行无参构造方法-->
<!-- 此配置方式底层借助的是类的无参构造创建的对象,因此把这种配置方式称为无参构造的配置方式-->
<bean id="str1" class="java.lang.String"></bean>
2)静态工厂
<bean id="calendar" class="java.util.Calendar" factory-method="getInstance"></bean>
3)动态工厂
factory-bean、factory-method
<!-- 先配置动态工厂方法所属对象-->
<bean id="factory" class="com.bh.factory.DynamicFactory"></bean>
<!-- factory-bean 表示通过id获取到一个对象-->
<!-- factory-method表示上面获取到的对象中的指定方法-->
<!-- 此种配置就是容器识别到此配置,那么调用factory-bean获取到的对象,让这个对象调用自己指定方法,最后容器创建出返回值对象-->
<bean id="str3" class="java.lang.String" factory-bean="factory" factory-method="getString"></bean>
小结
bean标签的相关属性:id、calss、factory-method、factory-bean
1)id --- 唯一标识、用于在容器中标志一个对象以及对象所属的类型
2)Class --- 要创建的对象(被容器管理的对象)所属类型
3)factory-method --- 类或者对象要调用的方法(通过此方法调用可以获取到其他对象)
4)factroy-bean --- 通过指定id名称来设置一个对象,容器让此对象调用factory-method指定的方法
5)scope --- 对象的作用范围,值:singleton(默认)、prototype、request、session、globalsession。
A、容器创建的对象特点:singleton此值决定当前对象是一个单例对象{即每次从容器获取的时候都是同一个对象} --- spring在此处使用了单例模式
B、prototype此值决定当前对象不是单例对象,{即每次从容器获取的时候,取出来的都是新的对象}
C、request 和 session 在 web 项目中使用,request 表示容器创建对象在一个request 域中是同一个(在一次请求中每次获取的对象是同一个)
D、session 表示容器创建的对象在一个 session 中是同一个(在一次会话中每次获取的对象是同一个)
6)destroy-method -- 表示在对象销毁之前先调用此属性指定的方法
7)init-method -- 表示在对象创建之后先执行此属性指定的方法进行对象的相关初始化操作
8)lazy-init -- 表示容器何时创建对象:默认是加载配置文件后立即创建对象;设置为true加载配置文件没有立即创建,何时使用何时创建
依赖注入的实现方式有:两种 -- 一种是借助 set 方法注入;另一种是借助有参构造实现注入
借助 set 方法方式实现注入
1)在类中要提供 set 方法、在 bean 标签中使用 property 下级标签 设置 name 和 ref 属性
借助有参构造的方式实现注入
1)在类中提供有参构造方法,在 bean 标签中使用 constructor-arg 下级标签 设置 name 和 ref 属性
小结
1)在 propertity 和 constructor-arg 标签中都含有 name、ref、value 三个属性
2)作用:
A、name 用于设置类中的属性名
B、ref 用于设置 id 标识名称,通过此标识在容器中的对应对象
C、value 用于设置普通的值(Integer、Double、Boolean、character、String)
如果在类中,各种不同形式的属性(一般、实体、数组、集合),如何注入?
1)不同形式属性注入都可采用set/有参构造
2)使用不同的标签和属性实现
<!-- 不同类型属性注入-->
<bean id="helloService2" class="com.bh.service.HelloService">
<!-- 简单类型注入 4类8种-->
<property name="size" value="10"></property>
<property name="size1" value="11"></property>
<property name="length" value="12"></property>
<property name="sho" value="13"></property>
<property name="by" value="14"></property>
<property name="dou" value="15.2"></property>
<property name="flo" value="16.3"></property>
<property name="flag" value="true"></property>
<property name="cha" value="A"></property>
<property name="str" value="abcd"></property>
<!--<property name="date" ref="date"></property>-->
<!-- 类似 xxxx.setDate(new Date())-->
<property name="date">
<bean class="java.util.Date"></bean>
</property>
<property name="helloDao" ref="helloDao"></property>
<property name="ints">
<array>
<value>20</value>
<value>21</value>
<value>22</value>
<value>23</value>
</array>
</property>
<property name="chars">
<array>
<value>a</value>
<value>x</value>
<value>7</value>
</array>
</property>
<property name="strs">
<array>
<value>abcde</value>
<value>opqm</value>
<value>22222</value>
</array>
</property>
<property name="dates">
<array>
<!-- 把已有的对象注入-->
<ref bean="date"></ref>
<bean class="java.util.Date"></bean>
</array>
</property>
<property name="helloDaos">
<array>
<!-- 把已有的对象注入-->
<ref bean="helloDao"></ref>
<bean class="com.bh.dao.HelloDao"></bean>
</array>
</property>
<!-- 集合-->
<property name="integerList">
<list>
<value>30</value>
<value>31</value>
<value>32</value>
</list>
</property>
<property name="integerSet">
<set>
<value>40</value>
<value>41</value>
<value>42</value>
</set>
</property>
<property name="integerMap">
<map>
<entry key="k1" value="1"></entry>
<entry key="k2" value="2"></entry>
<entry key="k3" value="3"></entry>
<entry key="k4" value="4"></entry>
</map>
</property>
<property name="daoList">
<list>
<ref bean="helloDao"></ref>
<bean class="com.bh.dao.HelloDao"></bean>
</list>
</property>
<property name="daoSet">
<set>
<ref bean="helloDao"></ref>
<bean class="com.bh.dao.HelloDao"></bean>
</set>
</property>
<property name="daoMap">
<map>
<entry key="hd1" value-ref="helloDao"></entry>
<entry key="hd2">
<bean class="com.bh.dao.HelloDao"></bean>
</entry>
</map>
</property>
<property name="properties">
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
<prop key="k3">v3</prop>
</props>
</property>
</bean>
实现对象实例化的相关注解 --- 等价 bean 标签
1)@Component、@Controller、@Service、@Repository
2)@Component 相当于父类 其他三个继承,主要的区别是不同层上用不同的注解,方便维护
A、@Controller 用于控制层 -- Servlet层
B、@Service 用于业务层 -- Service层
C、@Repositiory 用于持久层 -- Dao层
D、@Component 除以上三层使用
实现注入的相关注解 --- 等价 property 或者 constructor-arg 标签
1)@Autowired(+@Qualifier)、@Resource、@Value
2)@Autowired和@Resource实现对象注入的,@Value实现值注入的
3)Autowired 自动装配 -- 在容器中通过类型找此类的对象,然后完成注入;@Resource -- 可以通过名称找容器中的对应 id 标识的对象,也可以通过类型找对象
4)单独使用@Autowired,只能通过类型找到对象,如果@Autowired和@Qualifier结合使用,那么可以通过@Qualifier指定名称,此时可以从容器中通过名称找到对应 id 标识的对象,最后实现注入
5)@Value 加载 外部 properties 数据
<!-- 加载properties文件-->
<context:property-placeholder location="classpath:spring.properties"></context:property-placeholder>
<!-- 扫描包:告知spring框架到哪里找有什么注解-->
<!-- base-package 设置包名 扫描包和下级子包-->
<context:component-scan base-package="com.bh">
</context:component-scan>
@Component// 默认标识 --- 类名,首字母小写
public class HelloService {
// 从容器中找此类型 找不到 抛出异常
//@Autowired
// 按属性名id找,找不到 在按类型找
//@Resource(name = "hd")
@Autowired()
private HelloDao helloDao;
@Value("${spring_name}")
private String name;
纯注解的方式没有了配置文件,但是多了一个配置类
@Configuration、@ComponentScan、@PropertySource、@Bean
// 声明是配置类
@Configuration
// 扫描包 <context:component-scan>
@ComponentScan(basePackages = "com.bh")
// 加载properties文件
@PropertySource(value = "classpath:spring.properties")
public class SpringConfig {
// 创建对象的方法
// 将方法返回的对象放入容器,实现容器管理 放哪个类中都可以(灵活)
@Bean
public ModelMap getModelMap() {
return new ModelMap();
}
}
简化测试
如何实现整合
// 注解
// 把当前测试类也放入IOC容器管理
@RunWith(SpringRunner.class)
// 加载xml配置文件
//@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
// 加载配置类
@ContextConfiguration(classes = {SpringConfig.class})
public class SpringTest {
// 也可以实现注入
@Autowired
private HelloService helloService;
@Test
public void test1() {
helloService.method();
}
}
<!-- 动态工厂测试-->
<!-- inputStream 为抽象类 不能配置bean -->
<bean id="inputStream" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
<constructor-arg value="mybatis.xml"></constructor-arg>
</bean>
<bean id="sqlSessionFactoryBuilder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"></bean>
<bean id="factory2" class="org.apache.ibatis.session.SqlSessionFactory" factory-bean="sqlSessionFactoryBuilder" factory-method="build">
<constructor-arg ref="inputStream"></constructor-arg>
</bean>
<bean id="sqlSession" class="org.apache.ibatis.session.SqlSession" factory-bean="factory2" factory-method="openSession"></bean>
// 测试动态工厂
@Test
public void testDynamicFactory() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SqlSession sqlSession = (SqlSession) context.getBean("sqlSession");
System.out.println(sqlSession);
System.out.println("====================");
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user = mapper.selectOne(2);
System.out.println(user);
System.out.println("====================");
}
spring的持久层模块:spring-jdbc
功能:也是对原生 jdbc 的封装,也是实现和数据库交互的,实现增删改查功能,能实现参数传递,能实现结果映射,需要设计实现SQL语句,在程序中写SQL
演示
jdbcTemplate 更新update 查询query (单个queryforObject、多个query)
使用RowMapper的实现类new BeanPropertyRowMapper<对象>(对象.class)封装数据
DriverManagerDateSource 数据源
数据源配置bean Autowired引入
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///mybatis?characterEncoding=utf8"></property>
<property name="username" value="root"></property>
<property name="password" value="admin"></property>
</bean>
设置模式注重模板的设计,把相同的东西封装,便于代码复用。
设计实现
1)定义接口(可有可无)
2)定义抽象类 -- 实现接口 -- 把重复性的代码在类中实现
3)有其他的类继承抽象类的 -- 重写那些需要根据实际情况变化的内容
举例:
定义接口为所有要进行的步骤
定义抽象类实现接口,实现公共的步骤,有区别的步骤用抽象方法定义
定义实现类继承抽象类,自定义实现抽象方法,共有方法抽象类模板已经实现
产品经理需求变动频繁 代码要求灵活
代理方式,设计目的就是为了实现代码的灵活扩展,解决继承时从父类全继承、或者对象和对象之间出现高耦合
原生的代理方式:
1)介绍
一方(对象)代替另一方(对象)实现功能
2)代替方 -- 代理对象 ; 被代替方 -- 目标对象
实现
代理有静态代理的方式和动态代理的方式
Proxy代理
3)静态代理:
缺点
实现多个目标对象 同名方法会冲突
所以一个代理对象一个目标对象,代码多了近乎一倍,代码冗余太多了
4)动态代理:
A、两种动态代理方式 -- jdk动态代理方式、cglib动态代理方式
B、jdk 方式借助 jdk 中提供的类(java.lang.reflect.Proxy)、接口等功能实现;cglib 方式属于框架提供C、jdk 动态代理实现方式:接口、实现类、代理工厂类
回顾反射
// 测试反射
Class cla = Clas.forName("全限定名");
// 创建对象
Object o = cla.newInstance();
// 获取方法
Methid me = cla.getMethod("方法名");
// 调用方法
Object invoke = me.invoke(o);
实现 InvocationHandler 接口
传入目标对象
实现方法 invoke(反射方式调用方法也是invoke)return invoke
invoke(Object proxy,Method method,Object[] args)
获取代理对象的方法 getProxyObject
return Proxy.newProxyInstance(
obj.getClass().getiClassLoader(),
obj.getClass().getInterfaces(),
this)
参数类加载器、接口、实现接口的类的对象
解释
目标接口 -- 接口的实现类 -- Proxy动态代理 工厂动态生成 代理类 并销毁
// 动态 代理工厂
public class ProxyServiceFactory implements InvocationHandler {
private Object obj;
public void setObj(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 相当于目标对象调用功能 -- 原始功能的执行
Object invoke = method.invoke(obj, args);
if("register".equals(method.getName())) {
return invoke;
}
add();
return invoke;
}
public void add() {
System.out.println("添加新的功能.......");
}
// 获取到代理对象的方法
public Object getProxyObject() {
// 第一个参数类加载器
// 第二个参数是接口
// 第三个参数是实现InvocationHandler接口的类的对象 -- 本类对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}
}
5)动态代理和静态代理的区别
静态代理静态类一直存在 动态代理动态类是生成的
6)cglib 动态代理介绍
A、这种代理方式不需要接口,只需要类(项目没有接口,只有类)
spring-context导入就有 cglib
实现 MEthodInterceptor接口
实现 Intercept 方法 (和jdk的invoke类似)
获取代理对象的方法 getProxyObject
Enhancer enhancer = new Enhancer();
// 父类
enhancer.setSupperclass(目标类.class)
// 本类对象
enhancer.setCallBack(this);
//返回代理对象
return enhancer.create();
B、实现
目标类
工厂
测试
public class ProxyServiceFactory implements MethodInterceptor {
private Object obj;
public void setObj(Object obj) {
this.obj = obj;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object invoke = method.invoke(obj, objects);
add();
return invoke;
}
// 新增功能
public void add() {
System.out.println("新增的功能......");
}
// 获取代理对象的方法
public Object getProxyObject() {
// 增强器
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(obj.getClass());
// 设置 回调本类实现MethodInterceptor的类
enhancer.setCallback(this);
// 返回创建的代理对象
return enhancer.create();
}
}
7)动态代理同样存在缺点
jdk 基于接口的动态代理
cglib 基于父类(不是继承)的动态代理
如果目标类有多个功能,实现功能扩展,需要不同功能扩展不同内容,使用动态代理,invike 方法实现较为复杂(调用哪个方法Proxy就动态代理生成哪个方法的代理类)
需要有区别的进行添加,加判断?
method.getName() 判断。。。
如果方法很多 需要写大量的代码
aop 介绍
1)spring 的 aop 实现默认基于的是 jdk 动态代理
导入spring-aspects 导入的其实是aspectj
例
<aop:config>
<aop:aspect ref="">
<aop:pointcut id="" expression="execution(void com.xxx.xxx())"> </aop:pointcut>
<aop:after-returning method="" pointcut-ref="">
</aop:after-returning>
</aop:aspect>
</aop:config>
2)AOP
Aspect Oriented Programming
面向切面编程
是面向对象编程的扩展、补充,实现了松耦合,在开发过程中中专注于切面的开发,切面是由切点和扩展功能组合的模块,是通过预编译方式和运行期动态代理实现程序统一维护设计思想
降低耦合、提高代码可重用性
3)相关术语的介绍
A、切面
是切点和增强的模块化;包含切点和增强(aop:aspect)
B、切点
是指确定向哪些方法实现添加功能的点(定义),如何确定要被扩展的方法呢?通常使用函数和表达式来实现。
函数:execution()
表达式:最终确定的是方法(表达式代表的是方法)
表达式有格式:精确格式、模糊格式(带有符号:*和.)
模糊格式:方法返回值类型、包名、类名、方法名、形参都可以使用符号
// 几种扩展功能
// 指定方法
* com.bh.service.UserServiceImpl.login()
// 所有方法
* com.bh.service.UserServiceImpl.*(..)
// do开头方法
* com.bh.service.UserServiceImpl.do*(..)
// 所有类所有方法
* com.bh.service.*.*(..)
// 所有
*.*.*(..)
C、增强、通知
是指扩展的功能,通常封装成方法
不同的增强方式:前通知(前增强)、后通知(后增强)、环绕通知(环绕增强)、异常后通知(异常后增强)、最终通知(最终增强)
前通知:在原有的功能执行之前先执行的通知
后通知:在原有的功能执行之后执行的通知,如果原有的功能发生异常,那么后通知无法执行
环绕通知:在原有的通知执行之前和执行之后分别都有执行,如果原有的功能发生异常,那么在原有功能执行之后的再次执行就无法执行了
//环绕通知增强
public Object arround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强……");
// 切点方法
Object proceed = pjp.proceed();
System.out.println("环绕后增强……");
return proceed;
}
异常后通知:只有在原有功能发生异常时才执行,如果原有功能没有异常则不执行
最终通知:不论原有功能是否发生异常,在原有功能执行之后都会执行
D、连接点
E、代理
F、目标
G、织入
Aop-- 重点
术语
1)连接点
是指在程序运行过程中的一些特殊的点/位置;在 spring 框架连接点通常指的是程序运行过程中某个方法被调用(如:前通知,执行前那一刻,结合增强在此方法被调用之前或者之后或者抛出异常时这些位置都是连接点)
2)目标
原文being advised 过去被增强
被增强之后的对象(原始功能扩展新的功能之后方法所属的对象)
3)代理
框架创建出来的对象(容器注入属性的对象)
4)织入
把扩展功能向原始功能进行添加,并创建被增强对象的过程
(英文文档便于理解)
纯配置实现
1)ioc 实现原始功能所在类的对象、扩展功能所在类的对象
2)aop:config 标签、aop:aspect 标签、aop:pointCut 标签、aop:before 标签、aop:after-returning 标签、aop:around 标签、aop:after-throwing 标签、aop:after 标签
配置和注解
1)实现 IOC 功能的注解:@Service、@Commponent、@Controller、@Repositiory
2)实现切面、增强(切点)的注解:@Aspect、@Before、@After、@AfterReturning、@AfterThrowing、@Around
3)自动代理
aop:aspect-autoproxy(默认为 jdk 动态代理)
切换cglib 属性 proxy-target-class = true
spring 开发一般用接口,用类少
<!-- 扫描包-->
<context:component-scan base-package="com.bh"></context:component-scan>
<!-- aop自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Component
@Aspect// 定义一个切面 声明当前类是一个切面
// 封装扩展功能 -- 通知
public class AdviceTypeClass {
// 通知
@Before("execution(* com.bh.service..*.*(..))")
public void before() {
System.out.println("before通知!!!");
}
@AfterReturning("execution(* com.bh.service..*.*(..))")
public void after() {
System.out.println("after通知!!!");
}
@Around("execution(* com.bh.service..*.*(..))")
public void round(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("round前通知!!!");
// 原始功能被调用
joinPoint.proceed();
System.out.println("round后通知!!!");
}
@AfterThrowing("execution(* com.bh.service..*.*(..))")
public void exception() {
System.out.println("exception通知!!!");
}
@After(("execution(* com.bh.service..*.*(..))"))
public void last(){
System.out.println("last通知!!!");
}
}
注意:注解时候环绕通知变为最前和最后,xml配置则为原功能紧邻的
纯注解实现
1)相关类上的注解不变(有配置有注解方式中类上面的添加注解)
2)创建配置类(没有了配置文件),类上有声明注解@Configuration、@ComponentScan、自动代理注解@EnableAspectAutoProxy
@Configuration
@ComponentScan(basePackages = {"com.bh"})
@EnableAspectJAutoProxy
public class SpringConfig {
}
分页插件采用 aop 思想
如:mybatis 分页插件 pageHelper(采用 aop 思想)
日志管理采用 aop 思想
如:log4j 日志打印信息 (aop 前通知的思想)
拦截器采用 aop 思想、实现
如:spring-mvc Interceptor
权限验证采用 aop 思想
如:Spring Security 权限验证框架 用户的操作权限
事务管理采用 aop 思想
总结
Spring 框架涉及到的设计模式?
单例模式 scope属性/注解
工厂模式 BeanFactory
代理模式 aop 动态代理
模板模式 jdbcTemplate
什么是事务?
是一个完整的数据库增删改查的执行单元,可能由多条 SQL 语句组成。
事务的特性?
原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability) --ACID
1)原子性:要成功都成功(commit)、要失败都失败(rollback)
2)一致性:事务在执行前后,数据保持不变
3)隔离性:事务和事务之间彼此互不影响
4)持久性:事务在执行前后,对数据的影响是持久的
数据异常
脏读数据、不可重复读数据、幻读数据
1)脏读:读未提交
2)不可重复读:多次读数据不同
3)幻读:插入或者更改造成读取不同
数据异常的处理
事务隔离级别:读未提交、读已提交、可重复读、串行化/序列化
事务的传播性 -- spring实现事务管理时提出的概念
比如:在 service 层的某个方法中存在多个 dao 层方法的调用,service.save(){dao.insert();dao.select();dao.update()},那么传播性就是事务由第一个 insert 方法传播到第二个 select() 再传播到第三个 update 方法;反过来就是把多个方法纳入一个事务管理单元。
保证同一个连接对象(连接对象绑定到线程中ThreadLocal)
spring 中如何实现事务管理?
1)需求:银行中的转账功能的实现
2)实现:
A、创建表、实体类
B、造持久层功能方法
C、造业务层功能
D、相关配置以及事务管理的类
ThreadLocal<> 将连接对象绑定到线程中
queryRunner.query传递链接对象参数,保证事务传播一致性
spring 框架中提供的事务管理的方式的实现
1)纯配置
<!--自定义事务管理类对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--指定某些方法的增强方式-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--<tx:method name="*"/>-->
<!--<tx:method name="account*"/>-->
<!--isolation设置事务隔离级别,default表示默认级别,即数据库是什么级别就按照什么级别处理-->
<!--read-only表示只读,即方法中只有查询操作,可以没有事务管理-->
<!--REQUIRED当account方法被执行的时候,如果其中有多个SQL,那么在执行第一个SQL对应的方法被调用的时候,
如果没有事务则创建事务并把SQL纳入当前事务管理之中;
当第二个SQL对应的方法被调用的时候,如果当前已经存在了事务,那么就把SQL纳入已经存在的事务管理之中-->
<tx:method name="account" isolation="DEFAULT" read-only="false" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--切入-->
<aop:config>
<aop:pointcut id="pointcut1" expression="execution(* com.offcn.service.impl.AccountServiceImpl.account(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"></aop:advisor>
</aop:config>
2)配置和注解
<!-- 开启事务注解驱动-->
<tx:annotation-driven>
@Transactional
3)纯注解
@EnableTransactionManagement
开启事务管理等同于上面标签
整合之后的分工:mybatis 负责持久层的操作,即增删改查;spring 负责 IOC、AOP、事务管理
实现整合的步骤
1)搭建项目、导入依赖
spring依赖、mybatis依赖、整合包、驱动、连接池、日志、分页插件、测试的整合包、单元测试
2)配置 spring 配置文件:扫描包、数据源、事务对象、事务注解驱动、整合 mybatis 的配置
3)准备 mybatis 核心文件(可以没有任何配置)和映射文件(相关功能配置)
工厂配置:SqlSessionFactoryBean实现类
<!--整合mybatis的配置-->
<!--工厂-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--加载mybatis核心配置文件-->
<property name="configLocation" value="classpath:mybatis.xml"></property>
<!--加载映射文件-->
<property name="mapperLocations" value="classpath:mappers/*.xml"></property>
<!--数据源-->
<property name="dataSource" ref="dataSource"></property>
<!--设置别名-->
<property name="typeAliasesPackage" value="com.offcn.pojo"></property>
<!--设置插件-->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置参数,一行配置一个 -->
<value>
helperDialect=mysql
reasonable=true
</value>
</property>
</bean>
</array>
</property>
</bean>
创建代理对象,使用IOC容器管理配置
MapperScannerConfigurer
property name=basePackage
<!--创建代理对象,使用IOC容器管理-->
<!--UserDao ud = session.getMapper(UserDao.class);-->
<!--此配置的内部的执行作用是:当前对象到指定的包中进行扫描,扫描到所有的持久层的字节码文件对象,然后框架底层调用session.getMapper()方法
把每个持久层字节码文件对象作为是参数传入getMapper()方法,进而创建出来所有的持久层接口的代理对象,最后把这些代理对象放入IOC容器-->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--扫描包:持久层接口所在的包-->
<property name="basePackage" value="com.offcn.dao"></property>
</bean>
4)业务层