Spring

1、Spring入门

1.1 简介

  • Spring:给软件行业带来了春天
  • 2002年,首次推出Spring框架的雏形interface 21 框架
  • Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。
  • Rod Johnson,Spring Framework创始人
  • Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
  • SSH:Struct2 + Spring + Hibernate
  • SSM:SpringMVC + Spring + MyBatis
  • 官网
  • 官方下载地址
  • github地址
  • 文档

Maven依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.0.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.0.RELEASE</version>
    </dependency>
</dependencies>

1.2 优点

  • Spring是一个开源的免费的框架(容器)
  • 轻量级的,非入侵式(引入不会改变原来代码)的框架
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持!

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

1.3 组成

在这里插入图片描述

1.4 拓展

现代化Java开发:基于Spring的开发

在这里插入图片描述

  • Spring Boot

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

    • Spring Cloud是基于Spring Boot实现的

现在大多数公司都在使用Spring Boot进行快速开发,学习Spring Boot的前提是完全掌握Spring 和SpringMVC! 承上启下的作用!

弊端:发展太久后,违背了原来的理念!配置十分繁琐,人称配置地狱!

2、IOC

2.1 IOC理论推导

原来的项目流程:

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

在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改源代码!当程序代码量很大时,修改一次的成本比较大!

在这里插入图片描述
在这里插入图片描述

我们使用一个Set接口实现,革命性改变(从UserServiceImpl代码改变到MyTest代码改变,即从项目源码改变到客户端按钮改变,从0到1)
在这里插入图片描述
在这里插入图片描述

  • 之前,程序是主动创建对象!控制权在程序猿手上!
  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象!

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

2.2 IOC本质

控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI只是IOC的另一种说法。没有IOC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

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

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

3、 HelloSpring

代码实现:

  1. 实体类:
// 编写一个Hello实体类(属性和get/set,toString方法)
public class Hello {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}

  1. Spring配置文件(暂时命名为bean.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">

    <!--
        使用spring来创建对象,在spring中这些都称为Bean
        使用new创建对象
        类名 对象名 = new 类名();
        Hello hello = new Hello();
        给属性赋值
        hello.setStr = "Spring";

        创建对象
        对应bean标签中的:
            id = 对象名
            class = 类名(写全包名)

        给属性赋值
        对应property标签中的:
            name = str(属性名)
            value = Spring(属性值)

    -->
    <bean id="hello" class="com.GrandNovice.pojo.Hello">
        <!--等价于创建一个Hello类的对象hello,并将str属性赋值为Spring-->
        <property name="str" value="Spring"/>
    </bean>

</beans>
  1. 测试
public class MyTest {
    public static void main(String[] args) {
        // 获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        // 我们的对象现在都在Spring中管理了,我们要使用直接去里面取出来就可以了
        // 在Spring中获取Hello类的hello对象(需要强转)
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

回顾spring_01_ioc_01项目中的功能改用Spring注册实现

  1. 实体类不变
    在这里插入图片描述
  2. Spring配置文件(注意这里是引用数据类型的注册和获取)
<?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="userDaoMysql" class="com.GrandNovice.dao.UserDaoMysqlImpl"/>
    <bean id="userDaoOracle" class="com.GrandNovice.dao.UserDaoOracleImpl"/>
    <bean id="userDaoSqlServer" class="com.GrandNovice.dao.UserDaoSqlServerImpl"/>

    <bean id="userService" class="com.GrandNovice.service.UserServiceImpl">
        <!--
            ref:引用Spring容器中已经创建好的对象,引用数据类型
            value:基本数据类型的值使用value
        -->
        <property name="userDao" ref="userDaoSqlServer"/>
    </bean>


</beans>
  1. 测试
// 测试从Spring中获取对象
public class MyTestSpring {
    public static void main(String[] args) {
        // 获取ApplicationContext;拿到Spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 从容器中获取需要的东西——get
        UserServiceImpl userService = (UserServiceImpl) context.getBean("userService");
        userService.getUser();
        /**
         * 现在通过修改Spring的配置文件获取不同对象
         * 对象由Spring创建,管理,装配!
         */
    }
}

思考问题?

  • Hello 对象是谁创建的?
    hello对象是由Spring创建的

  • Hello对象的属性是怎么设置的?
    hello对象的属性是由Spring容器设置的,

这个过程就叫控制反转:

控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的.

反转:程序本身不创建对象,而变成被动的接收对象﹒

依赖注入:就是利用set方法来进行注入的.

IOC是一种编程思想,由主动的创建变成被动的接收.

可以通过newClassPathXmlApplicationContext去浏览一下底层源码

OK,到了现在,我们彻底不用再程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的loC,一句话搞定:对象由Spring来创建,管理,装配!

4、 IOC创建对象的方式

  1. 使用无参构造创建对象,默认!
  2. 假设我们要使用有参构造创建对象。
  • 下标赋值
<!--有参构造创建对象方式一:通过下标-->
<bean id="user" class="com.GrandNovice.pojo.User">
	<constructor-arg index="0" value="否否"/>
</bean>
  • 属性类型赋值(不建议使用,当出现两个同类型参数时会混淆)
<!--有参构造创建对象方式二:通过属性类型-->
<bean id="user" class="com.GrandNovice.pojo.User">
    <constructor-arg type="java.lang.String" value="方方"/>
</bean>
  • 直接通过参数名赋值
<!--有参构造方式三:直接通过参数名-->
<bean id="user" class="com.GrandNovice.pojo.User">
    <constructor-arg name="name" value="方方"/>
</bean>

总结:

在配置文件加载的时候,容器中管理的对象就已经初始化了!

5、 Spring配置

5.1 别名

<!-- alias 别名,如果添加了别名,我们可以使用别名或者原来名字获取对象-->
<alias name="user" alias="userAlias"/>
public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");  // 执行完这一句话(即加载完xml文件时),就是执行完了User的无参构造
        User user = (User) context.getBean("user");
        User userAlias = (User) context.getBean("userAlias");  // 通过别名获得对象,起了别名后,原来的名称仍然正常使用
        user.show();
        userAlias.show();
    }
}

5.2 Bean的配置

<!--
    id:bean的唯一标识符,相当于对象名
    class:bean对象所对应类型的全限定名(包名 + 类名)
    name:别名,name比Alias更好用,可以同时取多个别名(用逗号、分号分隔,或者用空格分隔都可以)
    scope:作用域,默认为单例  scope="singleton"
-->
<bean id="userT" class="com.GrandNovice.pojo.UserT" name="t, uT userT2; userT3" scope="singleton">
    <property name="name" value="袜袜"/>
</bean>

5.3 import

import一般用于团队开发使用,可以将多个配置文件,导入合并为一个

假设项目有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的xxx.xml合并为一个总的!

  • 张三
  • 李四
  • 王五
  • applicationContext.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">

    <import resource="beans.xml"/>
    <import resource="beans2.xml"/>
    <import resource="beans3.xml"/>


</beans>

在这里插入图片描述

6、 DI依赖注入

6.1 构造器注入

前面已介绍

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;
    }

    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                '}';
    }
}

  1. 真实测试对象
// 修改的toString方法
public class Student {
    private String name;
    private Address address;
    private String[] book;
    private List<String> hobbys;
    private Map<String, String> card;
    private Set<String> games;
    private Properties info;
    private String wife;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String[] getBook() {
        return book;
    }

    public void setBook(String[] book) {
        this.book = book;
    }

    public List<String> getHobbys() {
        return hobbys;
    }

    public void setHobbys(List<String> hobbys) {
        this.hobbys = hobbys;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

	// 修改了toString方法输出格式和address属性添加了toString方法
    @Override
    public String toString() {
        return "Student{" + '\n' +
                '\t' + "name='" + name + '\'' + ',' + '\n' +
                '\t' + "address=" + address.toString() + ',' + '\n' +
                '\t' + "book=" + Arrays.toString(book) + ',' + '\n' +
                '\t' + "hobbys=" + hobbys + ',' + '\n' +
                '\t' + "card=" + card + ',' + '\n' +
                '\t' + "games=" + games + ',' + '\n' +
                '\t' + "info=" + info + ',' + '\n' +
                '\t' + "wife='" + wife + '\'' + '\n' +
                '}';
    }
}


  1. 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="student" class="com.GrandNovice.pojo.Student">
        <!--第一种,普通值注入,直接使用<property name="name" value="方方"/>-->
        <property name="name" value="方方"/>
    </bean>


</beans>
  1. 测试类
public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = (Student) context.getBean("student");
        System.out.println("student.getName() = " + student.getName());
    }
}

完善注入信息:

<?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="com.GrandNovice.pojo.Address">
        <property name="address" value="河北秦皇岛"/>
    </bean>

    <bean id="student" class="com.GrandNovice.pojo.Student">

        <!--第一种,普通值注入,直接使用value注入-->
        <property name="name" value="方方"/>
        <!--第二种,bean注入,使用ref注入-->
        <property name="address" ref="address"/>

        <!--第三种,数组注入,使用双闭合标签<property></property>搭配展开的array标签-->
        <property name="book">
            <array>
                <value>红楼梦</value>
                <value>西游记</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>

        <!--第四种,列表注入,使用双闭合标签<property></property>搭配展开的list标签-->
        <property name="hobbys">
            <list>
                <value>打篮球</value>
                <value>敲代码</value>
                <value>看电影</value>
            </list>
        </property>

        <!--第四种,字典注入,使用双闭合标签<property></property>搭配展开的map标签-->
        <property name="card">
            <map>
                <entry key="学号" value="2014416438"/>
                <entry key="身份证号" value="3264783264723642389824747928"/>
                <entry key="银行卡号" value="3324274823670973264782"/>
            </map>
        </property>

        <!--第五种,集合注入,使用双闭合标签<property></property>搭配展开的set标签-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>糖果大作战</value>
                <value>CS</value>
            </set>
        </property>

        <!--第六种,空值注入,使用自标签<null/>-->
        <property name="wife">
            <null/>
        </property>

        <!--第七种,配置类注入,使用双闭合标签<property></property>搭配展开的prop标签-->
        <property name="info">
            <props>
                <prop key="driver">1971842</prop>
                <prop key="url">方方</prop>
                <prop key="username">18</prop>
                <prop key="password"></prop>
            </props>
        </property>


    </bean>

</beans>

6.3 其他方式注入

我们可以使用p命名空间和c命名空间进行注入
官方解释:
在这里插入图片描述

测试:

  1. 实体类
public class User {
    private String name;
    private int age;
    private Address address;

    public User() {
    }

    public User(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" + '\n' +
                '\t' + "name='" + name + '\'' + ',' + '\n' +
                '\t' + "age='" + age + '\'' + ',' + '\n' +
                '\t' + "address=" + address.toString() + ',' + '\n' +
                '}';
    }
}

  1. 配置文件
<?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:p="http://www.springframework.org/schema/p"
       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命名空间注入(对应set注入,需要有无参构造,因为无参构造是默认生成的,所以只要不是单独添加了有参构造把无参构造覆盖了情况下就可以使用)
        需要添加第三方约束:xmlns:p="http://www.springframework.org/schema/p"
    -->
    <bean id="user" class="com.GrandNovice.pojo.User" p:name="方方" p:age="18" p:address-ref="address"/>

    <bean id="address" class="com.GrandNovice.pojo.Address" p:address="北京"/>

    <!--
        当且仅当有有参构造器的时候才能使用c命名空间注入
        需要添加第三方约束:xmlns:c="http://www.springframework.org/schema/c"
    -->
    <bean id="user2" class="com.GrandNovice.pojo.User" c:name="袜袜" c:age="18" c:address-ref="address"/>

</beans>
  1. 测试
@Test
public void test02(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
    // User user = (User) context.getBean("user");
    User user = context.getBean("user2", User.class);  // 在参数中用反射声明类型就不用强制类型转换了
    System.out.println(user.toString());
    }

注意点:
p命名空间和c命名空间不能直接使用,需要导入xml约束
p: xmlns:p="http://www.springframework.org/schema/p"
c: xmlns:c="http://www.springframework.org/schema/c"

6.4 bean的作用域

在这里插入图片描述

6.4.1 singleton

在这里插入图片描述

Spring默认机制为单例模式,即scope="singleton",当您定义一个 bean 定义并且其作用域为单例时,Spring IoC 容器将为该 bean 定义所定义的对象创建一个实例。该单个实例存储在此类单例 bean 的高速缓存中,并且对该命名 bean 的所有后续请求和引用都返回该高速缓存的对象。

设置:

<bean id="user2" class="com.GrandNovice.pojo.User" c:name="袜袜" c:age="18" c:address-ref="address" scope="singleton"/>

或者直接什么都不写:

<bean id="user2" class="com.GrandNovice.pojo.User" c:name="袜袜" c:age="18" c:address-ref="address"/>

测试:

@Test
public void test02(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
    // User user = (User) context.getBean("user");
    User user = context.getBean("user2", User.class);  
    User u = context.getBean("user2", User.class);  
    System.out.println(user == u);  // true

}

6.4.2 prototype

在这里插入图片描述

每次对特定 bean 提出请求时,bean 部署的非单一原型范围都会导致创建一个新 bean 实例。也就是说,将 Bean 注入到另一个 Bean 中,或者您可以通过容器上的getBean()方法调用来请求它。通常,应将原型作用域用于所有有状态 Bean,将单例作用域用于 StatelessBean。

设置:

<bean id="user" class="com.GrandNovice.pojo.User" p:name="方方" p:age="18" p:address-ref="address" scope="prototype"/>

测试:

@Test
public void test3(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
    User user = context.getBean("user", User.class);
    User u = context.getBean("user", User.class);
    System.out.println(user == u);  // false
}

6.4.3 其他

request,session, application只能在web开发中使用到!

7、 Bean的自动装配

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

在Spring中有三种装配的方式

  1. 在xml中显式的配置
  2. 在java中显式配置(后续讲解)
  3. 隐式的自动装配[重点]

7.1 测试

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

7.2 byName自动装配

<bean id="cat" class="com.GrandNovice.pojo.Cat"/>
<bean id="dog" class="com.GrandNovice.pojo.Dog"/>

<!--
    byName:会自动在容器配置文件上下文中查找和自己对象set方法后面的值对应的bean id
-->
<bean id="human" class="com.GrandNovice.pojo.Human" autowire="byName">
    <property name="name" value="方方"/>
</bean>

7.3 byType自动装配

<bean class="com.GrandNovice.pojo.Cat"/>
<bean class="com.GrandNovice.pojo.Dog"/>

<!--
    byType:会自动在容器配置文件上下文中查找和自己对象属性类型相同的bean id(使用这种方式时注入类(Cat,Dog)的id可以省略,byName时不能省略class)
-->
<bean id="human" class="com.GrandNovice.pojo.Human" autowire="byType">
    <property name="name" value="方方"/>
</bean>

小结:

  • 使用byName时,需要保证所有bean的id唯一,并且这个bean需要和实体类中的set方法的参数名一致。
  • 使用byType时,需要保证所有的bean的class唯一,并且这个bean需要和实体类中的属性的类型一致。(使用这种方式时注入类(Cat,Dog)的id可以省略,)

7.4 使用注解实现自动装配

使用注解条件:

  1. 导入context约束 xmlns和xsi
  2. 配置注解支持 <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标签在属性或set方法上,一般在属性上(可以省略setter方法)

// 已省略set方法
public class Human {

    private String name;
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;

    public String getName() {
        return name;
    }

   

    public Cat getCat() {
        return cat;
    }

    

    public Dog getDog() {
        return dog;
    }

   

    @Override
    public String toString() {
        return "Human{" +
                "name='" + name + '\'' +
                ", cat=" + cat +
                ", dog=" + dog +
                '}';
    }
}

配置文件:

<?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/>

    <bean id="cat" class="com.GrandNovice.pojo.Cat"/>
    <bean id="dog" class="com.GrandNovice.pojo.Dog"/>
    <bean id="human" class="com.GrandNovice.pojo.Human"/>

</beans>

总结:@Autowired[使用频率高]
直接在属性上使用即可,也可以在set方式上使用!
使用@Autowired我们可以省略掉set方法,前提是自动装配的属性在IOC容器中存在,且符合byName

拓展:

  1. @Nullable字段标记了这个注解,说明这个字段可以为null;
public Human(@Nullable String name) {
    this.name = name;
}
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}

如果显式的定义了@Autowired(required = false),说明这个对象可以为null

  1. 当自动装配通过byName和byType都不能适配时,可以通过@Qualifier(value = “?”)锁定bean
<bean id="cat1" class="com.GrandNovice.pojo.Cat"/>
<bean id="cat2" class="com.GrandNovice.pojo.Cat"/>
<bean id="dog1" class="com.GrandNovice.pojo.Dog"/>
<bean id="dog2" class="com.GrandNovice.pojo.Dog"/>
<bean id="human" class="com.GrandNovice.pojo.Human"/>

这种bean无法自动装配,需要@Qualifier(value = “?”)辅助,指定一个唯一的bean对象注入

@Autowired
@Qualifier(value = "cat1")
private Cat cat;
@Autowired
@Qualifier(value = "dog1")
private Dog dog;
  1. @Resource注解
import javax.annotation.Resource;

public class Human {

    @Resource
    private Cat cat;

    @Resource
    private Dog dog;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.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;
    }

    @Override
    public String toString() {
        return "Human{" +
                "name='" + name + '\'' +
                ", cat=" + cat +
                ", dog=" + dog +
                '}';
    }
}

这种注解的使用要求配置文件必须满足byName或byType其中之一

既可满足byName:

<bean id="cat" class="com.GrandNovice.pojo.Cat"/>
<bean id="cat2" class="com.GrandNovice.pojo.Cat"/>
<bean id="dog" class="com.GrandNovice.pojo.Dog"/>
<bean id="dog2" class="com.GrandNovice.pojo.Dog"/>
<bean id="human" class="com.GrandNovice.pojo.Human"/>

又可满足byType:

<bean id="cat2" class="com.GrandNovice.pojo.Cat"/>
<bean id="dog2" class="com.GrandNovice.pojo.Dog"/>
<bean id="human" class="com.GrandNovice.pojo.Human"/>

PS:@Resource(name = "?") @Resource注解也可以指定bean id对应属性名称,等价于@Qualifier(value = “?”)

小结:
@Resource和@Autowired的区别:

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired通过byType/byName的方式实现,而且必须要求这个对象存在 [常用]
  • @Resource默认通过byName方式实现,byName找不到时,按照byType方式继续找,两个都找不到的情况下会报错

8、 使用注解开发

在Spring4之后,要使用注解开发,必须要保证导入AOP的包
使用注解需要导入context约束,使用注解支持

<?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. bean
  2. 属性

实体类:

@Component  // 组件 // 等价于<bean id="user" class="com.GrandNovice.pojo.User"/>
@Scope("singleton") // 作用域:单例模式...
public class User {
    public String name = "方方";

    @Value("18")  // 相当于<property name="age" value="18"/>  可以放在属性上,也可以放在属性对应的set方法上
    public int age;
}

配置文件仅仅开启注解,指定组件要扫描的包:

<?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:component-scan base-package="com.GrandNovice"/>
        
    <!--开启注解支持-->
    <context:annotation-config/>

</beans>

测试:

public class MyTest {
    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = context.getBean("user", User.class);
        System.out.println("user.name = " + user.name);
        System.out.println("user.age = " + user.age);
    }
}

  1. 衍生的注解
    @Component 有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!
  • dao【@Repository】
  • service 【@service】
  • controller 【@controller】

这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean

  1. 自动装配
- @Autowired :自动装配通过类型。名字
如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value="?")
- @Nullab1e字段标记了这个注解,说明这个字段可以为null;
- @Resource:自动装配通过名字。类型。

  1. 作用域

作用在类上:

@Scope("singleton") // 作用域:单例模式...
@Scope("prototype") // 作用域:原型模式...
  1. 小结

xml与注解:

  • xml更加万能,适用于任何场合!维护简单方便。
  • 注解只能作用于被注解的类,维护相对复杂!

xml 与注解最佳实践:

  • xml 用来管理bean;
  • 注解只负责完成属性的注入;
  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持!
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.GrandNovice"/>
    
<!--开启注解支持-->
<context:annotation-config/>

9、 使用Java配置类的方式配置Spring

我们现在完全不使用Spring的xml配置,全权交给Java来做!
JavaConfig 是Spring的一个子项目,在Spring 4之后,它成为了一个核心功能!

实体类:

@Component  // 这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
public class User {
    @Value("方方")  // 属性注入值
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

java配置类1,GrandNoviceConfig1:

// @Configuration代表配置类,等价于之前的beans.xml
@Configuration  // 这个也会被Spring容器托管,注册到容器中,因为它本来就是一个@Component
@ComponentScan("com.GrandNovice.pojo")
@Import(GrandNoviceConfig2.class)  // 导入另一个配置类GrandNoviceConfig2
public class GrandNoviceConfig1 {

    /**
     * 注册一个bean,相当于我们之前写的一个bean标签,
     * 这个方法的名字就相当于bean标签中的id属性,返回值就相当于bean标签中的class属性
     */
    @Bean
    public User getUser() {
        return new User();  // 返回要注入到bean中的对象
    }

}

java配置类2,GrandNoviceConfig2(被导入GrandNoviceConfig1):

@Configuration  
@ComponentScan("com.GrandNovice.pojo")
public class GrandNoviceConfig2 {

    @Bean
    public String getString() {
        return "我爱你";  
    }
}

测试类:

public class MyTest {
    @Test
    public void test01(){
        // Java配置类 new ACAC   正常配置文件  new CPXAC
        // 如果完全使用了配置类去做,我们就只能通过AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!
        ApplicationContext context = new AnnotationConfigApplicationContext(GrandNoviceConfig1.class);
        User getUser = context.getBean("getUser", User.class);  // 参数为配置类的@Bean下对应的方法名
        String getString = (String) context.getBean("getString");
        System.out.print(getString);
        System.out.println(getUser.getName());
    }
}

输出:
在这里插入图片描述

这种纯Java的配置方式,在SpringBoot中随处可见!

10、 代理模式

为什么要学习代理模式?因为这是SpringAOC的底层实现 【SpringAOP 和 SpringMVC】
代理模式的分类:

  • 静态代理
  • 动态代理

在这里插入图片描述

10.1 静态代理

角色分析:

  • 抽象角色:一般会使用接口或抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色的角色,其代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理角色的角色

代码步骤:

  1. 接口
// 租房的接口
public interface Rent {
    public void rent();
}
  1. 真实角色
// 房东,真实角色,往外租房
public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("房东要外租房子");
    }
}

  1. 代理角色
public class Proxy implements Rent{
    private Host host;

    public Proxy() {
    }

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

    @Override
    public void rent() {
        seeHouse();
        host.rent();
        fee();
        contract();
    }

    // 看房
    public void seeHouse() {
        System.out.println("中介带你看房");
    }

    // 收中介费
    public void fee() {
        System.out.println("收中介费");
    }

    // 签租房合同
    public void contract() {
        System.out.println("签租房合同");
    }
}

  1. 客户端访问代理角色
public class Client {
    // 直接找房东租房
    @Test
    public void test01(){
        Host host = new Host();
        host.rent();
    }

    // 通过中介租房子
    @Test
    public void test02(){
        // 房东要租房子
        Host host = new Host();
        // 中介帮房东租房子,代理角色(中介)还有一些附属操作
        Proxy proxy = new Proxy(host);
        
        proxy.seeHouse();
        proxy.fee();
        proxy.rent();
        proxy.contract();
    }
}

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共业务交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,代码量翻倍,开发效率变低

10.2 AOP理解

AOP
代码步骤:

  1. service层接口
public interface UserService {
    // 增删改查
    public void add();
    public void delete();
    public void update();
    public void query();
}

  1. service层实现类
// 真实角色
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 query() {
        System.out.println("查询用户");
    }
}

  1. service层实现类的代理
public class UserServiceProxy implements UserService {

    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    // 日志方法
    public void log(String msg) {
        System.out.println("[DEBUG:]使用了" + msg + "方法!");
    }

    /**
     * 直接多出来一个类,相比于在原来代码上直接添加有什么必要?
     *    改动原有的业务代码,在公司中是大忌
     */
}
  1. 客户端访问代理角色
public class Client {
    @Test
    public void test01(){
        UserServiceImpl userService = new UserServiceImpl();

        userService.add();
        userService.delete();
        userService.update();
        userService.query();

    }
    
    @Test
    public void test02(){
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceProxy userServiceProxy = new UserServiceProxy();
        userServiceProxy.setUserService(userService);

        userServiceProxy.add();
        userServiceProxy.delete();
        userServiceProxy.update();
        userServiceProxy.query();
    }
}

在这里插入图片描述

10.3 动态代理

  • 动态代理和静态代理角色一样

  • 动态代理的代理类是动态生成的,不是我们直接写好的

  • 动态代理分为两大类:

    • 基于接口的动态代理——JDK动态代理(学习这个)
    • 基于类的动态代理——cglib
    • java字节码实现——Javassist

需要了解两个类:Proxy, InvocationHandler

  • 动态代理的好处:
    • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
    • 公共也就就交给代理角色!实现了业务的分工!
    • 公共业务发生扩展的时候,方便集中管理!
    • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
    • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!

动态代理demo1代码:
租房接口:

// 租房的接口
public interface Rent {
    public void rent();
}

房东实体类:

// 房东,真实角色,往外租房
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要外租房子");
    }
}

动态代理类生成类:

// 使用这个类动态生成代理类
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);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 动态代理的本质,就是使用反射机制实现
        seeHouse();
        Object result = method.invoke(rent, args);
        contract();
        return result;
    }

    public void seeHouse() {
        System.out.println("中介带看房");
    }

    public void contract() {
        System.out.println("签合同");
    }
}

测试类:

public class Client {
    @Test
    public void test(){
        // 真实角色——外租房子的角色
        Host host = new Host();

        // 代理角色——现在没有(只有代理角色处理程序,通过其生成一个代理)
        // 首先实例化代理角色处理类
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        // 通过调用代理角色处理类对象调用程序处理我们要调用的接口对象
        proxyInvocationHandler.setRent(host);

        Rent proxy = (Rent) proxyInvocationHandler.getProxy();  // 这里的proxy就是动态生成的
        proxy.rent();
    }
}

动态代理demo2代码:

可以生成动态代理工具类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 使用这个类动态生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

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

    // 2.set方法
    public void setTarget(Object target) {
        this.target = target;
    }

    // 3.生成代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    // 4.处理代理实例,并返回结果
    @Override
    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("[DEBUG:]执行了" + msg + "方法");
    }

    /**
     * Proxy:生成动态代理实例
     * InvocationHandler:调用处理程序并返回一个结果
     */
}

Client测试类:

import com.GrandNovice.demo02.UserService;
import com.GrandNovice.demo02.UserServiceImpl;
import org.junit.Test;

public class Client {
    @Test
    public void test(){
        // 1.真实角色
        UserService userService = new UserServiceImpl();

        // 2.代理角色,不存在
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();

        // 3.设置要代理的对象
        proxyInvocationHandler.setTarget(userService);

        // 4.动态生成代理类并强转
        UserService proxy = (UserService) proxyInvocationHandler.getProxy();

        proxy.add();
        proxy.delete();
        proxy.update();
        proxy.query();
    }
}

11、AOP

11.1 什么是AOP

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

在这里插入图片描述

11.2 AOP在Spring中的作用

提供声明式事务;允许用户自定义切面

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

在这里插入图片描述

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

通知类型连接点实现接口
前置通知方法方法前org.springframework.aop.MethodBeforeAdvice
后置通知方法后org.springframework.aop.AfterReturningAdvice
环绕通知方法前后org.aopalliance.intercept.MethodInterceptor
异常抛出通知方法抛出异常org.springframework.aop.ThrowsAdvice
引介通知类中增加新的方法属性org.springframework.aop.IntroductionInterceptor

即Aop在不改变原有代码的情况下,去增加新的功能.

11.3 使用Spring实现AOP

【重点】使用AOP织入,需要导入一个依赖包!

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
</dependencies>

坑:xml配置文件中包名大小写写错,后面类名照样可以自动补全,IDEA——我的智能超乎你想象

方式一:使用Spring的接口[主要是Spring API 接口实现]
接口

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}

接口实现类

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 select() {
        System.out.println("查询用户");
    }
}

前日志

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {
    // method:要执行的目标对象的方法
    // args:参数列表
    // target:目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行!");
    }
}

后日志

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

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);
    }
}

配置文件

<?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: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="com.GrandNovice.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.GrandNovice.log.BeforeLog"/>
    <bean id="afterLog" class="com.GrandNovice.log.AfterLog"/>

    <!--方式一:使用原生的Spring API接口-->
    <!--配置AOP:需要导入aop的约束-->
    <aop:config>
        <!--
            切入点
            expression:表达式
            execution(返回值类型 包名和类名 方法名 参数列表)要执行的位置
        -->
        <!--表达式选中了UserServiceImpl类中的所有方法所有参数-->
        <aop:pointcut id="pointcut" expression="execution(* com.GrandNovice.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增强-->
        <!--把log和afterLog切入到expression中UserServiceImpl类中的全部方法上-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
    
</beans>

测试类

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理代理的是接口
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
        userService.delete();
        userService.update();
        userService.select();
    }
}

方式二:自定义类来实现[主要是切面定义]

接口:同上
接口实现类:同上
DIY切面

public class DIYPointCut {
    public void before() {
        System.out.println("--------------方法执行前--------------");
    }

    public void after() {
        System.out.println("--------------方法执行后--------------");
    }
}

配置文件

<?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: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="com.GrandNovice.service.UserServiceImpl"/>

    <!--方式二:自定义类-->
    <bean id="diy" class="com.GrandNovice.diy.DIYPointCut"/>

    <aop:config>
        <!--自定义切面,ref要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.GrandNovice.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

测试类

@Test
public void test02(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
    UserService userService = (UserService) context.getBean("userService");

    userService.add();
    userService.delete();
    userService.update();
    userService.select();

}

方式三:使用注解实现
接口:略
接口实现类:略
注解切面类:AnnotationPointCut

/* Java

author:GrandNovice  

time:2020/10/23

*/
package com.GrandNovice.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// 方式三:使用注解方式实现AOP
@Aspect  // 标注这个类是一个切面
public class AnnotationPointCut {

    @Before("execution(* com.GrandNovice.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("--------------方法执行前--------------");
    }

    @After("execution(* com.GrandNovice.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("--------------方法执行后--------------");
    }

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

        // System.out.println("Signature:" + proceedingJoinPoint.getSignature());  // 输出:Signature:void com.GrandNovice.service.UserService.add()
        // 执行方法
        Object proceed = proceedingJoinPoint.proceed();

        System.out.println("环绕后");
    }


}

配置文件

<?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: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="com.GrandNovice.service.UserServiceImpl"/>
    <bean id="log" class="com.GrandNovice.log.Log"/>
    <bean id="afterLog" class="com.GrandNovice.log.AfterLog"/>

    <!--方式三:注解-->
    <bean id="annotationPointCut" class="com.GrandNovice.annotation.AnnotationPointCut"/>

    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>

</beans>

测试类

@Test
    public void test03(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
        UserService userService = (UserService) context.getBean("userService");

        userService.add();
        userService.delete();
        userService.update();
        userService.select();

        /**
         * 执行顺序:
         * 环绕前
         * --------------方法执行前--------------
         * 增加用户
         * 环绕后
         */

    }

12、 整合MyBatis

步骤:

  1. 导入相关jar包

     - junit
     - mybatis
     - mysql数据库
     - Spring
     - aop织入
     - mybatis-spring
    
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>

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

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.2</version>
    </dependency>

</dependencies>
  1. 编写配置文件

  2. 测试

12.1 回忆mybatis

  1. 编写实体类
import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private String pwd;
}

  1. 编写核心配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>

    <typeAliases>
        <package name="com.GrandNovice.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.GrandNovice.mapper.UserMapper"/>
    </mappers>

</configuration>

  1. 编写接口
import com.GrandNovice.pojo.User;

import java.util.List;

public interface UserMapper {
    public List<User> selectUser();

}

  1. 编写Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口,相当于原来对应接口的实现类-->
<mapper namespace="com.GrandNovice.mapper.UserMapper">

    <select id="selectUser" resultType="user">
        select * from mybatis.user;
    </select>

</mapper>
  1. 测试
import com.GrandNovice.mapper.UserMapper;
import com.GrandNovice.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyTest {
    @Test
    public void test01() throws IOException {
        String resources = "mybatis-config.xml";
        InputStream in = Resources.getResourceAsStream(resources);
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sessionFactory.openSession(true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.selectUser();
        for (User user : userList) {
            System.out.println(user);
        }
    }
}

12.2 Mybatis-Spring

简介:MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

将mybatis注入spring方式一

  1. 导入jar包
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.5</version>
</dependency>
  1. 编写数据源
<!--
    DataSource:使用Spring的数据源替换Mybatis的配置 还有c3p0 dbcp druid等
    我们使用Spring提供的JDBC
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>
  1. SqlSessionFactory
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <!--绑定Mybatis配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!--替代Mybatis配置文件中注册的Mapper,不可跟mybatis-config.xml中的mapper注册共存-->
    <property name="mapperLocations" value="classpath:com/GrandNovice/mapper/*.xml"/>
</bean>
  1. SqlSessionTemplate
<!--SqlSessionTemplate:即我们使用的sqlSession-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <!--构造器注入值(只能通过构造器注入,因为该类没有set方法)-->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

汇总到spring-dao.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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       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.DataSource
        DataSource:使用Spring的数据源替换Mybatis的配置 还有c3p0 dbcp druid等
        我们使用Spring提供的JDBC
    -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--2.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:com/GrandNovice/mapper/*.xml"/>
    </bean>

    <!--3.SqlSessionTemplate:即我们使用的sqlSession-->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--构造器注入值(只能通过构造器注入,因为该类没有set方法)-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

	<!--4.给接口加实现类UserMapperImpl-->
    <!--5.将实现类UserMapperImpl注入到spring容器-->
    <bean id="userMapper" class="com.GrandNovice.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSessionTemplate"/>
    </bean>
</beans>
  1. 给接口加实现类

(Mapper.xml写SQL语句,因为sqlSession在Spring容器中被注册,面向对象,所以要加实现类)

public class UserMapperImpl implements UserMapper{

    // 我们的所有操作都使用sqlSession来执行,在原来,现在都是用SqlSessionTemplate;
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

  1. 将实现类注入到spring容器
<!--将实现类UserMapperImpl注入到spring容器-->
<bean id="userMapper" class="com.GrandNovice.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>
  1. 将spring-dao.xml注入到applicationContext.xml,并将实现类的bean改写到applicationContext.xml中,spring-dao.xml中的bean省略,以后一律在applicationContext.xml中写实现类的bean
<?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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       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">

    <import resource="spring-dao.xml"/>

    <!--注册UserMapperImpl类-->
    <bean id="userMapper" class="com.GrandNovice.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSessionTemplate"/>
    </bean>


</beans>
  1. 测试
@Test
public void test02(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }
}

将mybatis注入spring方式二

  1. UserMapperImpl2实现类
/**
 * SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。
 * 调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法
 * 洗完然后配到spring容器中去
 */
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}
  1. 将mybatis注入到spring

方式二对于spring-dao.xml文件可以将SqlSessionTemplate的bean删除

<!--
        方式二:使用UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper
        同样方式二整合到applicationContext.xml中时下面的userMapper的bean需要在applicationContext.xml中配置
        因为第二种方式没有使用到SqlSessionTemplate,因此使用方式二时上面一段关于SqlSessionTemplate的配置也省略
    -->
<!--    <bean id="userMapper2" class="com.GrandNovice.mapper.UserMapperImpl2">-->
<!--        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
<!--    </bean>-->

方式二在applicationContext.xml的import和bean注入

<?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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       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">

    <import resource="spring-dao.xml"/>

    <!--方式一:将实现类UserMapperImpl注入到spring容器-->
    <bean id="userMapper" class="com.GrandNovice.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

    <!--方式二:使用UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper
		UserMapperImpl2没有属性,但是因为UserMapperImpl2继承了SqlSessionDaoSupport ,需要注入SqlSessionDaoSupport 的属性值
		使用这种方式可以省略spring配置文件中SqlSessionTemplate的bean!!!!
	-->
    <bean id="userMapper2" class="com.GrandNovice.mapper.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>


</beans>

实验步骤汇总:

  1. 实体类User
  2. 功能接口UserMapper
  3. 功能接口配置UserMapper.xml
  4. 在spring中配置mybatis
  5. 写一个实现类注入到spring
  6. 通过spring去测试

13、 声明式事务

13.1 事务回顾

事务特性:

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

事务的ACID原则:

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

13.2 spring中的事务管理

  • 声明式事务:AOP方式
    UserMapper接口中增加功能验证事务:
public interface UserMapper {
    public List<User> selectUser();

    // 添加一个用户
    public int addUser(User user);

    // 删除一个用户
    public int deleteUser(int id);

}

配置UserMapper.xml(其中设置错误以此验证事务)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口,相当于原来对应接口的实现类-->
<mapper namespace="com.GrandNovice.mapper.UserMapper">

    <select id="selectUser" resultType="user">
        select * from mybatis.user;
    </select>

    <insert id="addUser" parameterType="user">
        insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
    </insert>

    <!--让delete的sql故意出错-->
    <delete id="deleteUser" parameterType="int">
        deletes from mybatis.user where id = #{id};
    </delete>



</mapper>

在实现类中组合正确和错误的功能验证事务

import com.GrandNovice.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

/**
 * SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。
 * 调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法
 * 洗完然后配到spring容器中去
 */
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
    
    public List<User> selectUser() {
        User user = new User(5, "小王", "123123");

        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);

        mapper.addUser(user);  // 开启声明式事务管理不会添加,反之会添加
        mapper.deleteUser(5);  // 这个因sql语句错误,不会被执行
        /**
         * 为了保证事务ACID特性,所以应确保要么都成功,要么都失败
         */

        return mapper.selectUser();


    }

    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }
}

spring-dao.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:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--
        DataSource:使用Spring的数据源替换Mybatis的配置 还有c3p0 dbcp druid等
        我们使用Spring提供的JDBC
    -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <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" />
        <!--绑定Mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/GrandNovice/mapper/*.xml"/>
    </bean>

    <!--SqlSessionTemplate:即我们使用的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--构造器注入值(只能通过构造器注入,因为该类没有set方法)-->
        <constructor-arg index="0" 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" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="txPointCUt" expression="execution(* com.GrandNovice.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCUt"/>
    </aop:config>

</beans>
  • 编程式事务:需要在代码中,进行事务的处理

思考:
为什么需要事务?

  • 如果不配置事务可能存在数据提交不一致的情况;
  • 如果我们不Spring中配置声明式事务,我们就需要在代码中手动配置事务!
  • 事务在现实项目开发中十分重要!

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值