文章目录
- 一、Spring框架:
- 1、原生web开发中存在哪些问题?
- 2、概念
- 二、Spring架构组成
- 三、自定义工厂
- 1、创建bean.properties
- 2、工厂类
- 3、测试
- 四、Spring环境搭建
- 1、在pom.xml中添加spring常用的依赖
- 2、创建Spring-context.xml配置文件
- 3、利用spring中的bean工厂创建对象
- 3.1 定义目标bean类型
- 3.2 配置spring-context.xml中的bean标签
- 3.3 测试(调用Spring工厂API(ApplicationContext接口))
- 五、IOC 控制反转 【重点】
- 六、DI(依赖注入)【重点】
- 1、setter方法注入(最常用):
- 1.1 定义目标Bean类型
- 1.2 基本类型 + 字符串类型 + 日期类型
- 1.3 容器类型
- 1.4 自建类型
- 2、构造函数注入(了解)
- 3、基于注解注入
- 需要的配置:
- 3.1 使用注解注册bean
- 3.2 装配bean时常用的注解
- 3.2.1 @Resource
- 3.2.2@Autowired
- 3.2.3 @Resource和@Autowired的区别
- 4、p标签(p名称空间)注入(了解)
- 5、自动注入(了解)
- 七、控制简单对象的单例模式/多例模式
- 八、spring中bean对象的生命周期
- 1、饿汉式创建的优势
- 2、生命周期方法
- 实体类中:
- spring-context.xml中:
- 单例模式:
- 多例模式:
- 3、生命周期注解
- 4、生命周期阶段
- 九、Spring+Mybatis【重点】
- 1、需要导入的依赖:
- 2、配置spring-mybatis.xml文件
- 3、剩余操作:
- 3.1 db.properties配置文件
- 3.2 实体类
- 3.3 mapper接口
- 3.4 services接口和实现类
- 3.5 测试
- 3.6 错误解析 | 注意事项 | 附加内容
- 3.6.1 使用@service等注解时
- 3.6.2 引入其他的xml文件
- 3.6.3 其他的数据源
- 3.6.4 Druid连接池可选参数
- 3.6.5 分页插件的使用
- 十、Spring+Mybatis+Servlet【重点】
- 1、需要导入的依赖:
- 2、剩余操作:
- 2.1 创建servlet
- 2.2 错误解析
- 十一、代理设计模式
- 1、概念
- 2、静态代理
- 2.1 概念
- 2.2 实现步骤
- ①UserService接口:
- ②UserServiceImpl实现类:
- ③静态代理模板:
- ④测试:
- ⑤运行结果:
- 3、动态代理
- 3.1 JDK动态代理实现(基于接口)
- ①UserService接口:
- ②UserServiceImpl实现类:
- ③JDK动态代理模板:
- ④测试:
- ⑤运行结果:
- 3.2 CGlib动态代理实现(基于继承)
- ①在pom.xml中添加依赖
- ②UserService接口:
- ③UserServiceImpl实现类:
- ④CGlib动态代理模板:
- ⑤测试:
- 十二、AOP(面向切面编程)【重点】
- 1、概念
- 2、AOP开发术语即解释
- 2.1 通知、增强(Advice):
- 2.2 连接点(Joinpoint):
- 2.3 切入点(Pointcut):
- 2.4 目标对象(Target):
- 2.5 引介(Introduction):
- 2.6 织入(Weaving):
- 2.7 代理(Proxy):
- 2.8 切面(Aspect):
- 3、作用
- 4、开发流程
- 4.1 在pom.xml中添加依赖
- 4.2 定义原始类和接口
- UserService接口:
- UserServiceImpl实现类:
- 4.3 定义通知类
- 4.4 在spring-context.xml中定义bean标签
- 4.5 定义切入点(PointCut),形成切面(Aspect)
- 4.6.1 方式一:使用原生的Spring API接口
- 4.6.2 方式二:自定义切面
- 5、注解开发:
- 十三、事务【重点】
- 1、事务属性
- 1.1 隔离级别
- 1.1.1 概念
- 1.1.2 特性
- 1.1.3 并发问题
- 1.2 传播行为
- 1.3 读写性
- 1.4 事务超时
- 1.5 事务回滚
- 2、具体的实现流程:
- 2.1 在pom.xml中添加依赖
- 2.2 编写相关的mapper接口,xml
- 2.3 编写service层接口和实现类
- 2.4 编写spring-context.xml
- 2.4.1 配置事务管理器
- 2.4.2 配置事务通知
- 2.5 测试
- 3、注解实现事务控制
- 3.1 编写 spring-context.xml 文件
- 3.2 具体操作
- 3.3 注解参数详解
- 十四、Spring-test和junit整合
- 十五、注解开发
- 1、 声明bean
- 2、 注入(DI)
- 3、 事务控制
- 4、注解所需配置
- 5、 AOP开发
- 5.1 注解使用
- 5.2 配置
- 十六、lombok:
- 1、添加依赖
- 2、例子
- 十七、附加:
- 1、BeanFactory接口和ApplicationContext接口,有什么区别(生命周期角度考虑)
- 2. 简述以下SpringIOC
- 3.什么是Spring的AOP
- 4.jdk动态代理和Cglib动态代理的区别
- 5.列出Spring常见的注解,分别阐述其意思
一、Spring框架:
1、原生web开发中存在哪些问题?
- 传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。
- 部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。
- 侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。
2、概念
- Spring是一个项目管理框架,同时也是一套Java EE解决方案。
- Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。
- Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。
- spring是以IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核的分层的Java SE/EE应用 full-stack轻量级开源框架。
二、Spring架构组成
Spring架构由诸多模块组成,可分类为
- 核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
- 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
- 数据访问:事务,DAO支持,JDBC,ORM,封送XML。
- Spring MVC和 Spring WebFlux Web框架。
- 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
- 语言:Kotlin,Groovy,动态语言。
GroupId | ArtifactId | 说明 |
org.springframework | ||
org.springframework | ||
org.springframework | ||
org.springframework | ||
org.springframework | ||
org.springframework | ||
org.springframework | ||
org.springframework | spring-instrument | JVM 引导的仪表(监测器)代理 |
org.springframework | spring-instrument-tomcat | Tomcat 的仪表(监测器)代理 |
org.springframework | spring-jdbc | 支持包括数据源设置和 JDBC 访问支持 |
org.springframework | spring-jms | 支持包括发送/接收JMS消息的助手类 |
org.springframework | spring-messaging | 对消息架构和协议的支持 |
org.springframework | spring-orm | 对象/关系映射,包括对 JPA 和 Hibernate 的支持 |
org.springframework | spring-oxm | 对象/XML 映射(Object/XML Mapping,OXM) |
org.springframework | spring-test | 单元测试和集成测试支持组件 |
org.springframework | spring-tx | 事务基础组件,包括对 DAO 的支持及 JCA 的集成 |
org.springframework | spring-web | web支持包,包括客户端及web远程调用 |
org.springframework | spring-webmvc | REST web 服务及 web 应用的 MVC 实现 |
org.springframework | spring-webmvc-portlet | 用于 Portlet 环境的MVC实现 |
org.springframework | spring-websocket | WebSocket 和 SockJS 实现,包括对 STOMP 的支持 |
org.springframework | spring-jcl | Jakarta Commons Logging 日志系统 |
三、自定义工厂
不使用Spring提供IOC容器 (Container),通过自定义的工厂来实现对象的创建
1、创建bean.properties
userService=com.mxd.service.impl.UserServiceImpl
2、工厂类
public class MyFactory {
//公共的访问方法
public static Object getBean(String beanName) throws Exception {
//读取properties文件中的信息
InputStream inputStream =
MyFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//创建属性集合列表
Properties properties = new Properties();
properties.load(inputStream);
//获取值(全类名)
String userServiceClass = properties.getProperty(beanName);
//反射获取类对象
Class clazz = Class.forName(userServiceClass);
//创建实例
Object obj = clazz.newInstance();
return obj;
}
}
3、测试
public void test1() throws Exception {
UserService userService = (UserService) MyFactory.getBean("userService");
String name = userService.getName();
System.out.println(name);
}
四、Spring环境搭建
1、在pom.xml中添加spring常用的依赖
<!--spring常用的依赖:spring-context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
2、创建Spring-context.xml配置文件
命名无限制,常用的名称有:spring-context.xml、applicationContext.xml、beans.xml
<?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>
3、利用spring中的bean工厂创建对象
3.1 定义目标bean类型
接口:
public interface UserService {
String getName();
}
实现类:
public class UserServiceImpl implements UserService {
@Override
public String getName() {
return "你好Spring!";
}
}
3.2 配置spring-context.xml中的bean标签
<!-- spring管理bean对象
bean标签 id属性: 标识符,必须唯一
举例: 接口名字:UserService- id标识符 "userService"
class属性: 业务层或者控制器层/dao层接口实现类的全限定名称
-->
<bean id="userService" class="com.mxd.service.impl.UserServiceImpl"></bean>
3.3 测试(调用Spring工厂API(ApplicationContext接口))
/**
** 程序中的对象都交由Spring的ApplicationContext工厂进行创建。
**/
@Test
public void test1(){
//1. 读取配置文件中所需创建的bean对象,并获得工厂对象
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-config.xml");
//2. 通过id获取bean对象
UserService userService = (UserService) applicationContext.getBean("userService");
//通过 当前类型.class 获取bean对象
//UserService userService = (UserService) applicationContext.getBean(UserService.class);
//3. 使用对象
String name = userService.getName();
System.out.println(name);
}
五、IOC 控制反转 【重点】
反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转),解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健。
IOC反转控制:程序之间解耦
在类和类之间存在控制权,控制权指的是对象的创建和使用
比如有类A和类B,我们之前的做法是在A中调用B,那么控制权就在A中,这样做的耦合度较高,如果修改了B,A也要做相应修改。引入Spring框架后,控制权由spring容器来负责。当A想使用B时,需要由Spirng容器通过配置文件进行注入。这种思想就是IoC。
(为了更好的理解,我们可以这样认为,对象创建和使用的控制权转移到了Spring容器,由Spring容器来控制)。
六、DI(依赖注入)【重点】
在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
常用的依赖注入有三种:setter注入,构造方法注入,基于注解的注入
1、setter方法注入(最常用):
创建对象时,Spring工厂会通过Set方法为对象的属性赋值。
1.1 定义目标Bean类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private Date birthday;
private String[] hobby;
private Set<String> phones;
private List<String> names;
private Map<String,String> countries;
private Properties files;
}
1.2 基本类型 + 字符串类型 + 日期类型
<bean id="user" class="com.mxd.pojo.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
<property name="age" value="66"/>
<property name="birthday" value="2020/11/11"/><!--注意格式"/"-->
</bean>
1.3 容器类型
<bean id="user" class="com.mxd.pojo.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
<property name="age" value="66"/>
<property name="birthday" value="2020/11/11"/>
<!--数组-->
<property name="hobby">
<array>
<value>篮球</value>
<value>足球</value>
<value>羽毛球</value>
</array>
</property>
<!--set集合-->
<property name="phones">
<set>
<value>17722223333</value>
<value>15366668888</value>
<value>1399997777</value>
</set>
</property>
<!--list集合-->
<property name="names">
<list>
<value>mxd</value>
<value>tlx</value>
<value>zy</value>
</list>
</property>
<!--Map集合-->
<property name="countries">
<map>
<entry key="china" value="中国"></entry>
<entry key="uk" value="英国"></entry>
<entry key="us" value="美国"></entry>
</map>
</property>
<!--propertie文件-->
<property name="files">
<props>
<prop key="one">一</prop>
<prop key="two">二</prop>
<prop key="three">三</prop>
</props>
</property>
</bean>
1.4 自建类型
定义目标bean类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TbUser {
private Integer id;
private String name;
private Integer age;
private TbUserInfo tbUserInfo;
}@Data
@AllArgsConstructor
@NoArgsConstructor
public class TbUserInfo {
private String sex ; //性别
private String phone ;//电话
private String address ;//地址
private Integer addrId ; //门牌号
}
配置spring-context.xml
<bean id="tbUser" class="com.mxd.pojo.TbUser">
<property name="id" value="1"/>
<property name="name" value="李四"/>
<property name="age" value="99"/>
<property name="tbUserInfo" ref="tbUserInfo"/><!--用ref属性引用-->
</bean>
<bean id="tbUserInfo" class="com.mxd.pojo.TbUserInfo">
<property name="sex" value="男"/>
<property name="phone" value="119"/>
<property name="address" value="南窑头"/>
<property name="addrId" value="999"/>
</bean>
2、构造函数注入(了解)
创建对象时,Spring工厂会通过构造方法为对象的属性赋值,必须要有构造函数
<!--构造函数注入,基本和set方法一致-->
<bean id="user" class="com.mxd.pojo.User">
<constructor-arg name="id" value="22"/>
<constructor-arg name="name" value="王五"/>
<constructor-arg name="age" value="66"/>
<constructor-arg name="birthday" value="2021/12/7"/><!--注意时间格式-->
<!--数组-->
<constructor-arg name="hobby" >
<array>
<value>打篮球</value>
<value>踢足球</value>
<value>打台球</value>
</array>
</constructor-arg>
<!--set集合-->
<constructor-arg name="phones">
<set>
<value>17722223333</value>
<value>15366668888</value>
<value>1399997777</value>
</set>
</constructor-arg>
<!--list集合-->
<constructor-arg name="names">
<list>
<value>mxd</value>
<value>tlx</value>
<value>zy</value>
</list>
</constructor-arg>
<!--Map集合-->
<constructor-arg name="countries">
<map>
<entry key="china" value="中国"></entry>
<entry key="uk" value="英国"></entry>
<entry key="us" value="美国"></entry>
</map>
</constructor-arg>
<!--propertie文件-->
<constructor-arg name="files">
<props>
<prop key="one">一</prop>
<prop key="two">二</prop>
<prop key="three">三</prop>
</props>
</constructor-arg>
</bean>
3、基于注解注入
需要的配置:
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.mxd"></context:component-scan>
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>
3.1 使用注解注册bean
有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
(1)@Component:可以用于注册所有bean
(2)@Repository:主要用于注册dao层的bean
(3)@Service:主要用于注册业务层的bean
(4)@Controller:主要用于注册控制层的bean
这些都是注解在平时的开发过程中出镜率极高,@Component、@Repository、@Service、@Controller实质上属于同一类注解,用法相同,功能相同,区别在于标识组件的类型。
@Component可以代替@Repository、@Service、@Controller,因为这三个注解是被@Component标注的。
注:注册bean就是装配到Spring容器中,表示这个类的创建、销户交由spring进行管理,但这时bean可能并没有被创建。
3.2 装配bean时常用的注解
3.2.1 @Resource
jdk中定义的注解
①@Resource是默认以byName(名字)的方式去匹配与属性名相同的bean的id,同时可以指定name属性。
②既不指定name属性,也不指定type属性时,则自动按byName方式先进行查找,如果没有找到符合的bean,则回退为一个原始类型(type)进行查找,如果找到就注入,找不到就会抛出异常
3.2.2@Autowired
spring中提供的注解
①使用@Autowired注解来自动装配指定的bean。
②默认按照类型(type)注入,如果需要按照名称搜索,则需要借助于@Qualifier
@Qualifier(value="filmTypeService")
@Autowired
③在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
④@Autowired属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值。
注:装配bean就是创建对象,依赖注入(DI)
3.2.3 @Resource和@Autowired的区别
相同点:
@Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。
不同点:
(1)提供方
@Autowired是Spring的注解;@Resource是javax.annotation注解,而是来自于JSR-250,J2EE提供,需要JDK1.6及以上。
(2)注入方式
@Autowired默认按照Type 注入;@Resource默认按Name自动注入,也提供按照Type 注入;
(3)属性
@Autowired注解可用于为类的属性、构造器、方法进行注值。
默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其required属性为false。
还有一个比较重要的点就是,@Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合@Qualified注解进行限定,指定注入的bean名称。
@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。
注:
@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
@Resource注解的使用性更为灵活,可指定名称,也可以指定类型;
@Autowired注解进行装配容易抛出异常,特别是装配的bean类型有多个的时候,而解决的办法是需要在增加@Qualitied进行限定。一般@Autowired和@Qualifier一起用,@Resource单独用。
4、p标签(p名称空间)注入(了解)
<bean id="tbUser" class="com.mxd.pojo.TbUser"
p:id="22" p:name="哈哈" p:age="33" p:tbUserInfo-ref="tbUserInfo">
</bean>
<bean id="tbUserInfo" class="com.mxd.pojo.TbUserInfo">
<property name="sex" value="女"/>
<property name="phone" value="110"/>
<property name="address" value="山西"/>
<property name="addrId" value="222"/>
</bean>
5、自动注入(了解)
<bean id="user" class="com.mxd.pojo.User" autowire="byName">
</bean>
autowire属性有5种模式:
(1)no
(默认)不采用autowire机制.。这种情况,当我们需要使用依赖注入,只能用ref属性。
(2)byName
通过属性的名称自动装配(注入)。Spring会在容器中查找名称与bean属性名称一致的bean,并自动注入到bean属性中。当然bean的属性需要有setter方法。例如:bean A有个属性master,master的setter方法就是setMaster,A设置了autowire=“byName”,那么Spring就会在容器中查找名为master的bean通过setMaster方法注入到A中。
(3)byType
通过类型自动装配(注入)。Spring会在容器中查找类(Class)与bean属性类一致的bean,并自动注入到bean属性中,如果容器中包含多个这个类型的bean,Spring将抛出异常。如果没有找到这个类型的bean,那么注入动作将不会执行。
(4)constructor
类似于byType,但是是通过构造函数的参数类型来匹配。假设bean A有构造函数A(B b, C c),那么Spring会在容器中查找类型为B和C的bean通过构造函数A(B b, C c)注入到A中。与byType一样,如果存在多个bean类型为B或者C,则会抛出异常。但时与byType不同的是,如果在容器中找不到匹配的类的bean,将抛出异常,因为Spring无法调用构造函数实例化这个bean。
(5)default
采用父级标签(即beans的default-autowire属性)的配置。
七、控制简单对象的单例模式/多例模式
在spring-context.xml中:
<!--
singleton(默认):每次调用工厂,得到的都是同一个对象。单例模式
prototype:每次调用工厂,都会创建新的对象。多例模式
-->
<bean id="user" class="com.mxd.pojo.User" scope="prototype">
</bean>
- 注意:需要根据场景决定对象的单例、多例模式。
- 可以共用:Service、DAO、SqlSessionFactory(或者是所有的工厂)。
- 不可共用:Connection、SqlSession、ShoppingCart。
八、spring中bean对象的生命周期
1、饿汉式创建的优势
工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。
提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)
注:单例:始终只有一个对象 。
饿汉式:类一加载就创建对象
懒汉式 :需要使用的时候才创建对象
2、生命周期方法
实体类中:
- 自定义无参构造:
public User(){
System.out.println("对象被创建");
}
- 自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。
public void init(){
System.out.println("对象初始化");
}
- 自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
public void destroy(){
System.out.println("对象销毁");
}
- 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
- 分类:singleton对象由Spring容器直接销毁、prototype对象由JVM销毁。
spring-context.xml中:
单例模式:
<bean id="user" class="com.mxd.pojo.User" init-method="init" destroy-method="destroy">
</bean>
测试:
public void test3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
User user = (User) applicationContext.getBean("user");
applicationContext.close();//调用close方法之后才会被销毁
}
输出结果:
对象被创建 对象初始化 对象销毁
多例模式:
<!--lazy-init(延时加载):当使用多例模式时,需要使用的时候才创建对象 初始化过程需要懒加载-->
<bean id="user" class="com.mxd.pojo.User" init-method="init"
destroy-method="destroy" scope="prototype" lazy-init="true">
</bean>
测试:
public void test3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
User user = (User) applicationContext.getBean("user");
applicationContext.close();//多例模式中销毁时由JVM直接销毁
}
输出结果:
对象被创建 对象初始化
3、生命周期注解
@PostConstruct //初始化注解
public void init(){
System.out.println("对象初始化");
}
@PreDestroy //销毁注解
public void destroy(){
System.out.println("对象销毁");
}
@Scope("singleton")//默认单例模式,依赖范围的注解,prototype:多例
//@Primary 如果有多个实现类,这个注解标记的哪个类上,就默认使用当前这个接口的实现类去使用的
4、生命周期阶段
**单例bean:**singleton
随工厂启动创建 → 构造方法 → set方法(注入值) → init(初始化) → 构建完成 → 随工厂关闭销毁
**多例bean:**prototype
被使用时创建 → 构造方法 → set方法(注入值) → init(初始化) → 构建完成 → JVM垃圾回收销毁
九、Spring+Mybatis【重点】
1、需要导入的依赖:
<!--spring和mybatis整合必须要导入的两个依赖-->
<!--1.mybatis提供和spring无缝整合的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<!--2.spring的jdbc的jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--*********************************************************-->
<!--spring常用依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!--分页插件(根据需求选择)-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
2、配置spring-mybatis.xml文件
<?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">
<!--
1、 加载properties文件中数据库参数信息
2、创建数据源对象(有好几种:①spring-jdbc中的数据源 ②mybatis中的数据源 ③druid中的数据源)
3、创建SqlSessionFactory对象 SqlSessionFactoryBean对象
4、配置mapper的扫描器对象
-->
<!--1.加载properties文件中数据库参数信息(输入<property...就会有提示了)-->
<context:property-placeholder location="classpath:db.properties"/>
<!--2.创建数据源对象(有好几种)-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}" />
</bean>
<!--3.创建SqlSessionFactory对象 SqlSessionFactoryBean对象-->
<bean id="sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置注入的数据源-->
<property name="dataSource" ref="dataSource" />
<!-- 配置mybatis映射文件的路径,如果用注解了这个就要注释-->
<property name="mapperLocations" value="classpath:com/mxd/mapper/*Mapper.xml" />
<!-- 配置实体的别名包扫描-->
<property name="typeAliasesPackage" value="com.mxd.pojo" />
<!--提供分页插件,如果配置了这个插件,就必须要进行分页查询,不然会抛异常-->
<!--<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor"></bean>
</array>
</property>-->
</bean>
<!--4.spring与mybatis整合:
配置mapper的扫描器对象 管理DAO实现类的创建,并创建DAO对象,存入工厂管理
mapper/dao实现对象在工厂中的id是:“首字母小写的-接口的类名”
mybatis提供的这个和spring整合的依赖中MapperScannerConfigurer,Mapper的配置扫描器必须配置这个类
他的id必须mapperScannerConfigurer,否则报错
-->
<bean id="mapperScannerConfigurer"
class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<!-- 必须配置扫描器数据源对象-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<!-- 配置接口扫描器扫描的默认包路径,然后交给spring -->
<property name="basePackage" value="com.mxd.mapper"/>
</bean>
</beans>
3、剩余操作:
3.1 db.properties配置文件
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/testdata?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=000000
3.2 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
private Integer deptno;
private String dname;
private String loc;
}
3.3 mapper接口
public interface DeptMapper {
List<Dept> findAll();
}
3.4 services接口和实现类
public interface DeptService {
List<Dept> findAll();
}//使用@Service注册bean
@Service
public class DeptServieImpl implements DeptService {
//使用@Autowired注入
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> findAll() {
return deptMapper.findAll();
}
}
3.5 测试
@Test
public void test1(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-context.xml");
DeptService bean = applicationContext.getBean(DeptService.class);
List<Dept> all = bean.findAll();
System.out.println(all);
}
3.6 错误解析 | 注意事项 | 附加内容
3.6.1 使用@service等注解时
Spring容器在运行的时候,怎么知道从哪个包扫描呢?需要在spring的xml文件中添加
<!--
在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,
如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean
注意:如果配置了<context:component-scan>那么<context:annotation-config/>标签
就可以不用再xml中配置了,因为前者包含了后者。
-->
<context:component-scan base-package="com.mxd.service"/>
3.6.2 引入其他的xml文件
可以通过下列标签引入其他的xml文件
<import resource="classpath:spring-mybatis.xml"></import>
3.6.3 其他的数据源
<!--与PooledDataSource集成(mybatis)-->
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!--与DruidDataSource集成(druid)-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--基本配置-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
使用druid数据源时,要导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
3.6.4 Druid连接池可选参数
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--基本配置-->
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.init}"/>
<property name="minIdle" value="${jdbc.minIdle}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>
3.6.5 分页插件的使用
在service层的实现类中:
@Override
public PageInfo<Shop> findShopByPage(Integer page, Integer size) {
//设置页面,每页显示条数
PageHelper.startPage(page,size) ;
List<Shop> list = shopMapper.findAll();
//创建PageInfo<Shop>对象
PageInfo<Shop> pageInfo = PageInfo.of(list);
return pageInfo;
}
测试类:
@Test
public void test1(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-config.xml");
//创建service对象,按照类型创建
ShopService shopService = applicationContext.getBean(ShopService.class);
PageInfo<Shop> pageInfo = shopService.findShopByPage(1, 2);
List<Shop> list = pageInfo.getList();
System.out.println(list);
System.out.println(pageInfo);
}
十、Spring+Mybatis+Servlet【重点】
在上面spring+mybatis整合的基础上完成下列操作
1、需要导入的依赖:
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope><!--不发布-->
</dependency>
<!--fastjson,json解析工具-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
2、剩余操作:
2.1 创建servlet
@WebServlet("/findAll")
public class DeptController extends HttpServlet {
private DeptService deptService;
//为了防止空指针
public DeptController(){
//创建Spring的ioc容器
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-context.xml");
deptService = applicationContext.getBean(DeptService.class);
System.out.println(deptService);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理返回页面的乱码问题方法①:
resp.setContentType("text/html;charset=utf-8");
//处理返回页面的乱码问题方法②:
/*resp.setContentType("application/json");
resp.setCharacterEncoding("utf-8");*/
List<Dept> all = deptService.findAll();
System.out.println(all);//在控制台输出查看一下
String res = JSONObject.toJSONString(all);
PrintWriter pw = resp.getWriter();
pw.write(res);
pw.flush();
pw.close();
}
}
2.2 错误解析
如果发送请求时出现空指针异常,检查注入问题,是否未注入
十一、代理设计模式
1、概念
将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。
2、静态代理
2.1 概念
代理角色和真实角色都需要实现同一个接口
通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。
- 代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法。
- 静态代理的问题
- 代理类数量过多,不利于项目的管理。
- 多个代理类的辅助功能代码冗余,修改时,维护性差。
2.2 实现步骤
①UserService接口:
public interface UserSrervice {
//添加用户
void addUser();
}
②UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
@Override
public void addUser() {
System.out.println("UserServiceImpl:添加了一个用户");
}
}
③静态代理模板:
public class StaticProxy implements UserService {
private UserService userService;
//构造为属性初始化
public StaticProxy(UserService userService){
this.userService = userService;
}
@Override
public void addUser() {
System.out.println("staticproxy: 权限校验!");
//调用业务层的方法
userService.addUser();
System.out.println("staticproxy: 生成日志!");
}
}
④测试:
@Test
public void staticProxyTest(){
//最初的使用方式,真实角色
UserService userService = new UserServiceImpl();
userService.addUser();
System.out.println("****************************");
//创建静态代理对象,业务能力增强(代理角色)
StaticProxy staticProxy = new StaticProxy(userSrervice);
staticProxy.addUser();
}
⑤运行结果:
UserServiceImpl:添加了一个用户
****************************
staticproxy: 权限校验!
UserServiceImpl:添加了一个用户
staticproxy: 生成日志!
3、动态代理
动态创建代理类的对象,为原始类的对象添加辅助功能。
3.1 JDK动态代理实现(基于接口)
基于接口实现 ,通过反射的方式,执行代理处理程序的时候就获取了代理对象
①UserService接口:
public interface UserSrervice {
//添加用户
void addUser();
}
②UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
@Override
public void addUser() {
System.out.println("UserServiceImpl:添加了一个用户");
}
}
③JDK动态代理模板:
public class JdkProxy implements InvocationHandler {//java.lang.reflect.InvocationHandler
//声明被代理接口
private Object target;
//注入
public JdkProxy(Object target) {
this.target = target;
}
//处理代理实例,调用被代理对象的方法!
/*
Object proxy:具体的代理类对象,其中有被代理接口的方法!
Method method:被调用的方法
Object[] args:被调用方法的参数
*/
//InvocationHandler接口是动态代理的桥梁
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JdkProxy: 权限校验!");
//调用被代理对象的方法
Object obj = method.invoke(target, args);
System.out.println("JdkProxy: 生成日志!");
return obj;
}
}
④测试:
@Test
public void jdkProxyTest(){
//最初的使用方式,真实角色
UserService userSrervice1 = new UserServiceImpl();
userSrervice1.addUser();
System.out.println("****************************");
JdkProxy jdkProxy = new JdkProxy(userSrervice1);
/* 用代理类,创建代理实例
参数为:
1.类加载器ClassLoader loader
2.被代理的接口 Class<?>[] interfaces
3.调用处理器对象 InvocationHandler h
不能强转为实现类,因为不能基于类实现,只能基于接口实现
*/
//Proxy类是动态代理的入口
UserService UserService2 = (UserService) Proxy.newProxyInstance(
jdkProxy.getClass().getClassLoader(),
userSrervice1.getClass().getInterfaces(),
jdkProxy);
//调用业务方法
UserService2.addUser();
}
⑤运行结果:
UserServiceImpl:添加了一个用户
****************************
JdkProxy: 权限校验!
UserServiceImpl:添加了一个用户
JdkProxy: 生成日志!
3.2 CGlib动态代理实现(基于继承)
①在pom.xml中添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
②UserService接口:
public interface UserSrervice {
//添加用户
void addUser();
}
③UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
@Override
public void addUser() {
System.out.println("UserServiceImpl:添加了一个用户");
}
}
④CGlib动态代理模板:
public class CglibProxy implements InvocationHandler {//net.sf.cglib.proxy.InvocationHandler
//代理的目标对象
private Object target;
public Object createProxyInstance(Object target){
this.target=target;
//创建目标对象的代理对象
//1.生成代理对象
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调 对象:代表当前类对象地址值引用 this
enhancer.setCallback(this);
//4.创建当前代理角色对象
Object obj = enhancer.create();
return obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("CglibProxy: 权限校验!");
//调用被代理对象的方法
Object obj = method.invoke(target, args);
System.out.println("CglibProxy: 生成日志!");
return obj;
}
}
⑤测试:
@Test
public void cglibProxyTest(){
//最初的使用方式,真实角色
UserService userSrervice1 = new UserServiceImpl();
userSrervice1.addUser();
System.out.println("****************************");
CglibProxy cglibProxy = new CglibProxy();
//基于子类,实现类
UserServiceImpl userService2 =
(UserServiceImpl) cglibProxy.createProxyInstance(userSrervice1);
userService2.addUser();
}
十二、AOP(面向切面编程)【重点】
1、概念
AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。对逻辑业务方法和非业务方法进行分离。对逻辑业务方法和非业务方法进行分离。
注:目前上面叙述的代理方式都可以实现针对业务方法进行增强,而且JDK动态代理/CGlib动态代理将增强代码和业务方法直接分离了,但是写法很麻烦,Spring提供一个核心AOP :面向切面编程 基于OOP(面向对象)
2、AOP开发术语即解释
2.1 通知、增强(Advice):
就是你想要的功能,例如安全,事物,日志等。先定义好,然后在想用的地方用一下。分为:前置通知、后置通知、异常通知、最终通知、环绕通知等。
2.2 连接点(Joinpoint):
连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectj还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
2.3 切入点(Pointcut):
被Spring切入连接点。上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对吧,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
2.4 目标对象(Target):
代理的目标对象。引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,它可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
2.5 引介(Introduction):
一种特殊的增强,可在运行期为类动态添加Field和Method。
允许我们向现有的类添加新方法属性。就是把切面(也就是新方法属性:通知定义的)用到目标类中
2.6 织入(Weaving):
把通知应用到具体的类,进而创建新的代理类的过程。
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时
2.7 代理(Proxy):
被AOP织入通知后,产生的结果类。
2.8 切面(Aspect):
由切入点和通知组成,将横切逻辑织入切面所指定的连接点中。
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切入点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
3、作用
Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。
就是为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等
4、开发流程
4.1 在pom.xml中添加依赖
<!--spring常用依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring切面jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
4.2 定义原始类和接口
UserService接口:
public interface UserSrervice {
//添加用户
void addUser();
}
UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
@Override
public void addUser() {
System.out.println("UserServiceImpl:添加了一个用户");
}
}
4.3 定义通知类
我们这里只利用一个通知做介绍,其余都差不多
//前置通知
public class BeforeHandler implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知:执行业务方法之前");
}
}
4.4 在spring-context.xml中定义bean标签
<!--service层bean对象-->
<bean id="userServiceImpl" class="com.mxd.service.impl.UserServiceImpl"/>
<!--前置通知-->
<bean id="beforeHandler" class="com.mxd.aop.BeforeHandler"/>
4.5 定义切入点(PointCut),形成切面(Aspect)
4.6.1 方式一:使用原生的Spring API接口
<!--pointcut:切点
使用切点表达式
execution()
execution(* *.*.service.impl.*.*(..))
第一个* 是默认的 和第二个*必须有空格,必须写
第二个* :就是com
第三个* :mxd
第四个:* :一般不用写*,要写为明确给业务层的包名service
第五个:* :一般service下的impl包
第五个*:impl包下的实现类
第六个*:类中方法名,也可以用*代替
(..):不明确方法的参数时在括号里点两个点
-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut" expression="execution(* *.*.service.*.*.addUser(..))"/>
<!--如果方法名唯一可以像下面这样写,但是不建议写-->
<!--<aop:pointcut id="pointcut" expression="execution(* addUser(..))"/>-->
<!--组装切面-->
<aop:advisor advice-ref="beforeHandler" pointcut-ref="pointcut"/>
</aop:config>
通知类接口:
前置通知:MethodBeforeAdvice
后置通知:AfterAdvice
后置通知(最终通知):AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值
异常通知:ThrowsAdvice
环绕通知:MethodInterceptor
4.6.2 方式二:自定义切面
①自定义切面:
public class MyAspect {
public void before() {
System.out.println("自定义前置通知!");
}
public void after(){
System.out.println("自定义后置通知!");
}
}
②spring-context.xml中配置:
<bean id="myAspect" class="com.mxd.aop.MyAspect"/>
<aop:config>
<!--自定义切面,注意要写ref属-->
<aop:aspect ref="myAspect">
<!--切点-->
<aop:pointcut id="pointcut" expression="execution(* *.*.*.*.*.addUser(..))"/>
<!--前置通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
③测试:
@Test
public void aopTest(){
//最初的使用方式,真实角色
UserService userSrervice1 = new UserServiceImpl();
userSrervice1.addUser();
System.out.println("****************************");
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-context.xml");
UserService userService =
applicationContext.getBean("userServiceImpl", UserService.class);
userService.addUser();
}
④运行结果:
UserServiceImpl:添加了一个用户
****************************
自定义前置通知!
UserServiceImpl:添加了一个用户
自定义后置通知!
5、注解开发:
①自定义切面
@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
// 定义切入点
@Pointcut("execution(* com.mxd.service.impl.UserServiceImpl.*(..))")
public void pc(){}
@Before("pc()") // 前置通知
public void mybefore(JoinPoint a) {
System.out.println("target:"+a.getTarget());
System.out.println("args:"+a.getArgs());
System.out.println("method's name:"+a.getSignature().getName());
System.out.println("before~~~~");
}
@AfterReturning(value="pc()",returning="ret") // 后置通知
public void myAfterReturning(JoinPoint a,Object ret){
System.out.println("after~~~~:"+ret);
}
@AfterThrowing(value="pc()",throwing="ex") // 异常通知
public void myThrows(JoinPoint jp,Exception ex){
System.out.println("throws");
System.out.println("===="+ex.getMessage());
}
@Around("pc()") // 环绕通知(使用around时,将其他的注解注释了)
public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
//是要将所有的通知关联进来
Object target = null ; //代理角色
try{
System.out.println("执行业务方法前,前置通知") ;
target= p.proceed();//执行底层业务方法
System.out.println("执行业务方法之后的,后置通知");
return target;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("执行异常通知..."+throwable.getMessage());
}finally {
System.out.println("after..通知");
}
}
}
②配置spring-context.xml文件:
<!-- 添加如下配置,启用aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
十三、事务【重点】
1、事务属性
1.1 隔离级别
1.1.1 概念
isolation
隔离级别
名称 | 描述 |
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
1.1.2 特性
- 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
- 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
1.1.3 并发问题
事务并发时的安全问题
问题 | 描述 |
脏读 | 一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止 |
不可重复读 | 一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止 |
幻影读 | 一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止 |
1.2 传播行为
propagation
传播行为当涉及到事务嵌套(Service调用Service)时,可以设置:
- SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
- REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
1.3 读写性
readonly
读写性
- true:只读,可提高查询效率。(适合查询)
- false:可读可写。 (默认值)(适合增删改)
1.4 事务超时
timeout
事务超时时间当前事务所需操作的数据被其他事务占用,则等待。
- 100:自定义等待时间100(秒)。
- -1:由数据库指定等待时间,默认值。(建议)
1.5 事务回滚
rollback-for
回滚属性
- 如果事务中抛出 RuntimeException,则自动回滚
- 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
- 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for=“Exception”
2、具体的实现流程:
2.1 在pom.xml中添加依赖
<!--提供spring的事务支持的jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2.2 编写相关的mapper接口,xml
@Repository
public interface BankMapper {
void subMoney(Bank bank);
void addMoney(Bank bank);
}<update id="subMoney" parameterType="com.mxd.pojo.Bank">
update bank
set money=money - #{money}
where id = #{id}
</update>
<update id="addMoney" parameterType="com.mxd.pojo.Bank">
update bank
set money=money + #{money}
where id = #{id}
</update>
2.3 编写service层接口和实现类
public interface BankService {
void transfer_accounts(int id1,int id2,float price);
}@Service
@Transactional
public class BankServiceImpl implements BankService {
@Autowired
private BankMapper bankMapper;
@Override
@Transactional(propagation = Propagation.MANDATORY)
public void transfer_accounts(int id1,int id2,float price) {
//转账人,减去金额
Bank bank1 = new Bank();
bank1.setId(id1);
bank1.setMoney(price);
bankMapper.subMoney(bank1);
int i=1/0; //异常报错
//被转账人,加上金额
Bank bank2 = new Bank();
bank2.setId(2);
bank2.setMoney(price);
bankMapper.addMoney(bank2);
}
}
2.4 编写spring-context.xml
2.4.1 配置事务管理器
事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。
<!--xml配置方式; 引入配置事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--管理数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
注意: DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败
2.4.2 配置事务通知
基于事务管理器,进一步定制,生成一个额外功能:Advice。
此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。
<!--配置事务通知:
id:事务控制的标识
transaction-manager:引入指定的事务管理器
-->
<tx:advice id="transactionInterceptor" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"
propagation="REQUIRED" read-only="false"/>-->
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
2.4.3 编织
将事务管理的Advice 切入需要事务的业务方法中
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* transfer_accounts(..))"/>
<!-- 组织切面 -->
<aop:advisor advice-ref="transactionInterceptor" pointcut-ref="pointcut"/>
</aop:config>
2.5 测试
@Test
public void test1(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("spring-context.xml");
BankService bean = applicationContext.getBean(BankService.class);
bean.transfer_accounts(1, 2, 100);
}
3、注解实现事务控制
3.1 编写 spring-context.xml 文件
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
3.2 具体操作
用于控制事务切入
- @Transactional
- 工厂配置中的 <tx:advice… 和 <aop:config… 可以省略 !!
//标记在类上,表示类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
...
//标记在方法上表示该方法自己的事务控制,仅对此方法有效
@Transactional(propagation=Propagation.REQUIRED)//默认情况
public List<User> queryAll() {
return userDao.queryAll();
}
public void save(User user){
userDao.save(user);
}
}
3.3 注解参数详解
事物传播行为介绍:
@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.事物超时设置:
@Transactional(timeout=30) //默认是30秒
事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据
十四、Spring-test和junit整合
1、在pom.xml文件中添加依赖
<!--spring-test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
2、具体操作:
可以免去工厂的创建过程;
可以直接将要测试的组件注入到测试类。
@RunWith(SpringRunner.class) //由SpringJUnit4ClassRunner启动测试
@ContextConfiguration("classpath:spring-context.xml") //spring的配置文件位置
public class SpringTest{//当前测试类也会被纳入工厂中,所以其中属性可以注入
@Autowired // 注入要测试的组件
@Qualifier("userDAO")
private UserDAO userDAO;
@Test
public void test(){
// 测试使用userDAO
userDAO.queryUser();
....
}
}
十五、注解开发
1、 声明bean
用于替换自建类型组件的 <bean…>标签;可以更快速的声明bean
- @Service 业务类专用
@Repository dao实现类专用
@Controller web层专用- @Component 通用
- @Scope 用户控制bean的创建模式
// @Service说明 此类是一个业务类,需要将此类纳入工厂 等价替换掉 <bean class="xxx.UserServiceImpl">
// @Service默认beanId == 首字母小写的类名"userServiceImpl"
// @Service("userService") 自定义beanId为"userService"
@Service //声明bean,且id="userServiceImpl"
@Scope("singleton") //声明创建模式,默认为单例模式 ;@Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService {
...
}
2、 注入(DI)
用于完成bean中属性值的注入
- @Autowired 基于类型自动注入
- @Resource 基于名称自动注入
- @Qualifier(“userDAO”) 限定要自动注入的bean的id,一般和@Autowired联用
- @Value 注入简单类型数据 (jdk8种+String)
@Service
public class UserServiceImpl implements UserService {
@Autowired //注入类型为UserDAO的bean
@Qualifier("userDAO2") //如果有多个类型为UserDAO的bean,可以用此注解从中挑选一个
private UserDAO userDAO;
}
@Service
public class UserServiceImpl implements UserService {
@Resource("userDAO3") //注入id=“userDAO3”的bean
private UserDAO userDAO;
/*
@Resource //注入id=“userDAO”的bean
private UserDAO userDAO;
*/
}
public class XX{
@Value("100") //注入数字
private Integer id;
@Value("shine") //注入String
private String name;
}
3、 事务控制
用于控制事务切入
- @Transactional
- 工厂配置中的 <tx:advice… 和 <aop:config… 可以省略 !!
//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
...
//该方法自己的事务控制,仅对此方法有效
@Transactional(propagation=Propagation.SUPPORTS)
public List<User> queryAll() {
return userDao.queryAll();
}
public void save(User user){
userDao.save(user);
}
}
4、注解所需配置
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"></context:component-scan>
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>
5、 AOP开发
5.1 注解使用
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
// 定义切入点
@Pointcut("execution(* com.qf.spring.service.UserServiceImpl.*(..))")
public void pc(){}
@Before("pc()") // 前置通知
public void mybefore(JoinPoint a) {
System.out.println("target:"+a.getTarget());
System.out.println("args:"+a.getArgs());
System.out.println("method's name:"+a.getSignature().getName());
System.out.println("before~~~~");
}
@AfterReturning(value="pc()",returning="ret") // 后置通知
public void myAfterReturning(JoinPoint a,Object ret){
System.out.println("after~~~~:"+ret);
}
@Around("pc()") // 环绕通知
public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
System.out.println("interceptor1~~~~");
Object ret = p.proceed();
System.out.println("interceptor2~~~~");
return ret;
}
@AfterThrowing(value="pc()",throwing="ex") // 异常通知
public void myThrows(JoinPoint jp,Exception ex){
System.out.println("throws");
System.out.println("===="+ex.getMessage());
}
}
5.2 配置
<!-- 添加如下配置,启用aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
十六、lombok:
Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。通过添加注解的方式,不需要为类编写getter或eques方法,同时可以自动化日志变量。
1、添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
2、例子
@Data
@NoArgsConstructor //提供无参构造
@AllArgsConstructor //提供所有参数的有参构造
public class Bank {
private Integer id;
private String username;
private Float money;
}
十七、附加:
1、BeanFactory接口和ApplicationContext接口,有什么区别(生命周期角度考虑)
答:
BeanFactory是Spring容器中的顶层接口,ApplicationContext是它的子接口。
BeanFactory和ApplicationContext的区别:创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。(立即加载的思想来创建bean对象)
BeanFactory:什么使用什么时候创建对象。(延迟加载的思想来创建bean对象)
BeanFactory顶层的父接口
ApplicationContext:间接的子接口
共同点:都是工厂接口
前者:老工厂接口,部分不支持SpringAOP结束
管理bean对象的操作 bean对象的初始化/销毁...,不支持国际化
后者:是常用的工厂接口
ClassPathXmlApplicationContext:就是ApplicationContext的具体的子实现类
很多子实现类
ClassPathXmlApplicationContext:获取项目下类路径下的配置文件(推荐:resources下面的配置文件)
FileSystemXmpApplicationContext:可以获取系统文件的配置文件
GenericXmlApplicationContext:通用的子类(统一资源定位的文件)
ApplicationContext:支持国际化/SpringAOP..
2. 简述以下SpringIOC
控制反转/控制倒置,通过引入SpringIOC容器,利用依赖注入的方式,实现对象间的解耦
实现对象的解耦,利用工厂模式.
创建容器对象,加载Spring的配置文件----读取xml文件 (dom4j解析:基于面向对象/sax解析:基于事件编程)
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="" class=""></bean>
</beans>
容器对象.getBean("唯一标识"/类型的Class)
通过反射的方式:获取class属性 "xx.xx.x"---->Class.forName("类的全限定名称");---Class
创建当前类实例
<bean id="标识" class="类的全限定名称"></bean>
3.什么是Spring的AOP
面向切面编程:基于OOP面向对象的!
利用通知类型:前置通知,后置通知,异常通知,最终通知(不去设置),环绕通知(前面所有通知都可以进行配置)
实现业务层方法的增强(控制)
需要实现类似于jdk动态代理/cglib动态代理:将业务层代码和增强代码进行分离
好处:
1)方便后期业务的方法维护管理:管理整个业务方法service
2)通过AOP实现程序间的解耦(Spring ioc)
3)业务层代码和增强代码进行分离
//简单的配置---- 实现了SpringAOP相关的接口 xxAdvice
<aop:config>
<aop:pointcut id="" 表达式="execulotion(* *.*.service.impl.*.*(..))" >
<aop:advice>
<aop:config>
4.jdk动态代理和Cglib动态代理的区别
jdk动态代理基于接口实现的:需要有一个接口:xxService
代理实现过程: java.lang.reflect. InvocationHandler :代理的处理程序
cglib:基于子类实现
InvocationHandler(cglib.proxy包下的) :代理的处理程序
实现的过程:需要增强类Enhancer
//定义方法:bindObject
public Object bindObject(Object tareget){
this.tareget = tareget ;
//1)使用步骤
// 创建增强类
Enhancer enhancer = new Enhancer() ;
//2)给Enhancer增强类对象 进行赋值
enhancer.setSuperclass(tareget.getClass());
//3)设置回调 对象:代表当前类对象地址值引用 this
enhancer.setCallback(this);
//4)创建当前角色对象
Object obj = enhancer.create();
return obj ;
}
5.列出Spring常见的注解,分别阐述其意思
Spring注解
创建bena对象的注解
@Component: 通用的注解
@Service :业务层
@Controller :控制器
@Reospitory :持久层dao层
注入注解
@Autowired:自动按照类型注入
@Qualifier:按照唯一标识进行注入,结合使用
@Resource(name="唯一标识")
生命相关的注解
@PostConstruct:初始化
@PreDestroy:销毁
@Scope("名称"):单例/多列
//@Primary 指定唯一标识