Spring 5
1. 介绍
1.1 简介
历史:2002年,首次推出了 Spring 框架的雏形:interface 21。2004年3月24日,Spring诞生,发布了 1.0 正式版本。
Rod Johnson,Spring Framework 的创始人,不是计算机博士,著名作者,是悉尼大学音乐学博士。
理念:使现有的技术更容易使用,本身是一个大杂烩,整合了现有的技术框架。
- 官网:https://docs.spring.io/spring-framework/docs/current/reference/
- 官方下载地址:https://repo.spring.io/release/org/springframework/spring/
//https://mvnrepository.com/artifact/org.springframework/spring-webmvc 因为maven会导入依赖包,所以只需要导入 spring-webmvc 即可 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.2.RELEASE</version> </dependency>
1.2 优点
- Spring 是一个开源的免费的框架即容器
- Spring 是一个轻量级的,非入侵式的框架
- IOC,AOP
- 支持事物处理,对框架整合的支持
- 总结:Spring 就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。
1.3 组成
1.4 扩展
官网介绍:现代化的 Java 开发,就是基于 Spring 的开发。
- Spring Boot
- 一个快速开发的脚手架,基于 SpringBoot 可以快速的开发单个微服务。
- 约定大于配置
- Spring Cloud
- 基于 SpringBoot 实现。
弊端:发展太久之后违背了原来的理念,配置十分繁琐,人称 “配置地狱”。
2. IOC 理论推导
2.1 原始代码情况
------原始代码------
原始情况下,如果需要增加接口,增加需求则需要去修改源代码,如果程序代码量很大,修改一次的成本代价非常的昂贵
程序是主动创建对象,控制权在程序员的手上。
-- UserDao.java
public interface UserDao {
void getUser();
}
--UserDaoImpl.java
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("默认获取接口的数据");
}
}
--UserDaoMysqlImpl.java
public class UserDaoMysqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("Mysql获取用户数据");
}
}
--UserService.java
public interface UserService {
void getUser();
}
--UserServiceImpl.java
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
--MyTest.java
public class MyTest {
public static void main(String[] args) {
//用户实际调用的是业务层,dao层不需要接触
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
2.2 修改后使用 set 注入
------修改后------
使用了 set 注入后,程序不再具有主动性而是变成了被动的接受对象。
//只需要修改业务层 service 即可,
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
//利用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
--MyTest.java
public class MyTest {
public static void main(String[] args) {
//用户实际调用的是业务层,dao层不需要接触
UserService userService = new UserServiceImpl();
((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());//调用实现方法
userService.getUser();
}
}
- 这种修改,本质上解决了问题,程序员不用再去管理对象的创建,系统的耦合性大大降低,可以更加专注的在业务的实现上,这时 IOC 的原型。
2.3 IOC 本质
控制反转 IOC 是一种设计思想,DI (依赖注入) 是实现 IOC 的一种方法,没有 IOC 的程序中,使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,所谓控制反转就是:获得依赖对象的方式反转了。
采用 XML 方式配置 Bean 的时候,Bean 的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转时一种通过描述 (XML 或注解) 并通过第三方去生产或获取特定对象的方式。在 Spring 中实现控制反转的是 IOC 容器,其实现方法是依赖注入(Dependency Injection, DI)。
3. Hello Spring
- 创建实体类 Hello.java
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 + '\'' +
'}';
}
}
- 在 resources 下建立 beans.xml 文件,并粘贴下面代码
官网地址:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-metadata
<!--原始框架-->
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
<!--修改后使用-->
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用 Spring 来创建对象,在 spring 中这些都称为 Bean
Hello hello = new Hello();
id = 变量名
class = 相当于要 new 的对象
property 相当于给对象中的属性设置一个值
bean = 对象
-->
<bean id="hello" class="edu.lfsfxy.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>
- MyTest.java
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());
}
}
代码中 hello 对象是由Spring 创建的,对象的属性是由 Spring 容器设置的
这个过程叫做控制反转:
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用 Spring 后,对象是由 Spring 来创建的。
反转:程序本身不创建对象,而变成被动的接收对象
依赖注入:就是利用 set 方法来进行注入的
IOC 是一种编程思想,由主动的编程变成被动的接收。
至此,我们彻底不用在程序中改动,要实现不同的操作,只需要在 xml 配置文件中进行修改(注册 bean),所谓的 IOC,一句话:对象由 Spring 来创建,管理,装配!!!
4. IOC 创建对象的方式
- 使用无参构造创建对象,默认
- 使用有参构造创建对象
- 构造函数参数下标赋值
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
- 构造函数参数的类型,不建议使用
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
- 直接通过参数参数名设置
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="name" value="java"/>
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了。
5. Spring 配置
5.1 别名(alias)
<alias name="user" alias="userNew"/>
//如果添加了别名,我们也可以使用别名获取到这个对象
5.2 Bean 的配置
<!--
id : bean 的唯一标识符,也就是相当于我们学的对象名
class : bean 对象对应的全限定名:包名+类型
name : 也是别名,等同于 alias,而且 name 可以同时取多个别名
scope : 作用域
-->
<bean id="hello" class="edu.lfsfxy.pojo.Hello" name="hello2,hello3">
<property name="name" value="Spring"/>
</bean>
5.3 import
一般用于团队开发使用,可以将多个配置文件导入合并为一个
假设项目多人开发,三个人进行不同的类开发,不同的类需要注册不同的bean中,可以利用import 将所有人的 beans.xml 合并为一个导入到总的 applicationContext.xml 中,使用的时候直接使用总的配置就可以了。
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
6. 依赖输入 (DI)
6.1 构造器注入
等同于:4. IOC 创建对象的方式
6.2 Set 方式注入(重点)
- 依赖注入:Set 注入
- 依赖:bean 对象的创建依赖于容器
- 注入:bean 对象中的所有属性,由容器来注入
测试代码
- 复杂类型
Address 字段为一个class类
---Address.java---
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 + '\'' +
'}';
}
}
- 测试对象,不同的类型以及集合类型
---Student---
//下方省略 get set 方法
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
}
- 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="edu.lfsfxy.pojo.Address">
<property name="address" value="河北"/>
</bean>
<bean id="student" class="edu.lfsfxy.pojo.Student">
<!--普通值注入,value-->
<property name="name" value="李明"/>
<!--Bean 注入,ref-->
<property name="address" ref="address"/>
<!--数组注入-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!--List-->
<property name="hobbys">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="身份证" value="12345678"/>
<entry key="银行卡" value="12345678"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>LOL</value>
<value>DNF</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="driver">10101010</prop>
<prop key="url">localhost:8080</prop>
<prop key="username">root</prop>
<prop key="userpassword">root</prop>
</props>
</property>
</bean>
</beans>
- MyTest.java
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.toString());
}
}
执行结果:
Student{
name='李明',
address=Address{address='河北'},
books=[红楼梦, 西游记, 水浒传, 三国演义],
hobbys=[听歌, 敲代码, 看电影],
card={身份证=12345678, 银行卡=12345678},
games=[LOL, DNF], wife='null',
info={url=localhost:8080, userpassword=root, driver=10101010, username=root}
}
6.3 拓展方式注入
我们可以使用 p 命名空间 和 c 命名空间进行注入
官方文档:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!--p命名空间需要引入-->
xmlns:p="http://www.springframework.org/schema/p"
<!--c命名空间需要引入-->
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p 命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="edu.lfsfxy.pojo.User" p:name="黎明" p:age="20"/>
<!--c 命名空间注入,通过构造器注入:construct-args-->
<bean id="user2" class="edu.lfsfxy.pojo.User" c:name="李明" c:age="22"/>
</beans>
注意:p 命名和 c 命名空间不能直接使用,需要导入约束 (上面代码区有)。
7. Bean Scopes,Bean 作用域
- 单例模式(Spring 默认机制)
<bean id="user" class="edu.lfsfxy.pojo.User" p:name="黎明" p:age="20" scope="singleton"/>
- 原型模式:每次从容器中get的时候,都会产生一个新对象。
<bean id="user" class="edu.lfsfxy.pojo.User" p:name="黎明" p:age="20" scope="prototype"/>
- request,session,application 这些只能在web开发中使用。
8. Bean 的自动装配
- 自动装配是 Spring 满足 bean 依赖一种方式
- Spring 会在上下文中自动寻找,并自动给 bean 装配属性
Spring 中有三种装配方式:
- 在 xml 中显示的配置
- 在 java 中显示配置
- 隐式地自动装配 bean(重要)
8.1 ByName 自动装配
<bean id="cat" class="edu.lfsfxy.pojo.Cat"/>
<bean id="dog" class="edu.lfsfxy.pojo.Dog"/>
<!--
byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的 bean id
-->
<bean id="people" class="edu.lfsfxy.pojo.People" autowire="byName">
<property name="name" value="小明"/>
</bean>
8.3 ByType 自动装配
<bean id="cat" class="edu.lfsfxy.pojo.Cat"/>
<bean id="dog" class="edu.lfsfxy.pojo.Dog"/>
<!--
byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的 bean id
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean,必须保证type唯一
-->
<bean id="people" class="edu.lfsfxy.pojo.People" autowire="byType">
<property name="name" value="小明"/>
</bean>
总结:
- byname 时候需要保证所有 bean 的 id 唯一,并且这个 bean 需要和自动注入的属性的set 方法的值一致。
- bytype 的时候需要保证所有 bean 的class唯一,并且这个 bean 需要和自动注入的属性的类型一致。
8.4 使用注解实现自动装
- 导入约束,context 约束
- 配置注解的支持: context:annotation-config/
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
@Autowired注解
直接在属性上使用即可(可以省略 get set 方法),也可以在 set 方法上使用。
使用 Autowired 我们可以不用编写 Set 方法,前提是这个自动装配的属性在 IOC 容器中存在,且符合名字 byname。
public class People {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
//省略 get set 方法
}
特殊:
@Nullable 字段标记了这个注解,说明这个字段可以为 null
//构造函数 public People(@Nullable String name) { this.name = name; }
public class People {
//如果显示定义了 Autowired 的required 属性false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
//required 源码
public @interface Autowired {
boolean required() default true;
}
如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解 @Autowired 完成的时候,(如果 Autowired 不能唯一自动装配上属性)可以使用 @Qualifier(value = “xxx”) 去配置 @Autowired的使用,指定一个唯一的 bean 对象注入。
public class People {
@Autowired
@Qualifier(value = "cat11")
private Cat cat;
@Autowired
@Qualifier(value = "dog11")
private Dog dog;
private String name;
}
@Resource注解
public class People {
@Resource(name = "cat1")//多个id通过指定
private Cat cat;
@Resource
private Dog dog;
private String name;
}
总结:
@Resource 和 @Autowired 特点:
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired 通过 bytype 的方式实现,而且必须要求这个对象存在
- @Resource 默认通过 byname 实现,如果找不到名字则通过 bytype 实现。如果两个都找不到就报错。
- 执行顺序不同:@Autowired 通过 bytype -> byname 的方式实现,@Resource 默认通过 byname -> bytype 实现
9. 使用注解开发
Spring 4 之后,使用注解开发,必须保证 AOP 包导入。
使用注解需要导入 context 约束,增加注解的支持。
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="edu.lfsfxy.pojo"/>
<!--开启注解的支持-->
<context:annotation-config/>
</beans>
- bean
@Component :组件,放在类上,说明这个类被 Spring 管理了,就是bean
//等价于 <bean id="user" class="edu.lfsfxy.pojo.User"/>
//Component 组件
@Component
public class User {
public String name = "李明";
}
- 属性如何注入
@Component
public class User {
//相当于 <property name="name" value="李明"/>
//@Value("李明")
public String name;
@Value("李明")
public void setName(String name) {
this.name = name;
}
}
- 衍生的注解
@Component 有几个衍生注解,在 web 开发中,会按照 mvc 三层架构分层
- dao (@Repository)
- service (@Service)
- controller (@Controller)
- 四个注解功能都是一样的,都是将某个类注册到 Spring 中进行装配 Bean。
- 自动装配置
上面有:@Autowired,@Qualifier,@Resource - 作用域
@Scope("singleton") //单例模式
@Scope("prototype") //原型模式
public class User {
}
总结:xml与注解:
- xml 万能,适用于任何场合,维护简单方便。
- 注解 不是自己的类使用不了,维护相对复杂
xml与注解最佳实践:
- xml 用来管理 bean
- 注解只负责完成属性的注入
- 我们在使用的过程中,只需要注意一个问题,必须让注解生效,就需要开启注解支持。
10. 使用 java 的方式配置 Spring
完全不适用 Spring 的xml 配置,完全交给 java 来做。
JavaConfig 是 Spring 的一个子项目,在Spring4之后成为了一个核心功能。
- User.java 实体类
//说明这个类被Spring管理,注册到了容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("李明")//注入值,属性
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
- MyConfig.java 配置类
@Configuration
@ComponentScan("edu.lfsfxy.pojo")
//@Import(MyConfig2.class)//引入另一个配置类
//这个也会被 Spring 容器托管,注册到容器中,因为它本身就是一个 Component(组件),
//@Configuration 代表一个配置类,和 beans.xml 是一样的。
public class MyConfig {
//注册一个 bean,就相当于之前写的一个 bean 标签,
//id 相当于bean 标签中的 id 属性
//方法的返回值相当于 bean 标签中的 class 属性。
@Bean
public User getUser(){
return new User();//就是返回要注入到 bean 的对象
}
}
- MyTest.java 测试类
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置类方式,只能通过 AnnotationConfigApplicationContext 上下文来获取容器,通过 配置类的 class对象加载。
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User getUser = (User) context.getBean("getUser");//取得是配置类的方法名
System.out.println(getUser.getName());
}
}
11. 代理模式(SpringAop 底层)
代理模式分类:
- 静态代理
- 动态代理
11.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后会做一些附属操作
- 客户:访问代理对象的人
代码实现过程:
- 接口
//租房
public interface Rent {
public void rent();
}
- 真实角色
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
- 代理角色
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
hetong();
fare();
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
//签合同
public void hetong(){
System.out.println("签合同");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
- 客户
public class Client {
public static void main(String[] args) {
//房东要租房子
Host host = new Host();
//代理,中介帮房东租房子,代理角色会有一些附属操作
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介租房即可
proxy.rent();
}
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共也就交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率降低
11.2 动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理分两大类:基于接口的动态代理,基于类的动态代理
- 基于接口–JDK动态代理
- 基于类:cglib
- java 字节码实现:JAVAssist
两个类:Proxy:代理;InvocationHandler:调用处理程序;
动态代理的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共业务
- 公共也就交给代理角色,实现业务的分工
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。
InvocationHandler:是由代理实例的调用处理程序实现的接口。
每个代理实例都有一个关联的调用处理程序,当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的 invoke 方法。
接口里面的方法:Object invoke(Object proxy, 方法 method, Object[] args),处理代理实例上的方法调用并返回结果。
- proxy --调用该方法的代理实例(你要代理谁)
- method --所述方法对应于调用代理实例上的接口方法的实例,方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口(要代理的类里面的哪一个方法)
- args --固定的,方法里面传递的参数
**Proxy:**提供了创建动态类和实例的静态方法,是一个类
ProxyInvocationHandler.class 动态实现代理通用代码
//会用这个类自动生成代理类
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);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质就是使用反射机制实现
Object result = method.invoke(target, args);
return result;
}
}
Client.java
public class Client {
public static void main(String[] args) {
//真实角色
UserDaoImpl userDao = new UserDaoImpl();
//代理角色,没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象
pih.setTarget(userDao);//设置要代理的对象
//动态生成代理类
UserDao proxy = (UserDao) pih.getProxy();
proxy.add();
}
}
12. AOP
12.1 什么是 AOP
AOP(Aspect Oriented Programming):面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
12.2 AOP 在 Spring 中的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能,即是,与我们业务逻辑无关,但是我们需要关注的部分就是横切关注点,如日志,安全,缓存,事务等等…
- 切面(ASPECT):横切关注点被模块化的特殊对象,即 它是一个类
- 通知(Advice):切面必须要完成的工作,即 它是类中的一个方法
- 目标(Target):被通知对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知执行的“地点”的定义
- 连接点(JointPoint):与切入点匹配的执行点
Spring AOP 中,通过 Advice 定义横切逻辑,Spring 中支持 5 种类型的 Advice:
12.3 使用 Spring 实现 AOP
需要导入依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
方式一:使用 Spring 的 API 接口
Log.java
public class Log implements MethodBeforeAdvice { //method: 要执行的目标对象的方法 //args:参数 //target: 目标对象 @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了"); } }
AfterLog.java
public class AfterLog implements AfterReturningAdvice {
//returnValue:返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了" + method.getName() + "返回结果为:" + returnValue);
}
}
UserService.java
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
UserServiceImpl.java
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 select() {
System.out.println("查询了一个用户");
}
}
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="edu.lfsfxy.service.UserServiceImpl"/>
<bean id="log" class="edu.lfsfxy.log.Log"/>
<bean id="afterLog" class="edu.lfsfxy.log.AfterLog"/>
<!--方式一:使用原生 Spring API 接口-->
<!--配置 aop:需要导入aop约束-->
<aop:config>
<!--切入点(在那个地方执行):expression:表达式,execution(要执行的位置!*****)
修饰词 返回值 类名 方法名 参数-->
<aop:pointcut id="pointcut" expression="execution(* edu.lfsfxy.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
MyTest.java
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口,注意点
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
方式二:自定义来实现 AOP
不要使用 方式一 的Log类和 AfterLog 类,新加入一个自定义类 DiyPointCut.java
DiyPointCut.java
public class DiyPointCut { public void before(){ System.out.println("方法执行前"); } public void after(){ System.out.println("方法执行后"); } }
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="edu.lfsfxy.service.UserServiceImpl"/>
<!--方式二-->
<bean id="diy" class="edu.lfsfxy.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面,ref 要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* edu.lfsfxy.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
方式三:使用注解实现
不使用 方式一 的Log类和 AfterLog 类,新加入一个自定义类 AnnotationPointCut.java
AnnotationPointCut.java
//方式三:使用注解方式实现 AOP
@Aspect//标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* edu.lfsfxy.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("===方法执行前===");
}
@After("execution(* edu.lfsfxy.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("===方法执行后===");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* edu.lfsfxy.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();//获得签名
System.out.println("signature:" + signature);
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="edu.lfsfxy.service.UserServiceImpl"/>
<!--方式三:使用注解实现 AOP-->
<bean id="annotationPointCut" class="edu.lfsfxy.diy.AnnotationPointCut"/>
<!--开启注解支持
JDK(默认 proxy-target-class="false")
cglib(proxy-target-class="true")
两种,结果没有区别-->
<aop:aspectj-autoproxy/>
</beans>
- AOP 就是横向编程,在改变原有代码的情况下修改业务代码,增加需求。
13. 整合 Mybatis
步骤:
- 导入相关 jar 包
- junit
- mybatis
- mysql数据库
- spring相关
- Aop织入
- mybatis-spring,专门用来整合 mybatis 和 spring
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>maven_repository.mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!--Spring需要操作数据库需要 spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
- 编写配置文件
- 测试
13.1 Mybatis
13.2 Mybatis-spring
mybatis-spring 这种方式就是把 mybatis-config.xml 里面的配置信息都注册到 spring 中进行托管。
User.java 实体类
public class User { private Integer id; private String name; private String pwd; //省略 get set 方法 }
UserMapper.java 接口
public interface UserMapper { public List<User> selectUser(); }
- 编写数据源配置 ( 3步 ) spring-dao.xml 封装不用修改,使用的使用 import 导入
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--1. 配置数据源-->
<!--DataSource:使用 Spring 的数据源替换 Mybatis 的配置
这里使用Spring提供的 JDBC:org.springframework.jdbc.datasource
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/studentmanagement?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--2. sqlSessionFactory-->
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--绑定 Mybatis 配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:edu/lfsfxy/mapper/*.xml"/>
</bean>
<!--3. sqlSessionTemplate-->
<!--SqlSessionTemplate:就是我们使用的 sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能用构造器注入sqlSessionFactory,因为它没有 set 方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
- 需要给接口加实现类
public class UserMapperImpl implements UserMapper {
//我们的所有操作,都使用 sqlSession 来执行,在原来,现在都是用 SqlSessionTemplate;
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
//执行SQL getMapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- 将自己写的实现类,注入到 Spring 中
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--导入 spring-dao.xml-->
<import resource="spring-dao.xml"/>
<bean id="userMapper" class="edu.lfsfxy.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
- 测试使用
public class MyTest {
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}
SqlSessionDaoSupport
官网另一个更加简单的整合
在实现类中继承 SqlSessionDaoSupport 方法。
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectUser() {
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}<bean id="userMapper" class="edu.lfsfxy.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
14. 声明式事务
- 事务
- 把一组业务当成一个业务来做:要么都成功,要么都失败
- 事务在项目开发中,非常的重要,涉及到数据的一致性问题,不能马虎
- 是确保完整性和一致性
事务 ACID 原则:
- 原子性(Atomicity):确保这些东西要么都成功,要么都失败
- 一致性(Consistency):一旦事务完成,要么都被提交,要么都不能提交
- 隔离性(Isolation):一个操作不会影响另一个操作,多个业务可能操作同一个资源,防止数据损坏。
- 持久性(Durability):事务一旦提交,无论系统发生什么问题,结果都不会被影响,被持久化的写道存储器中。
- spring中的事务管理
- 声明式事务:AOP
- 编程式事务:需要在代码中,进行事务的管理
【案例】
图中的sql是错误的,运行肯定会报错,下图实现类中这么写的话会添加成功一条数据,但是删除的由于sql语句有错误所以失败,这两个操作都是在一个方法里面,所以需要事务进行约束,要么都成功,要么都失败。
- applicationContext.xml AOP 实现了横向的增加事务,不需要修改源代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--导入 spring-dao.xml-->
<import resource="spring-dao.xml"/>
<bean id="userMapper" class="edu.lfsfxy.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>
<!--结合 AOP 实现事务织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给那些方法配置事务-->
<!--配置事务的传播特性:propagation= (默认,可以不写)-->
<tx:attributes>
<tx:method name="add"/>
<tx:method name="delete"/>
<tx:method name="update"/>
<tx:method name="query"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* edu.lfsfxy.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
- UserMapperImpl.java 实现类
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(new User(4,"小李","121212"));
mapper.deleteUser(4);
return mapper.selectUser();
}
@Override
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}