狂神说java阶段5-Spring
- 1. Spring
- 1.1 简介
- 1.2 spring 优点
- 1.3 组成
- 1.4 扩展
- 2. IOC 理论推导
- 2.1 IOC基础
- 2.2 IOC 本质
- 3. HelloSpring
- 3.1 导包后在pojo中写实体类
- 3.2 resource中创建spring的配置文件
- 3.3 test
- 3.4 创建的bean依赖于另一个bean
- 4 IOC创建的方式
- 4.1 参数下标赋值
- 4.2 类型赋值(不建议使用)
- 4.3 直接通过参数名(推荐)
- 5 Spring 的配置
- 5.1 别名alias
- 5.2 Bean的配置
- 5.3 import
- 6. 依赖注入(DI)
- 6.1 构造器注入
- 6.2 set方式注入(重点)
- 6.2.1【环境搭建】
- 6.3 扩展方式注入(如第三方)
- 6.4 Bean 作用域
- 7. Bean的自动装配
- 7.1 什么是自动装配
- 7.2 测试环境搭建:
- 7.3 Byname 自动装配
- 7.4 Bytype 自动装配
- 7.5 使用注解自动装配
- 8.使用注解开发
- 8.1 bean注解注入
- 8.2 属性注入
- 8.3 衍生的注解
- 8.4 自动装配
- 8.5 作用域
- 8.6 小结
- 9. 基于Java类进行配置
- 10 代理模式
- 10.1 静态代理
- 10.1.1 静态代理角色分析
- 10.1.2 代码实现
- 10.1.3 静态代理优缺点
- 10.1.4 静态代理再理解
- 10.2 动态代理
- 10.2.1 InvocationHandler 和 Proxy两个类
- 10.2.2 代码实现
- 10.2.3 深化理解
- 10.2.3 动态代理的好处
- 10.1 静态代理
- 11 什么是AOP
- 11.1 Aop中术语解释
- 11.2 切点表达式
- 11.3 使用Spring实现Aop
- 11.2.1 第一种方式-通过 Spring API 接口实现
- 11.2.2 第二种方式--自定义切面类来实现Aop
- 11.2.3 第三种方式 使用注解实现
- 12. 整合Mybatis
- 12.1回忆mybatis
- 12.2 MyBatis-Spring学习
- 12.2.1 整合实现一(重点理解第一个)
- 12.2.2 实现方式二
- 13. 声明式事务
- 13.1 回顾事务
- 13.2 测试
- 13.3 Spring中的事务管理
- Spring 官方文档
百度搜索 “spring 官方文档”-->projects->spring Framework->learn->Ref Doc(文档)/API Doc
https://spring.io/projects/spring-framework#learn - spring 仓库
https://repo.spring.io/ui/native/release/org/springframework/spring
点击想要下载的版本
搜索 spring maven,找到spring web MVC 对应的maven依赖,导了这个其他的spring就自动依赖下载
github标志,表示开源,可以到github里看源码,点击wiki标签也可以找到对应的文档
笔记参考
https://blog.csdn.net/weixin_44822455/article/details/108933150
https://www.pianshen.com/article/16311415022/
1. Spring
1.1 简介
-
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
-
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
-
很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
-
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
-
SSH: Struct2+Spring+Hibernate
-
SSM: SpringMvc+Spring+Mybatis
官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects
org.springframework
spring-webmvc
5.2.5.RELEASE
org.springframework
spring-jdbc
5.2.3.RELEASE
1.2 spring 优点
- spring是开源的免费的容器。
- spring是一个轻量级的,非入侵式的。(导入后对原来的项目没有任何影响)
- 控制反转(IOC),面向切面编程 (AOP)。
- 支持事务处理,对框架整合的支持。
总结:spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
1.3 组成
Spring 七大模块
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
-
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
-
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
-
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
-
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
-
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
-
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
-
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.4 扩展
Spring Boot与Spring Cloud
* Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
* Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,可以独立使用
* Spring Cloud是基于Spring Boot实现的,离不开SpringBoot;
* Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
Spring发展弊端:发展太久,配置十分繁琐,“配置地狱”(所以出现spring boot)
2. IOC 理论推导
2.1 IOC基础
1. UserDao
2. UserDaoImpl
3. UserSevice
4. UserServiceImpl
在我们之前业务层调用Dao层,把创建的Dao对象组合在serviceImpl里,用户的需求可能会影响原来的代码。如果程序代码十分大,修改一次成本过高。
如改调用UserDaoSqlImpl,UserDaoOracleImpl...,又需要去service实现类里面修改对应的实现。耦合性强
class UserServiceImpl implements UserService{
private UserDao userDao= new UserDaoImpl();
public voide getUser(){
userDao.getUser();
}
}
使用一个set。
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
- 之前是主动创建对象,控制权在程序员手上。
- 使用set之后,是被动接受对象。
程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注业务的实现上。这是IOC的原型。
(后端的工作大多数为写接口给前端,前端传参,我们根据这个参数去判断调用哪个方法,这样用set方法不需要改业务层,直接可以调用对应dao的方法)
2.2 IOC 本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)
3. HelloSpring
3.1 导包后在pojo中写实体类
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Hello{" +
"name='" + name + '\'' +
'}';
}
}
3.2 resource中创建spring的配置文件
xml名字随便起,但是后面调用前要通过ClassPathXmlApplicationContext引入,通常叫beans.xml
<?xml version="1.0" encoding="UTF-8"?>
bean = 对象
id = 变量名
class = new的对象
property 相当于给对象中的属性设值
核心用set注入
3.3 test
new ClassPathXmlApplicationContext获取spring上下文环境对象,加载配置文件数量可以是多个
获取ApplicationContext后,直接getBean(ID)传入bean的ID即可
import com.hou.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest {
public static void main(String[] args) {
//获取spring上下文对象,加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在spring中管理了,我们要使用,直接取出来就可以了
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
传统对象由程序本身创建的,此处是Spring创建的。
反转:程序本身不创建对象,而编程被动接收对象.
依赖注入:就是利用set方法来进行注入的
可以通过new ClassPathXmlApplicationContext去浏览下底层代码
改动只需要在xml配置文件中进行,IOC对象由Spring来创建,管理,装配
3.4 创建的bean依赖于另一个bean
在spring配置文件中property要ref另一个spring中已经创建好的对象
不能用value,它表示一个具体的值
<?xml version="1.0" encoding="UTF-8"?>
测试
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//容器在手天下我有,需要什么,直接get什么即可
UserServiceImpl userServiceImpl = (Hello) context.getBean("UserServiceImpl");
userServiceImpl.getUser();
4 IOC创建的方式
1. 使用无参构造创建对象,默认。
2. 使用有参构造
4.1 参数下标赋值
<?xml version="1.0" encoding="UTF-8"?>
配置文件idea里面小叶子图标要配置spring文件上下文才有,右键->new->spring bean configuration file->输入创建文件名字beans
4.2 类型赋值(不建议使用)
4.3 直接通过参数名(推荐)
bean对象在加载配置文件初始化spring上下文的时候就被创建了,默认是单例模式。
Spring类似于婚介网站!
你想不想要,对象都在里面。注册bean之后用不用被实例化。
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了
5 Spring 的配置
5.1 别名alias
在测试时context.getBean(别名)通过别名可以取出该对象
5.2 Bean的配置
- id:bean的id标识符
- class:bean对象所对应的类型,包名+类名
- name:别名,更高级,可以同时取多个别名。多个name可以通过逗号、空格、分号分割
5.3 import
一般用于团队开发,它可以将多个配置文件,导入合并为一个,初始上下文时只传入总的
6. 依赖注入(DI)
三种注入方式
6.1 构造器注入
前面说过了
6.2 set方式注入(重点)
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
set方式注入:要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , =如果属性是boolean类型 , 没有set方法 , 是 is .=
6.2.1【环境搭建】
1. 测试pojo类 :
属性包括基本数据类型,对象,数组,list,map,set,properties,null
student类
package com.pojo;
import java.util.*;
public class Student {
private String name;
private Address address;
private String[] books;
private List hobbies;
private Map card;
private Set game;
private Properties infor;
private String wife;
//自动生成各属性的set/get方法,此处略
address类
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
2. 配置文件注入
<?xml version="1.0" encoding="UTF-8"?>
三国
西游
水浒
eat
drink
play
wangzhe
daota
lol
<!--null标签空值,和空串不同-->
20200405
hdk
6.3 扩展方式注入(如第三方)
p命名和c命名注入
User.java :【注意:这里没有有参构造器!】
public class User {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- P命名空间注入 : 需要在头文件中加入约束文件,对应set注入
导入约束 : xmlns:p="http://www.springframework.org/schema/p"
- c 命名空间注入 : 需要在头文件(xml开始的头中)中加入约束文件,对应构造器注入
导入约束 : xmlns:c="http://www.springframework.org/schema/c"
xml
发现问题:爆红了,刚才我们没有写有参构造!
解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!
测试代码:
@Test
public void test02(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
6.4 Bean 作用域
1.单例模式(默认)
2.原型模式: 每次从容器中get的时候,都产生一个新对象!
- 其余的request、session、application,webSocket这些只能在web开放中使用!
7. Bean的自动装配
7.1 什么是自动装配
-
自动装配是Spring是满足bean依赖的一种方式
-
spring会在应用上下文中为某个bean寻找其依赖的bean。
-
在Spring中有三种装配的方式
-
在xml中显示配置
-
在java中显示配置
-
隐式bean发现机制和自动装配。
这里我们主要讲第三种:自动化的装配bean。
-
-
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
-
组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
-
自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
-
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
** 推荐不使用自动装配xml配置 , 而使用注解。**
7.2 测试环境搭建:
- 环境搭建:一个人有两个宠物
public class Cat {
public void jiao(){
System.out.println("miao");
}
}
public class Dog {
public void jiao(){
System.out.println("wow");
}
}
package com.pojo;
public class People {
private Cat cat;
private Dog dog;
private String name;
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
配置文件,显示指定依赖的bean关系
<?xml version="1.0" encoding="UTF-8"?>
7.3 Byname 自动装配
ref : https://mp.weixin.qq.com/s/kvp_3Uva1J2Q5ZVqCUzEsA
autowire= "byName" (按名称自动装配):byname会在容器上下文中自动查找,和自己set方法名后面内容对应的Beanid
保证所有id唯一,并且和set注入的值一致,如果bean id= "cat11"和set方法名后面内容不一致,就找不到了
7.4 Bytype 自动装配
Bytype自动装配:byType会自动查找,和自己对象属性相同的bean
保证所有的class唯一
<?xml version="1.0" encoding="UTF-8"?>
缺点如果对象有两个同样类型class的属性,实现不了会报错
小结:
- byname的时候,需要保证所有的bean的id唯一,并且这个ben需要和自动注入的属性的set方法的值一致!
- bytype,需要保证bean的class唯一,这个bean需要和自动注入的属性类型一致
7.5 使用注解自动装配
jdk1.5支持的注解,spring2.5支持的注解
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.
要使用注解需要导入context约束(具体写时可以去官方文档1.9里去复制xml头),并且配置注解支持 context:annotation-config
<?xml version="1.0" encoding="UTF-8"?>
@Autowire
- 在属性上个使用,也可以在set上使用!
- 在属性上使用时,可以不用编写set方法了,前提时你这个自动装配的属性在IOC容器中存在
- 是默认优先ByType进行自动装配,当发现装配类型于spring容器中存在两个及以上实例时,会采用ByName的方式继续寻找对应的实例进行装配
public class People {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
科普:
- 原理实现:通过反射机制来实现访问,所以不需要写set方法
- @Nullable 如果某个字段标记了这个注解后,说明这个字段可以为null
- @Autowired 里面有个参数required可以为false,说明这个字段可以为null,否则不允许为空
- @Resource注解是Java自带的注解,也可以实现和@autowired一样的功能,同时也可以增加参数@Resource(name="dog"),使name唯一
jdk11移除了 - 当环境比较复杂,只通过一个autowired无法完成自动装配时,可以在autowired后面增加@qualifier注解来指定一个唯一的beanid对象注入
ublic class People {
@Autowired
private Cat cat;
@Autowired
@Qualifier(value = "dog")
private Dog dog;
private String name;
}
区别:
@autowire默认byType实现,不行再byname
@resource默认通过byName实现,如果找不到,通过byType实现
注: 自己学习的时候可以在idea里创建markdown文档,记录常用依赖,注解说明等,用的时候直接复制过来,学的多了容易忘。
8.使用注解开发
在spring4之后,必须要保证aop的包导入
使用注解需要导入contex的约束
<?xml version="1.0" encoding="UTF-8"?>
8.1 bean注解注入
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
@Component:组件,放在类上,说明这个类被Spring管理了就是Bean
@Component("user")
// 相当于配置文件中
public class User {
public String name = "xiaoming";
}
测试:
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans.xml");
//不想写强制转换可以写getBean("user",User.class),名字直接时类名小写
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
8.2 属性注入
使用注解注入属性
- 可以不用提供set方法,直接在直接名上添加@value("值")
@Component("user")
// 相当于配置文件中
public class User {
@Value("秦疆")
// 相当于配置文件中
public String name;
}
- 如果提供了set方法,在set方法上添加@value("值");
@Component("user")
public class User {
public String name;
@Value("秦疆")
public void setName(String name) {
this.name = name;
}
}
8.3 衍生的注解
@Component有几个衍生注解,会按照web开发中,mvc架构中分层。
-
@Controller:web层
-
@Service:service层
-
@Repository:dao层
这四个注解功能一样的,都是代表将某个类的bean注册到容器中
8.4 自动装配
参上7章 autowired,略
8.5 作用域
-
@singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
-
@prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("秦疆")
public String name;
}
8.6 小结
- xml与注解
- xml更加万能,使用一切场合!维护简单
- 注解,不是自己的类,使用不了,维护复杂
- 最佳实践:
- xml用来管理bean
- 注解只用来完成属性的注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
作用:
进行注解驱动注册,从而使注解生效
用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
如果不扫描包,就需要手动配置bean
如果不加注解驱动,则注入的值为null!
9. 基于Java类进行配置
(这边有点乱,以后有时间查些资料)
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
测试:
1、编写一个实体类,Dog
@Component //将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
public String name = "dog";
}
2.新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类,也会在spring容器托管,本质也是component
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog dog(){
return new Dog();//就是要注入的对象
}
}
3、测试
@Test
public void test2(){
//按配置类的方式,需要通过AnnotionConfig上下文来获取容器,通过配置类class对象加载
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("dog");
System.out.println(dog.name);
}
4、成功输出结果!
导入其他配置如何做呢?
1、我们再编写一个配置类!
@Configuration //代表这是一个配置类
public class MyConfig2 {
}
2、在之前的配置类中我们来选择导入这个配置类
@Configuration
@Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {
@Bean
public Dog dog(){
return new Dog();
}
}
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
10 代理模式
代理模式:
* 静态代理
* 动态代理
代理类和实际类实现同一个接口/抽象类,客户通过访问代理类访问实际类的方法
10.1 静态代理
10.1.1 静态代理角色分析
- 抽象角色 : 一般使用接口或者抽象类来实现
- 真实角色 : 被代理的角色
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
- 客户 : 使用代理角色来进行一些操作 .
10.1.2 代码实现
Rent.java 即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 即真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy.java 即代理角色
少用继承,多用组合
//代理角色:中介
public class Proxy implements Rent {
//组合了真实角色
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client.java 即客户访问代理角色
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
Proxy proxy = new Proxy(host);
//你去找中介!
proxy.rent();
}
}
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
10.1.3 静态代理优缺点
-
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工,耦合性降低
- 公共业务发生扩展时变得更加集中和方便.
(业务分工,代码实现和修改更纯粹方便)
-
缺点 :
类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
10.1.4 静态代理再理解
- 创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
//抽象角色:增删改查业务
public interface UserService {
void add();
void delete();
void update();
void query();
}
2、我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
3、需求来了,现在我们需要增加一个日志功能,怎么实现!
* 思路1 :在实现类上增加代码 【麻烦!】
* 思路2:使用代理来做,**能够不改变原来的业务情况下,实现此功能就是最好的了**
百度面向对象七大原则学习
4、设置一个代理类来处理日志!代理角色
//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
5、测试访问类:
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能!
proxy.setUserService(userService);
proxy.add();
}
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
聊聊AOP:纵向开发,横向开发
10.2 动态代理
- 动态代理的角色和静态代理的一样 .
-
动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
-
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理--cglib
- 现在用的比较多的是java字节码实现, javasist 来生成动态代理 . 百度一下javasist
我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
-
10.2.1 InvocationHandler 和 Proxy两个类
JDK的动态代理需要了解两个类核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
【InvocationHandler:调用处理程序】
机器翻译的中文版jdk文档,看不懂可以,但是要知道怎么用,传什么参数
Object invoke(Object proxy, 方法 method, Object[] args);
//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
【Proxy : 代理】
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
10.2.2 代码实现
抽象角色和真实角色和之前的一样!
Rent.java 即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 即真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
ProxyInvocationHandler. java 即代理角色
proxy用于创建代理
invocationHandler,主要利用反射调用代理的实例方法,并返回结果
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
// proxy : 代理类 method : 代理类的调用处理程序的方法对象.
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现!
Object result = method.invoke(rent, args);
fare();
return result;
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client . java
//租客
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理实例的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host); //将真实角色放置进去!
Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
proxy.rent();
}
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、
10.2.3 深化理解
我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
测试!
public class Test {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
}
}
测试,增删改查,查看结果!
10.2.3 动态代理的好处
静态代理有的它都有,静态代理没有的,它也有!
* 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
* 公共的业务由代理来完成 . 实现了业务的分工 ,
* 公共业务发生扩展时变得更加集中和方便 .
* 一个动态代理 , 一般代理某一类业务
* 一个动态代理可以代理多个类,代理的是接口!
11 什么是AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等。基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
11.1 Aop中术语解释
Aop在Spring中的作用:提供声明式事务;允许用户自定义切面
以下名词需要了解下:
* 横切关注点:跨越应用程序多个模块的公共方法或功能。如日志 , 安全 , 缓存 , 事务等等 ....(我们程序是纵向开发的,但是现在想要横向上增加新的功能,这些功能就是一个个横切关注点)
* 切面(ASPECT):横切关注点 被模块化 的类。它包含切入点和通知,共同定义了切面的全部内容,它是干什么的,什么时候在哪执行(关注点对应的类Log)
* 通知(Advice):切面必须要完成的工作。切面(Aspect)是一个类,而通知就是类里的方法以及这个方法如何织入到目标方法的方式(Log中的方法)
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice

* 目标(Target):被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。(一个接口或者方法)
* 连接点(JointPoint):就是spring中允许使用通知的地方
* 切入点(PointCut):其实就是筛选出的连接点,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。如果说**通知定义了切面的动作或者执行时机的话,切点则定义了执行的地点**。
* 代理(Proxy):向目标对象应用通知之后创建的对象。(代理类)
* 织入(Weaving)
把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:
* 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器
* 类加载期:切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码
* 运行期:切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。
Aop 核心功能就是在不改变原有代码的情况下 , 去增加新的功能
(这部分概念的解释和更详细应用参考https://blog.csdn.net/zzhongcy/article/details/102484741
kuangshen的视频里只提到简单应用,没有给通知里传递参数,如目标对象状态,返回值等)
11.2 切点表达式
切点的功能是指出切面的通知应该从哪里织入应用的执行流。切面只能织入公共方法。
使用AspectJ的切点表达式语言定义切点其中excecution()是最重要的描述符
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
- 分别表示修饰符匹配(modifier-pattern?)、返回值匹配(ret-type-pattern)、类路径匹配(declaring-type-pattern?)、方法名匹配(name-pattern)、参数匹配((param-pattern))、异常类型匹配(throws-pattern?)
- 其中后面跟着“?”的是可选项。
- 各个pattern中,可以使用"*"来表示匹配所有
- 在param-pattern中,可以指定具体的参数类型,也可以用“”来表示匹配任意类型的参数,多个参数间用“,”隔开,如(String)表示匹配一个String参数的方法;(,String)表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型
- 可以用(..)表示零个或多个任意的方法参数。
- 使用&&符号表示与关系,使用||表示或关系、使用!表示非关系。在XML文件中使用and、or和not这三个符号。
例:
- execution(public * com. savage.service.UserService.*(..))
//表示匹配com.savage.server.UserService中所有的公有方法 - execution(* com.savage.server...(..))
//表示匹配com.savage.server包及其子包下的所有方法 - excecution(* com.tianmaying.service.BlogService.updateBlog(..)) and !bean('tianmayingBlog')
这表示将切面应用于BlogService的updateBlog方法上,但是仅限于ID为tianmayingBlog的Bean
11.3 使用Spring实现Aop
【重点】使用AOP织入,需要导入一个依赖包(织入包)!
org.aspectj
aspectjweaver
1.9.4
11.2.1 第一种方式-通过 Spring API 接口实现
- 首先编写我们的业务接口和实现类
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
- 然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
- 最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束.
因为我们的增强类自带实现的是前置还是后置,引用标签直接是http://mybatis.org/spring/zh/
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。2.编写配置文件
3.测试pom.xml
<?xml version="1.0" encoding="UTF-8"?>
spring-study com.hou 1.0-SNAPSHOT 4.0.0 spring-10-mybatis junit junit 4.12 org.mybatis mybatis 3.5.2 mysql mysql-connector-java 5.1.47 org.springframework spring-webmvc 5.1.10.RELEASE org.springframework spring-jdbc 5.2.3.RELEASE org.aspectj aspectjweaver 1.9.4 org.mybatis mybatis-spring 2.0.4 org.projectlombok lombok 1.18.12 src/main/resources **/*.properties **/*.xml src/main/java **/*.properties **/*.xml true 12.1回忆mybatis
步骤:
- 编写实体类
- 编写核心配置文件
- 编写接口
- 编写Mapper.xml
- 测试
编写pojo实体类
package com.kuang.pojo; public class User { private int id; //id private String name; //姓名 private String pwd; //密码 }
实现mybatis的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
UserDao接口编写
public interface UserMapper { public List
selectUser(); } 接口对应的Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
测试类
@Test public void selectUser() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List
userList = mapper.selectUser(); for (User user: userList){ System.out.println(user); } sqlSession.close(); } 12.2 MyBatis-Spring学习
引入Spring之前需要了解mybatis-spring包中的一些重要类;http://www.mybatis.org/spring/zh/index.html
什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可导入依赖:org.mybatis mybatis-spring 2.0.2 12.2.1 整合实现一(重点理解第一个)
1、引入Spring配置文件beans.xml
<?xml version="1.0" encoding="UTF-8"?>
参考官方文档快速上手
Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类2.配置数据源替换mybaits的数据源
此处数据源类使用DriverManagerDataSource,也可以用其他的JDBC 的 DataSource,如c3p0,dbcpMyBatis配置中,不在需要环境配置(
),数据源( )和 MyBatis 的事务管理器( ),这些都被spring接手了。
只剩下基础配置指的是 < settings> 或 < typeAliases>元素3、配置SqlSessionFactory,关联MyBatis
SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径4、注册sqlSessionTemplate,关联sqlSessionFactory
这个spring 提供的template就是替换原来使用的sqlSession,但它是线程安全的5、增加Dao接口的实现类;私有化sqlSessionTemplate
这个实现类相当于原来我们写的测试方法public class UserDaoImpl implements UserMapper { //sqlSession不用我们自己创建了,Spring来管理 private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List
selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } } 6、注册bean实现
7、测试
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List
user = mapper.selectUser(); System.out.println(user); } 结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!
<?xml version="1.0" encoding="UTF-8" ?>
总结
思路:spring整合mybatis后多了个实现类,原因spring自动创建bean,而mybatis里的东西它无法直接自动创建,所以多个可以管理的实现类,类内属性要有set方法
整体步骤总结:
1.编写数据源配置
2.sqlSessionFactory
3.sqlSessionTemplate
4.需要给接口加实现类
5.将自己写的实现类注入到spring中,测试之后做项目的时候可以写多个spring配置文件,applicationContext.xml作为总配置,只管理bean的注册。spring-dao.xml只管理事务是sqlsession,datasource等,还可以有springmvc.xml,然后把后两者导入import到总xml文件即可
12.2.2 实现方式二
mybatis-spring1.2.3版以上的才有这个 .
官方文档截图 :
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
测试:
1、将我们上面写的UserDaoImpl修改一下public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper { public List
selectUser() { //代码简化成一行 return getSqlSession().getMapper(UserMapper.class).selectUser(); } } 2、修改bean的配置
不再注入sqlSessionTemplate,而是sqlSessionFactory因为继承的support需要这个属性3、测试
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List
user = mapper.selectUser(); System.out.println(user); } 总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!
13. 声明式事务
13.1 回顾事务
-
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。用来确保数据的完整性和一致性。
事务四个属性ACID- 原子性(atomicity)
事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
- 一致性(consistency)
一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
- 隔离性(isolation)
可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
- 持久性(durability)
事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
13.2 测试
将上面的代码拷贝到一个新项目中
在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户//添加一个用户 int addUser(User user); //根据id删除用户 int deleteUser(int id);
mapper文件,我们故意把 deletes 写错,测试!
insert into user (id,name,pwd) values (#{id},#{name},#{pwd}) deletes from user where id = #{id} 编写接口的实现类,在实现类中,我们去操作一波
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper { //增加一些操作 public List
selectUser() { User user = new User(4,"小明","123456"); UserMapper mapper = getSqlSession().getMapper(UserMapper.class); mapper.addUser(user); mapper.deleteUser(4); return mapper.selectUser(); } //新增 public int addUser(User user) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.addUser(user); } //删除 public int deleteUser(int id) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.deleteUser(id); } } 测试
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List
user = mapper.selectUser(); System.out.println(user); } 报错:sql异常,delete写错了
结果 :插入成功!
没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!
以前我们都需要自己手动管理事务,十分麻烦!
但是Spring给我们提供了事务管理,我们只需要配置即可;
13.3 Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理.
Spring支持编程式事务管理和声明式的事务管理。编程式事务管理
* 将事务管理代码嵌到业务方法中来控制事务的提交和回滚 * 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
* 一般情况下比编程式事务好用。 * 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。 * 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
事务管理器DataSourceTransactionManager
无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
JDBC事务
配置好事务管理器后我们需要去配置事务的通知
spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
* propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的默认的选择。 * propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。 * propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。 * propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。 * propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 * propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。 propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
配置AOP
导入aop的头文件!进行测试
删掉刚才插入的数据,再次测试!@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); //这个select测试方法里包括增加和删除 List
user = mapper.selectUser(); System.out.println(user); }