Spring5学习笔记:IOC、DI注入、Bean、代理、AOP、spring与MyBatis

1. Spring

1.1 简介

  • Spring春天:给软件开发带来春天

  • 2002年,首次推出Spring框架的雏形,interface 21框架

  • 2004年3月24号,Spring以interface 21框架为基础,发布了1.0版本

  • Rod Johnson,Spring Framwork创始人。悉尼大学音乐学博士

  • Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架

    • SSH:早年,Struct2 + Spring + Hibernate
    • SSM:SpringMVC + Spring + Mybatis

官方文档

官方api

官方下载地址(包含历史文件)

github地址

  • maven导入依赖包:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.21</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.21</version>
</dependency>

1.2 Spring的优点

  • Spring是一个开源的免费的框架(容器)
  • Spring是一个轻量级的、非入侵式(不会影响代码,会使代码更加简洁)的框架
  • 控制反转(IOC), 面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

总结:Spring是一个轻量级的控制反转(IOC)和面向切片编程(AOP)的框架

1.3 组成

image

1.4 拓展

Spirng官网的介绍:现代化的Java开发,就是基于Spring的开发

image

  • Spring Boot
    • 一个快速开发的脚手架
    • 基于Spring Boot开源快速的开发单个微服务
    • 约定大于配置
  • Spring Cloud
    • Spring Cloud是基于Spring Boot实现的

现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提需要完全掌握Spring以及SpringMVC。承上启下。

弊端:发展太久以后,违背了原来更加容易的理念,配置十分繁琐

2. IOC理论

普通业务实现流程:

  1. UserDao接口
  2. UserDaoImpl实现类
  3. UserService业务接口
  4. UserServiceImpl业务实现类

image

在过去的业务中,用户需求可能会影响原来的代码,需要根据用户求修改代码。如果程序代码量大,修改代价昂贵。

使用一个set接口实现,发生了革命性的变化

private UserDao userDao;

// 利用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
}
  • 修改前:程序是主动创建的对象,控制权在程序员手上
  • 修改后:使用set注入后,程序不再具有主动性,变成了被动的接受对象

image

这种思想从本质上解决了问题,程序员不用再管理对象的创建。系统的耦合性大大降低,开源更加专注的在业务的实现上。IOC是原型。

IOC的本质

控制反转IoC(Inversion of Control)是一种设计思想,DI(依赖注入)是实现IoC的一种方法。没有IoC的程序中,使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转将对象的创建转移给第三方。

可以理解为:获得依赖对象的方式反转了

image

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IoC容器中取出需要的对象。

image

采用xml配置Bean时,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(xml或注解)并通过第三方去生产或获取特定对象的方式 。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)

3. HelloSpring

类中必须有set

  1. 导入maven依赖

  2. 创建实体类

  3. 配置xml文件:使用spring创建对象,在Srping中这些都称为Bean

    • 类型 变量名 = new 类型()

      Hello hello = new Hello()

    • bean = 对象

    • id = 变量名

    • class = new 的对象

    • property:给对象中的属性设置值

      • ref:引用文件中创建好的对象
      • value: 具体的值,基本数据类型

配置文件1:

<?xml version="1.0" encoding="UTF8"?>
<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创建对象,在Srping中这些都称为Bean
        类型   变量名 =  new 类型()
        Hello hello =  new Hello()

        bean = 对象
        id = 变量名
        class = new 的对象
        property:给对象中的属性设置值
    -->
    <bean id="hello" class="com.cha.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>
</beans>

配置文件2:

<?xml version="1.0" encoding="UTF8"?>
<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="daoImpl" class="com.cha.dao.UserDaoImpl"/>
    <bean id="mysqlImpl" class="com.cha.dao.UserDaoMysqlImpl"/>
    <bean id="oracleImpl" class="com.cha.dao.UserDaoOracleImpl"/>

    <!--注册真实对象-->
    <bean id="UserServiceImpl" class="com.cha.service.UserServiceImpl">
        <property name="userDao" ref="mysqlImpl"/>
    </bean>
    <!--
    ref: 引用文件中创建好的对象
    value: 具体的值,基本数据类型
    -->
</beans>
  1. 读取xml文件,通过spring创建对象
// 使用必写的语句
// 获取Spring的上下文对象,传入参数是配置文件,可以传入多个
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 传入的对象都在Spring的管理中,要使用就直接从Spring中取出
Hello hello = (Hello) context.getBean("hello");// 配置文件中传入的,可以传入类型
System.out.println(hello.toString());
  1. 实现不同的操作,只需在xml配置文件中修改。

总结:

  • hello对象由Spring创建
  • hello对象的属性由Spring容器设置

控制反转:

  • 控制:传统程序的对象由程序本身控制创建,使用Spring后由Spring创建
  • 反转:程序不创建对象,变成被动接收对象
  • 依赖注入:利用set方法来进行注入
  • IoC:一种编程思想,由主动编程转化为被动接收,对象由Spring来创建、管理、装配

继承树:

image

4. IoC创建对象的方式

  • 默认使用无参构造创建对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  • 使用有参构造创建对象

    • 下标赋值:仅需要有参构造
    <bean id="user" class="com.cha.pojo.User">
        <constructor-arg index="0" value="cha"/>
    </bean>
    
    • 类型匹配:不建议使用,仅需要有参构造
    <bean id="user" class="com.cha.pojo.User">
        <constructor-arg type="java.lang.String" value="cha"/>
    </bean>
    
    • 通过参数名设置:可以引用其他bean,需要set方法
    <bean id="user" class="com.cha.pojo.User">
        <constructor-arg name="name" value="cha"/>
    </bean>
    

总结:在配置文件加载时,容器中管理的所有对象已经初始化了

5. Spring配置

5.1 别名

起别名,原名和别名都可以获得内容

<alias name="user" alias="userTest"/>

5.2 Bean的配置

  • id:bean的唯一标识符,相当于对象名
  • class:bean对象所对应的限定全名:包名+class类型
  • name:别名,可以同时取多个别名
<bean id="user" class="com.cha.pojo.User" name="user2,u2">
    <constructor-arg name="name" value="cha"/>
</bean>

5.3 import

一般用于团队开发使用,可以将多个配置文件导入为同一个。使用时只需要使用总配置。

重复内容会自动合并

<import resource="beans.xml"/>

6. DI依赖注入

6.1 构造器注入

  1. IoC创建对象的方式
 <constructor-arg>

6.2 set方式注入(重要)

依赖注入:set注入

  • 依赖:bean对象的创建依赖容器
  • 注入:bean对象的所有属性,由容器注入

复杂环境的搭建

  1. 复杂类型
public class Address {
    private String address;
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}
  1. 复杂测试对象
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String, String> card;
    private Set<String> games;
    private String couple;
    private Properties info;
}
  1. beans.xml
<?xml version="1.0" encoding="UTF8"?>
<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="student" class="com.cha.pojo.Student">
        <property name="name" value="cha"/>
    </bean>
</beans>
  1. 测试类
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Student student = (Student) context.getBean("student");
    System.out.println(student.getAddress());
}
  1. 注入
<bean id="address" class="com.cha.pojo.Address">
    <property name="address" value="China"/>
</bean>

<bean id="student" class="com.cha.pojo.Student">
    <!--普通值注入,value-->
    <property name="name" value="cha"/>

    <!--bean注入,ref-->
    <property name="address" ref="address"/>

    <!--数组注入-->
    <property name="books">
        <array>
            <value>《蚁群》</value>
            <value>《圆圈正义》</value>
            <value>《java编程基础》</value>
        </array>
    </property>

    <!--List注入-->
    <property name="hobbies">
        <list>
            <value>ns</value>
            <value>twice</value>
            <value>听歌</value>
            <value>写代码</value>
        </list>
    </property>

    <!--Map-->
    <property name="card">
        <map>
            <entry key="身份证" value="111111222222333333"/>
            <entry key="银行卡" value="111122223333444"/>
        </map>
    </property>
    
    <!--Set-->
    <property name="games">
        <set>
            <value>ns sports</value>
            <value>fitness boxing</value>
            <value>塞尔达</value>
            <value>splatoon</value>
        </set>
    </property>

    <!--空值注入-->
    <property name="couple">
        <null/>
    </property>

    <!--Properties-->
    <property name="info">
        <props>
            <prop key="driver">123432345</prop>
            <prop key="url">sdfhghsdf</prop>
            <prop key="username">root</prop>
            <prop key="password">123456</prop>
        </props>
    </property>
</bean>

6.3 p命名空间

可以直接注入

  1. 导入p命名空间,xml约束
xmlns:p="http://www.springframework.org/schema/p"
  1. 注入
<bean id="user" class="com.cha.pojo.User" p:name="cha" p:age="123"/>
  1. 测试
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
    User user = context.getBean("user", User.class); // 可以输入类型
    System.out.println(user.toString());
}

完整bean.xml

<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.cha.pojo.User" p:name="cha" p:age="123"/>
</beans>

6.4 c命名空间(c-namespace)

c命名空间的使用需要有参构造

  1. 导入c命名空间,xml约束
xmlns:c="http://www.springframework.org/schema/c"
  1. 注入
<bean id="user2" class="com.cha.pojo.User" c:age="11" c:name="me"/>

6.5 Bean的作用域(scopes)

Scope Description
singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototype Scopes a single bean definition to any number of object instances.
request Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

singleton 单例模式(Spring默认)

只有一个实例,只使用一个实例

image

显式设置

<bean id="user" class="com.cha.pojo.User" p:name="cha" p:age="123" scope="singleton"/>

原型模式(prototype)

每次从容器中get时,都会产生新的对象

image

设置:

<bean id="user" class="com.cha.pojo.User" p:name="cha" p:age="123" scope="prototype"/>

其他

request、session、application等仅在web开发中使用

7. Bean的自动装配

  • 自动装配是SPringle满足Bean依赖的一种方式
  • Spring在上下文中自动寻找,并自动给Bean装配属性

Spring中的三种装配方式:

  1. 在xml显式配置
  2. 在java中显式配置
  3. 隐式的自动装配Bean

7.1 环境测试

搭建环境:一个人有两个宠物

7.2 byName自动装配

byName:

  • 会自动在容器上下文中查找和自己对象set方法后的值对应的Bean id,自动小写(dog -> Dog ,装配失败)
  • 如果把id修改成其他字符,会找不到内容(如dog -> dog1 ,找不到内容)
<bean id="cat" class="com.cha.pojo.Cat"/>
<bean id="dog" class="com.cha.pojo.Dog"/>

<!--
byName:会自动在容器上下文中查找和自己对象set方法后的值对应的Bean ID,自动小写
-->
<bean id="people" class="com.cha.pojo.People" autowire="byName">
    <property name="name" value="me"/>
</bean>

7.3 byType自动装配

byType:

  • 会自动在容器上下文中查找和自己对象属性类型相同类型的Bean,可以省略id
  • 如果存在多个相同类型的bean,会装配失败
<bean class="com.cha.pojo.Cat"/>
<bean id="dog23" class="com.cha.pojo.Dog"/>

<!--
byType: 会自动在容器上下文中查找和自己对象属性类型相同类型的Bean,可以省略id。如果存在多个相同类型的bean,会装配失败
-->
<bean id="people" class="com.cha.pojo.People" autowire="byType">
    <property name="name" value="me"/>
</bean>

byName和byType自动装配总结

  • byName:需要保证所有Bean的id唯一,并且bean需要和自动注入的属性的set方法的值一致,首字母小写
  • byType:需要保证所有Bean的class唯一,并且bean需要和自动注入的类型一致

7.4 注解自动装配

jdk1.5,Spring2.5开始支持注解

使用注解(以Autowired为例)

  1. 导入约束
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">
  1. 配置注解的支持
<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>
  1. 在类中添加注解
@Autowired
  • 可以添加在属性和set方法上使用
  • 使用@Autowired可以不编写set方法,前提是自动装配的实行在IoC(Spring)容器中存在,且符合byType的要求

@Autowired会现根据类型进行注入(byType),如果容器中有多个满足类型的实例,就会根据ID进行注入(byName)

@Autowired的参数

@Autowired的参数设为false:

  • 该对象可以null,否则不允许为空

  • 赋值失败不报错,默认赋值为null,但代码运行会出现空指针异常

public @interface Autowired {
    boolean required() default true;
}
@Autowired(required = false)

类型重复时,可以通过@Qualifier指定自动装配的id,指定唯一的bean对象

@Autowired
@Qualifier(value = "dog222")

@Recource

根据ID进行注入(byName),如果容器中有多个满足id的实例,就会根据类型进行注入(byType)

可以通过对name参数的设置限制id

@Resource(name = "cat")

最好是将@Resource放在setter方法上

@Autowired 和@Resource的对比

  • 共同点:
    • 都用来自动装配,都可以放在属性字段上
  • 区别:
    • @Autowired优先通过byType注入
    • @Resource优先通过byName注入

@Nullabel

字段标记该注解,说明这个字段可以为null

8. 使用注解开发

8.1 配置环境

在Spring4后,使用注解开发,必须保证aop的包导入,导入context约束,增加注解的支持

aop和context约束环境:

<?xml version="1.0" encoding="UTF8"?>
<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"
       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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:annotation-config/>

</beans>

一种扫描注解方式:

<!--指定要扫描的包,包下的注解就会生效-->
<context:component-scan base-package="com.cha.pojo"/>

8.2 bean的创建和属性的注入

@Component

自动生成bean对象
相当于 <bean id="user" class="com.cha.pojo.User"/>
@Component 组件

@Component
public class User {
    public String name = "cha";
}

@Value

进行赋值,相当于<property name="name" value="cha"/>

也可以放在set方法上

@Component
public class User {
    @Value("cha")
    public String name;
}

8.3 衍生注解

@Component有几个衍生注解,按照web开发中的MVC三层架构分层:

  • dao:@Repository
  • service:@Service
  • controller:@Controller

四个注解的功能相同,都是将某个类注册到Spring中,装配Bean

8.4 自动装配注解(同7.4)

7.4 中内容

8.5 作用域

通过@Scope注解对作用域进行限制(作用域,6.5)

  • singleton 单例模式
  • prototype 原型模式
@Scope("prototype")

8.6 总结

  • xml与注解

    • xml:万能,适用于任何场合,维护简单方便
    • 注解:不是自己类使用不了,维护相对复杂
  • 常用时间

    • xml:用于管理bean
    • 注解:完成属性的注入
    • 在使用的过程中,需要注意:要让注解生效,需要开启注解的支持

9. 使用Java配置Spring(JavaConfig)(该部分的理解可能有错误)

不适用Spring的xml配置,使用java进行配置。

JavaConfig是Spring的子项目,在Spring4之后成为核心功能。

  1. 实体类:使用@Value在set方法上进行赋值
@Conponent // 配合@Configuration + 扫描使用,如果使用@Bean可以不加,理解可能有错
public class User {
    private String name;

    public String getName() {
        return name;
    }

    @Value("cha")
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  1. 配置类config

    • 使用@Bean注解指定要交于spring托管的bean

    • 对应的类名为方法名,相当于bean标签中的id属性,对@Bean传参可以指定bean的名字

    • 返回值为对应的对象,相当于bean标签的class属性

@Configuration:是一个@Component,添加@Configuration代表这是个配置类,不添加会实例化两次

@Configuration // 是一个@Component,添加@Configuration代表这是个配置类,
public class MyConfig {
    // Bean注释生成实体类,类名为方法名,返回对象
    @Bean
    public User getUser() {
        return new User();
    }
}
  • @Configuration的两种方法
    • @Configuration + 配置类@Bean:相当于bean标签
    • @Configuration + 扫描@ComponentScan("com.cha.pojo")需要给实体类添加@Conponent注释,使用小写类名getBean
    • 都使用会创建两个对象
  1. 测试
    • 使用AnnotationConfigApplicationContext()上下文获取容器context
    • 通过配置类的class对象加载
@Test
public void Test() {
    ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
    User user = context.getBean("getUser", User.class);
    System.out.println(user.toString());
}
  1. 引入其他配置类:@Import(配置类.class)

10. 代理模式

代理模式是SpringAOP的底层,SpringAOP和SpringMVC面试必问

代理模式分为:

  • 静态代理
  • 动态代理

10.1 静态代理

角色分析:

  • 抽象角色:一半使用接口或抽象类
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理后会进行一些附属操作
  • 客户:访问代理对象的人

代码实现步骤

  1. 接口
// 出租房屋
public interface Rent {
    public void rent();
}
  1. 真实角色
// 房东
public class Host implements Rent {
    public void rent() {
        System.out.println("出租房子");
    }
}
  1. 代理角色
// 代理房东出租房子
public class Proxy implements Rent {
    private Host host;

    public Proxy() {}

    public Proxy(Host host) {
        this.host = host;
    }

    // 代理房东出租房屋
    public void rent() {
        host.rent();
    }
    
    //可以执行其他附加操作
}
  1. 客户
// 客户
public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        // 代理
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}

优缺点

优点:

  • 可以使真实角色的操作更加纯粹,不用去关注公共业务
  • 公共业务交给代理角色,实现类业务的分工,降低耦合性
  • 公共业务扩展时便于集中管理,将需要拓展的功能交给代理,不需要修改源代码

缺点:

  • 一个真实角色会产生一个代理角色,开发效率会降低:动态代理

静态代理的理解和AOP关系

image

10.2 动态代理

  • 动态代理和静态代理的角色相同

  • 动态代理的代理类是动态生成的

  • 动态代理的两大类:基于接口,基于类

    • 基于接口:JDK动态代理
    • 基于类:cglib
    • java字节码实现:javasist

两个类:proxyInvocationHandler

  • Proxy:获得代理类
  • InvocationHandler:调用处理程序

优点:

  • 可以使真实角色的操作更加纯粹,不用去关注公共业务
  • 公共业务交给代理角色,实现类业务的分工,降低耦合性
  • 公共业务扩展时便于集中管理,将需要拓展的功能交给代理,不需要修改源代码
  • 一个动态代理类代理的是一个接口,对应的是一个业务
  • 一个动态代理可以代理多个类,只要是实现同一个接口

实现步骤

  • 代理创建的工具类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成得到代理类
    // 参数中的this实际上是invoke方法
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    // 处理代理实例,并返回结果
    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 msg) {
        System.out.println("执行了" + msg);
    }
}
  • 使用举例
public class Client {
    public static void main(String[] args) {
        // 真实角色
        UserServiceImpl userService = new UserServiceImpl();
        // 代理角色:未填充
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        // 设置代理角色
        pih.setTarget(userService);
        // 动态生成代理类
        UserService proxy = (UserService) pih.getProxy();

        proxy.add();
    }
}

11. AOP

11.1 什么是AOP

AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

image

11.2 AOP在Spring中的作用

提供声明式事务;运行用户自定义切面

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等...
  • 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个
  • 通知(Advice) :切面必须要完成的工作。即,它是类中的一个方法
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(Point Cut):切面通知执行的“地点"的定义。
  • 连接点(Joint Point) :与切入点匹配的执行点。 (动态代理中的invoke)

SpringAOP中,通过Advice定义横切逻辑,Spring支持五种类型的Advice:

  • 前置通知(before advice):在 join point 前被执行的 advice
  • 后置通知(after return advice):在一个 join point 正常返回后执行的 advice
  • 环绕通知(around advice):在 join point 前和 joint point 退出后都执行的 advice
  • 异常抛出通知(after throwing advice):当一个 join point 抛出异常后执行的 advice
  • 引介通知(introduction):为原有的对象增加新的属性和方法

在AOP不改变原代码是,增加新的功能

11.3 使用Spring实现AOP

需要导入依赖包,配置aop约束

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
    <scope>runtime</scope>
</dependency>

测试相关类

  • 前置通知
public class Log implements MethodBeforeAdvice {
    // method: 要执行的目标对象的方法
    // args: 参数
    // target: 目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "执行了");
    }
}
  • 后置通知
public class AfterLog implements AfterReturningAdvice {
    // returnValue: 返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行" + method.getName() + "返回结果:" + returnValue );
    }
}
  • 测试类

获取的bean的类型必须是接口类型

public class MyTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理返回的是接口,返回的类型需要是接口
        UserService userService = context.getBean("userService", UserService.class);

        userService.add();
    }
}
  • 注册bean
<!--注册bean-->
<bean id="userService" class="com.cha.service.UserServiceImpl"/>
<bean id="log" class="com.cha.log.Log"/>
<bean id="afterLog" class="com.cha.log.AfterLog"/>

方式一:使用api接口

<!--方式一:使用Spring API接口-->
<!--配置AOP:需要导入aop的约束-->
<aop:config>
    <!--切入点:
    expression: 表达式
    execution(要执行的位置): * * * * *(修饰词 返回值 类名 方法名 参数)
    (..): 任意参数
    -->
    <aop:pointcut id="pointcut" expression="execution(* com.cha.service.UserServiceImpl.*(..))"/>

    <!--执行环绕增强-->
    <!--
    advice-ref:切入的类
    pointcut-ref: 切入位置
    -->
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

方式二:使用自定义类(注重切面定义)

  • 自定义类
public class DiyPointCut {
    public void before() {
        System.out.println("=========执行前=========");
    }

    public void after() {
        System.out.println("=========执行后=== =====");
    }
}
  • 配置文件
<bean id="diy" class="com.cha.diy.DiyPointCut"/>
<aop:config>
    <!--自定义切面, ref: 引用的类-->
    <aop:aspect ref="diy">
        <!--切入点-->
        <aop:pointcut id="point" expression="execution(* com.cha.service.UserServiceImpl.*(..))"/>
        <!--通知-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
</aop:config>

方式三:使用注解实现

  • 定义方法
@Aspect // 标注类未切面
public class AnnotationPointCut {

    @Before("execution(* com.cha.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("=========执行前=========");
    }

    @After("execution(* com.cha.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("=========执行后=== =====");
    }

    // 在环绕增强中,可以给定一个参数,代表要切入的点
    @Around("execution(* com.cha.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");

        System.out.println(jp.getSignature()); // 获得签名
        // 执行方法
        Object proceed = jp.proceed();

        System.out.println("环绕后");
    }
}
  • 配置
<!--方式三-->
<bean id="annotationPointCut" class="com.cha.diy.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
  • 执行结果:Around前 - Before - 方法 - After - Around后
环绕前
void com.cha.service.UserService.add()
=========执行前=========
增加了
=========执行后=== =====
环绕后

其他:cglib实现

cglib: proxy-target-class="true"

<!--开启注解支持 proxy-target-class默认为false-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

12. 整合Mybatis

步骤:

  1. 导入相关jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关
    • aop织入
    • mybatis-spring(新知识)
  2. 编写配置文件

  3. 测试

12.1 mybaits复习

  1. 编写实体类
  2. 编写核心配置文件
  3. 编写接口
  4. 编写Mapper.xml
  5. 测试

12.2 mybatis-spring

  1. 编写数据源配置
  2. sqlSessionFactory
  3. sqlSessionTemplate
  4. 给接口加实现类
  5. 将实现类注入到spring中
  6. 测试

方法一:复杂

  • mybatis-config.xml :可以不进行配置,被spring配置替代
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
    <typeAliases>
        <package name="com.cha.pojo"/>
    </typeAliases>
</configuration>
  • spring-dao.xml:
<?xml version="1.0" encoding="UTF8"?>
<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"
       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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--DataSource: 这里使用Spirng的数据源替换Mybatis的配置 c3p0 dbcp druid
    此处使用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/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=GMT"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--帮i的那个Mybatis配置文件, value中的classpath: 可以不写-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <property name="mapperLocations" value="com/cha/mapper/*.xml"/>
    </bean>

    <!--SqlSessionTemplate:就是sqlSession, Template为模板,仅能使用配置赋值-->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--没有set方法,智能使用构造器注入sqlSessionFactory-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>
  • UserMapperImpl.java
public class UserMapperImpl implements UserMapper {
    // 所有操作都使用sqlSessionTemplate执行
    private SqlSessionTemplate sqlSessionTemplate;

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    public List<User> selectUser() {
        UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
  • applicationContext.xml
<?xml version="1.0" encoding="UTF8"?>
<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"
       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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <import resource="spring-dao.xml"/>
    <bean id="userMapper" class="com.cha.mapper.UserMapperImpl">
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
    </bean>
</beans>
  • 测试
@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);
    }
}

方法二

spring-dao.xml为配置文件,需要存在

  • 实现类:通过继承SqlSessionDaoSupport省略getSqlSession()的中间步骤
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}
  • 实体类注入spring
<bean id="userMapper2" class="com.cha.mapper.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

13. 声明式事务

13.1 回顾

  • 把一组业务当成一个业务,要么都成果,要么都失败
  • 在项目开发中涉及到一致性问题,十分重要
  • 确保完整性和一致性

事务的ACID原则:

  • 原子性
  • 一致性
  • 隔离性:多个业务可能操作同一个资源,防止数据损坏
  • 持久性:事务一旦提交,结果会被持久化的写道存储器中

13.2 spring中的事务

  • 声明式事务:AOP
  • 编程式事务:需要在代码中进行事务的管理

如果步骤spring中配置声明式事务,就需要在代码中手动配置事务

spring-dao.xml中配置声明式事务

  • 声明事务transactionManager
  • 事务通知tx
  • 事务切入aop
<!--配置声明式事务-->
<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="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!--配置事务切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.cha.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
posted @ 2022-07-07 17:02  chachan53  阅读(75)  评论(0编辑  收藏  举报