Spring

1、Spring

1.1、 简介

Spring : 春天 —>给软件行业带来了春天

2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。

2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。

很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。

Spring理念:简化服务器的开发,使现有的技术更加容易使用,本身就是一个大杂烩,整合了现有的技术框架。

  • SSH:Struct2 + Spring + Hibernate
  • SSM: SpringMvc + Spring + MyBatis

官网 :http://spring.io/

官方下载地址 :https://repo.spring.io/libs-release-local/org/springframework/spring/

GitHub : https://github.com/spring-projects

Maven地址:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.8</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

1.2 、优点

  • Spring是一个免费的开源的框架(容器)
  • Spring是一个轻量级的,非入侵式的框架
  • 控制反转(IOC),面向切面编程(AOP) 【重点】
    • Inversion of Control
    • Aspect-Oriented Programming
  • 支持事物的处理,对框架整合的支持

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

1.3、 组成

Spring-1

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .

Spring-2

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

1.4、组成

Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
  • Spring Cloud是基于Spring Boot实现的;
  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。

Spring-3

2、IOC

2.1、 引入IOC

新建一个空白的maven项目,使用这个项目来说明IOC原理

分析实现

先按照原来的方式写一段代码

1、先写一个UserDao接口

public interface UserDao {
   public void getUser();
}

2、再去写Dao的实现类

public class UserDaoImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("获取用户数据");
  }
}

3、然后去写UserService的接口

public interface UserService {
   public void getUser();
}

4、最后写Service的实现类

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoImpl();

   @Override
   public void getUser() {
       userDao.getUser();
  }
}

5、测试一下

@Test
public void test(){
   //这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下 .
   UserService service = new UserServiceImpl();
   service.getUser();
}

把Userdao的实现类增加一个

public class UserDaoMySqlImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("MySql获取用户数据");
  }
}

紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoMySqlImpl();

   @Override
   public void getUser() {
       userDao.getUser();
  }
}

在假设, 我们再增加一个Userdao的实现类

public class UserDaoOracleImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("Oracle获取用户数据");
  }
}

那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .

那我们如何去解决呢 ?

我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下

public class UserServiceImpl implements UserService{
    private UserDao userDao ;

    //利用set实现动态的值的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

现在去我们的测试类里 , 进行测试 ;

public class MyTest {
    @Test
    public void test(){
        UserServiceImpl service = new UserServiceImpl();
        service.setUserDao(new UserDaoOracleImpl());
        service.getUser();
    }
}

  • 之前,程序是主动创建对象,控制权在程序员手上!

    image-20210721104921591

  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象!

    image-20210721104942935

这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !

2.2、IOC本质

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

Spring-4

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

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

Spring-5

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

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

HelloSpring

编写代码

1、编写一个Hello实体类

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 + '\'' +
                '}';
    }
}

2、编写我们的spring文件 , 这里我们命名为beans.xml

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

    <!--bean就是java对象 , 由Spring创建和管理-->
    <!--使用Spring来创建对象,在Spring中这些都称为Bean
            类型  变量名 = new 类型()
            Hello hello = new Hello
            id=变量名
            class=需要 new 的类型或对象
            property=给对象中的属性赋值
    -->
    <bean id="hello" class="com.yt.pojo.Hello">
        <property name="name" value="Spring"/>
    </bean>

</beans>

3、我们可以去进行测试了

@Test
public void test(){
    //解析beans.xml文件 , 生成管理相应的Bean对象
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //getBean : 参数即为spring配置文件中bean的id .
    Hello hello = (Hello) context.getBean("hello");
    System.out.println("hello.toString() = " + hello.toString());
}

思考

  • Hello 对象是谁创建的 ? 【hello 对象是由Spring创建的】
  • Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的
  • 这个过程就叫控制反转 :
    • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
    • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

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

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

可以通过new ClassPathXmlApplicationContext去浏览一下底层源码 .

修改案例一

我们在案例一中, 新增一个Spring配置文件beans.xml

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

    <bean id="MysqlImpl" class="com.yt.dao.impl.UserDaoMysqlImpl"/>
    <bean id="OracleImpl" class="com.yt.dao.impl.UserDaoOracleImpl"/>

    <bean id="ServiceImpl" class="com.yt.service.impl.UserServiceImpl">
        <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
        <!--引用另外一个bean , 不是用value 而是用 ref-->
        <property name="userDao" ref="OracleImpl"/>
    </bean>

</beans>

测试!

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

@Test
public void test2(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
    serviceImpl.getUser();
}

2.3、IOC创建对象方式

通过无参构造方法来创建

1、User.java

public class User {

    private String name;

    public User() {
        System.out.println("user无参构造方法");
    }

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

    public String getName() {
        return name;
    }

    public void show(){
        System.out.println("name="+ name );
    }

}

2、beans.xml

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

   <bean id="user" class="com.yt.pojo.User">
       <property name="name" value="yt"/>
   </bean>

</beans>

3、测试类

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //在执行getBean的时候, user已经创建好了 , 通过无参构造
    User user = (User) context.getBean("user");
    //调用对象的方法 .
    user.show();
}

结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!

通过有参构造方法来创建

1、User.java

public class User {

    private String name;

    public User(String name) {
        this.name = name;
        System.out.println("有参构造");
    }

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

    public String getName() {
        return name;
    }

    public void show(){
        System.out.println("name="+ name );
    }

}

2、beans.xml 有三种方式编写

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 第一种根据index参数下标设置 -->
    <!-- 第二种根据参数类型设置 -->
    <!-- 第三种根据参数名字设置 -->
    <bean id="user" class="com.yt.pojo.User">
<!--        <constructor-arg index="0" value="yt"/>-->
<!--        <constructor-arg type="java.lang.String" value="yt"/>-->
        <constructor-arg name="name" value="yt"/>
    </bean>

</beans>

3、测试

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //在执行getBean的时候, user已经创建好了 , 通过无参构造
    User user = (User) context.getBean("user");
    //调用对象的方法 .
    user.show();
}

结论:在配置文件加载的时候。其中管理的对象都已经初始化了!

3、Spring配置

3.1、 alias

别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名

<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="user" alias="user2"/>

3.2、 bean

Bean的配置

<!--bean就是java对象,由Spring创建和管理-->
<!--   id bean的唯一标识符,相当于变量名,要唯一,如果没有配置id,name就是默认标识符
       如果配置id,又配置了name,那么name是别名
       name可以设置多个别名,可以用逗号,分号,空格隔开
       如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
       class:bean对象所对应的全限定名=包名+类名
-->
<bean id="userT" class="com.yt.pojo.UserT" name="userT2,userT3">

</bean>

3.3 、import

import

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

<import resource="beans.xml"/>

这样使用的时候就只需要调用总的配置文件就行了

4、依赖注入

4.1、构造器注入

我们在之前的案例已经讲过了

4.2、Set 注入 (重点)

概念

  • 依赖注入(Dependency Injection,D)
    • 依赖 : 指Bean对象的创建依赖于容器 . 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 + '\'' +
                    '}';
        }
    }
    
  2. 真实测试对象

    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;
    }
    
  3. beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="student" class="com.yt.pojo.Student">
            <!--第一种,普通值注入,直接value赋值-->
            <property name="name" value="yt"/>
            <!--Bean注入,ref-->
            <property name="address" ref="address"/>
            <!--数组注入-->
            <property name="books">
                <array>
                    <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="111111222233334444"></entry>
                    <entry key="银行卡" value="1221232355664444000"></entry>
                </map>
            </property>
            <!--set注入-->
            <property name="games">
                <set>
                    <value>LOL</value>
                    <value>CSGO</value>
                    <value>GTA5</value>
                </set>
            </property>
            <!-- null值-->
            <property name="wife">
                <null/>
            </property>
            <!--Properties注入-->
            <property name="info">
                <props>
                    <prop key="学号">2021-06-21</prop>
                    <prop key="性别">男</prop>
                </props>
            </property>
        </bean>
        <bean id="address" class="com.yt.pojo.Address">
            <property name="address" value="地球"/>
        </bean>
    </beans>
    
  4. 测试类

    public class MyTest {
        @Test
        public void test(){
           ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            Student student = (Student) context.getBean("student");
            System.out.println(student.toString());
    
        }
    }
    Student{
        name='yt', 
        address=Address{address='地球'}, 
        books=[西游记, 水浒传, 三国演义], 
        hobbys=[听歌, 敲代码, 看电影], 
        card={
            身份证=111111222233334444, 
            银行卡=1221232355664444000
        }, 
        games=[LOL, CSGO, GTA5], 
        wife='null', 
        info={学号=2021-06-21, 性别=男}
    }
    

4.3、拓展方式注入(C,P)

P命名空间

User.java【注意:这里没有有参构造器!】

public class User {
    private String name;
    private int age;

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

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

P命名空间注入 : 需要在头文件中加入约束文件

导入约束 : xmlns:p="http://www.springframework.org/schema/p"

userbeans.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"       
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--p命名空间注入,可以直接注入属性的值,类似于 property -->
    <bean id="user" class="com.yt.pojo.User" p:name="yt" p:age="18"/>

</beans>

c 命名空间注入 : 需要在头文件中加入约束文件

导入约束 : xmlns:c="http://www.springframework.org/schema/c"

发现问题:爆红了,刚才我们没有写有参构造!

解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!

userbeans.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"       
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--c命名空间注入,通过构造器(有参)注入,类似于 constructor-arg -->
    <bean id="user2" class="com.yt.pojo.User" c:age="18" c:name="yt"/>
</beans>

4.4、Bean的作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .

image-20210721152721532

几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

  1. 单例模式(Spring默认模式)

    当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

    <bean id="user2" class="com.yt.pojo.User" c:age="18" c:name="yt" scope="singleton"/>
    
  2. 原型模式:每次从容器中取出(get)时,都会产生一个新对象。

    当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

    <bean id="user2" class="com.yt.pojo.User" c:age="18" c:name="yt" scope="prototype"/>
    
  3. Request
    当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

     <bean id="loginAction" class="cn.csdn.LoginAction" scope="request"/>
    

    针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

  4. Session
    当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

     <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
    
    

    针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

5、Bean的自动装配

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

在Spring中,有三种装配的方式

  1. 在xml中显示的配置
  2. 在java中显示配置
  3. 隐式的自动装配bean【重点】

Spring的自动装配需要从两个角度来实现,或者说是两个操作:

  • 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  • 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

5.1、测试

1、新建一个项目

2、新建两个实体类,Cat Dog 都有一个叫的方法

public class Cat {
   public void shout() {
       System.out.println("miao~");
  }
}
public class Dog {
   public void shout() {
       System.out.println("wang~");
  }
}

3、新建一个类 People

public class People {
    private Cat cat;
    private Dog dog;
    private String name;

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

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

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

4、编写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"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="cat" class="com.yt.pojo.Cat"/>
    <bean id="dog" class="com.yt.pojo.Dog"/>
    <bean id="people" class="com.yt.pojo.People" p:name="yt" p:cat="cat" p:cat-ref="cat" p:dog="dog" p:dog-ref="dog"/>

</beans>

5、测试

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        People people = (People) context.getBean("people");
        people.getCat().shout();
        people.getDog().shout();
    }
}

结果正常输出,环境OK

5.2、ByName自动装配

autowire byName (按名称自动装配)

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试:

1、修改bean配置,增加一个属性 autowire=“byName”

<!-- byName: 会在容器上下文中查找,和自己对象set方法后面的值对应的 beanId -->
<bean id="people" class="com.yt.pojo.People" p:name="yt" autowire="byName"/>

2、再次测试,结果依旧成功输出!

3、我们将 cat 的bean id修改为 cat222

4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

小结:

当一个bean节点带有 autowire byName的属性时。

将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。

去spring容器中寻找是否有此字符串名称id的对象。

如果有,就取出注入;如果没有,就报空指针异常。

5.3、ByType自动装配

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

NoUniqueBeanDefinitionException
测试:

1、将user的bean配置修改一下 : autowire=“byType”

<!-- byType: 会在容器上下文中查找,和自己对象属性类型相同的bean (匹配class) 全局唯一 -->
    <bean id="people" class="com.yt.pojo.People" p:name="yt" autowire="byType"/>

2、测试,正常输出

3、在注册一个cat 的bean对象!

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

<bean id="user" class="com.kuang.pojo.User" autowire="byType">
   <property name="str" value="qinjiang"/>
</bean>

4、测试,报错:NoUniqueBeanDefinitionException

5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

这就是按照类型自动装配!

小结:

  • byName:需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的一致
  • byType:需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致

5.4、使用注解自动装配

jdk1.5开始支持注解,spring2.5开始全面支持注解。

准备工作:利用注解的方式注入属性。

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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    
</beans>

2、开启属性注解支持!

<context:annotation-config/>

@Autowired

  • @Autowired是按类型自动转配的,不支持id匹配。

  • 需要导入 spring-aop的包!

  • 直接在属性上使用即可!也可以在set方式上使用

  • 使用Autowired我们可以不用编写set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中寻找,切符合名字byname

  • 如果显示的定义了Autowired的required属性为false,就说明这个对象可以为null

  • @Nullable 字段标记了这个注解,说明这个字段可以为空

  • @Autowired
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog222")
    private Dog dog;
    private String name;
    
  • 如果Autowired自动装配的环境比较复杂的时候,有多个对象,自动装配无法通过一个注解【Autowired】完成的时候,我们可以使用@Qualifier(value = “beanid”)去配置Autowired使用,指定一个唯一的Bean注入对象

拓展:@Resource(java注解)

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;

  • 其次再进行默认的byName方式进行装配;

  • 如果以上都不成功,则按byType的方式自动装配。

  • 都不成功,则报异常。

  • @Resource(name = “beanid”)在配置环境复杂的时候,也可以去指定一个唯一的Bean注入对象,等同于@Qualifier(value = “beanid”),不过@Resource使用较少

@Resource与@Autowired区别

  • 都是用来自动装配的。都可以写在字段上,或写在setter方法上。
  • @Autowired 是通过byType的方式实现,而且必须要求这个对象存在,否则报错
  • @Resource默认通过byName方式实现,如果找不到名字,则通过byType方式实现,都找不到,则报错
  • 执行顺序不同:@Autowired通过byType实现,@Resource默认使用byName方式实现

6、使用注解开发

  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:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
        <!--指定要扫描的包,这个报下的注解就会生效-->
        <context:component-scan base-package="com.yt.pojo"/>
        <context:annotation-config/>
    
    </beans>
    
  2. @Component

    • @Component:组件,放在类上,说明这个类被Spring管理了,就是Bean,不用再去xml文件配置!

    • //等同于<bean id="user" class="com.yt.pojo.User"/>
      @Component
      public class User {
          public String name="yt";
      }
      
      
  3. 属性注解@Value

    • 可以放在属性上或者属性的set方法上

    • //等同于:<property name="name" value="yt"/>       
      @Value("yt") 
      public String name;
      
  4. 衍生的注解

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

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

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

  5. 作用域 @Scope

    • @Scope("singleton")
      
  6. 小结 :

    xml与注解

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

    xml与注解最佳实践

    • xml用来管理bean
    • 注解只负责属性的注入
    • 在使用的过程中,只需要注意一个问题,必须让注解生效,就需要开启注解的支持

7、使用java的方式配置Spring

实体类

@Component
public class User {
    @Value("yt")
    private String name;

    public String getName() {
        return name;
    }

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

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

appConfig配置类

//这个也会被Spring容器接管,注册到容器中,因为他本身就是一个@Component
//@Configuration代表这是一个配置类,等同行与之前的beans.xml
//@ComponentScan 指定要扫描的包,这个包下的注解就会生效
//@ComponentScan 等同于<context:component-scan base-package="com.yt.pojo"/>
//@Import 引入别的配置类

@Configuration
@ComponentScan("com.yt.pojo")
public class YtConfig {

    // 注册一个bean,等同于之前的bean标签
    // 方法的名字相当于bean标签中的id
    // 这个方法中返回值相当于bean标签中的class

    @Bean
    public User getUser(){
        //返回要注入到bean的对象 
        return new User();
    }
}

小结:

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

8、代理模式

为什么要学习代理模式,因为AOP的底层机制就是动态代理!

代理模式:

  • 静态代理
  • 动态代理

学习AOP之前,我们先要了解一下代理模式

8.1、静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决 (房源)
  • 真实角色:被代理的角色 (房东)
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作 (寻找房源之类)
  • 客户:访问代理对象的人 (有租房需求的人)

代码实现

Rent.java 即抽象角色

//抽象角色:租房
public interface Rent {
    /**
     * 租房
     */
    public void rent();
}

Host.java 即真实角色

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

Proxy.java 即代理角色

//代理角色:中介
public class Proxy implements Rent{
    private Host host;

    public Proxy() {
    }

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

    @Override
    public void rent() {
        seeHouse();
        heTOng();
        host.rent();
        fare();
    }

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

    public void fare(){
        System.out.println("收中介费");
    }
    public void heTOng(){
        System.out.println("签租凭合同");
    }
}

Client.java 即客户

//客户类,一般客户都会去找代理!
public class Client {
   public static void main(String[] args) {
       //房东要租房
       Host host = new Host();
       //中介帮助房东
       Proxy proxy = new Proxy(host);

       //你去找中介!
       proxy.rent();
  }
}

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .

缺点:

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

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理

8.2、 静态代理再理解

练习步骤:

1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!

//抽象角色:增删改查业务
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}

2、我们需要一个真实对象来完成这些增删改查操作

//真实对象,完成增删改查操作的人
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("查询了一个用户");
    }
}

3、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4、设置一个代理类来处理日志!代理角色

//代理角色,在这里面增加日志的实现
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("执行了"+msg+"方法");
    }

}

5、测试访问类:

public class Client {
    public static void main(String[] args){
        //真实业务
        UserServiceImpl userService = new UserServiceImpl();
        //代理类
        UserServiceProxy userServiceProxy = new UserServiceProxy();
        //使用代理类实现日志功能!
        userServiceProxy.setUserService(userService);
        userServiceProxy.add();
    }
}

OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

聊聊AOP:纵向开发,横向开发

8.3、动态代理模式

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

JDK的动态代理需要了解两个类

核心 : InvocationHandlerProxy , 打开JDK帮助文档看看

【InvocationHandler:调用处理程序】

image-20210721234318379

image-20210721234452641

【Proxy : 代理】

image-20210721234545016

代码实现

抽象角色和真实角色和之前的一样!

//抽象角色:租房
public interface Rent {
    /**
     * 租房
     */
    public void rent();
}

Host.java 即真实角色

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

ProxyInvocationHandler.java 即代理角色

public class ProxyInvocationHandler implements InvocationHandler {

    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    // proxy : 代理类
    //method : 代理类的调用处理程序的方法对象.
    // 处理代理实例上的方法调用并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //核心:本质利用反射实现!
        Object result = method.invoke(rent, args);

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

    public void fare(){
        System.out.println("收中介费");
    }
    public void heTOng(){
        System.out.println("签租凭合同");
    }
}

Client.java

public class Client {
    public static void main(String[] args){
        //真实角色
        Host host = new Host();
        //代理实例的调用处理程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //将真实角色放置进去!
        pih.setRent(host);
        //动态生成对应的代理类!
        Rent proxy = (Rent)pih.getProxy();
        proxy.rent();
    }
}

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

8.4、深化理解

我们来使用动态代理实现代理我们后面写的UserService!

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

public class ProxyInvocationHandler implements InvocationHandler {

    private Object target;

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

    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    // proxy : 代理类
    //method : 代理类的调用处理程序的方法对象.
    // 处理代理实例上的方法调用并返回结果
    @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 methodName) {
        System.out.println("执行了" + methodName + "方法");
    }
}

测试

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

测试,增删改查,查看结果!

动态代理的好处

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!

9、AOP

9.1、什么是aop

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

image-20210722100515051

9.2、aop在spring中的作用

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

以下名词需要了解下:

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

image-20210722100816238

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

image-20210722101017565

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

9.3、 使用spring实现aop

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

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
方式一:使用Spring的API接口

首先编写我们的业务接口和实现类

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

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("查询了一个用户");
    }
}

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

public class Log implements MethodBeforeAdvice {
    //method : 要执行的目标对象的方法
    //objects : 被调用的方法的参数
    //target : 目标对象

    @Override
    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了!");
    }
}

public class AfterLog implements AfterReturningAdvice {
    //returnValue 返回值
    //method被调用的方法
    //args 被调用的方法的对象的参数
    //target 被调用的目标对象


    @Override
    public void afterReturning( Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + target.getClass().getName()
                +"的"+method.getName()+"方法,"
                +"返回值:"+returnValue);
    }
}

最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

<?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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="userService" class="com.yt.service.UserServiceImpl"/>
    <bean id="log" class="com.yt.log.Log"/>
    <bean id="afterLog" class="com.yt.log.AfterLog"/>
    <!--在头文件导入Aop约束
    方式一:使用原生的Spring API接口-->
    <aop:config>
        <!--切入点  expression:表达式 expression(要执行的位置):-->
        <aop:pointcut id="pointcut" expression="execution(* com.yt.service.UserServiceImpl.*())"/>
        <!--执行环绕增加-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

定义切点:

expression="execution(* com.iandf.service.UserServiceImpl.*(..))

对这个表达式分析:

  • 表示返回值的方法

  • com.iandf.service.UserServiceImpl.* 表示UserServiceImpl类中的任意方法

  • (…)表示任意参数

测试

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

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

方式二:使用自定义类实现AOP【切面定义】

第一步 : 写我们自己的一个切入类

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

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

去spring中配置

<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.yt.diy.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
    <!--第二种方式:使用AOP的标签实现-->
    <aop:aspect ref="diy">
        <aop:pointcut id="diyPointcut" expression="execution(* com.yt.service.UserServiceImpl.*())"/>
        <aop:before method="before" pointcut-ref="diyPointcut"/>
        <aop:after method="after" pointcut-ref="diyPointcut"/>
    </aop:aspect>
</aop:config>

测试:

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //动态代理代理的是接口
    UserService userService = (UserService) context.getBean("userService");
    userService.add();
}
方式三:使用注解实现AOP

第一步:编写一个注解实现的增强类

@Aspect
public class AnnotationPointCut {
    @Before("execution(* com.yt.service.UserServiceImpl.*())")
    public void before() {
        System.out.println("====方法执行前====");
    }
    @After("execution(* com.yt.service.UserServiceImpl.*())")
    public void after() {
        System.out.println("====方法执行后====");
    }
    //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    
    @Around("execution(* com.yt.service.UserServiceImpl.*())")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.yt.diy.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>

aop:aspectj-autoproxy:说明

  • 通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
  • <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表- 示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

10、整合MyBatis

步骤:

1、导入相关的jar包

  • junit
  • mybatis
  • mysql数据库
  • spring相关的
  • aop织入
  • mybatis-spring 【new】

2、编写配置文件

3、测试

10.1、回忆MyBatis

  1. 编写实体类

    @Data
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
    
  2. 编写核心配置文件

    <?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>
    
        <properties resource="db.properties"/>
        <typeAliases>
            <package name="com.yt.pojo"/>
        </typeAliases>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--注册mapper-->
            <mapper class="com.yt.dao.UserMapper"/>
        </mappers>
    
    </configuration>
    
  3. 编写接口

    public interface UserMapper {
        List<User> getUserList();
    }
    
    
  4. 编写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">
    <mapper namespace="com.yt.dao.UserMapper">
        <select id="getUserList" resultType="user">
            select * from mybatis.user
        </select>
    </mapper>
    
  5. 测试

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList) {
            System.out.println("user = " + user);
        }
    }
    

10.2、MyBatis-Spring

10.2.1、 使用方式一 基于SqlSessionTemplate

1、编写实体类User

2、在spring-dao.xml配置文件中配置数据源 这里使用的数据源是spring-jdbc依赖下的DriverManagerDataSource。也可以使用dbcp c3po druid

<!--DataSource : 使用Spring的数据源替换MyBatis的配置
我们这里使用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?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>

3、编写UserMapper和UserMapper.xml 和原生开发一样

4、在spring-dao.xml配置文件中创建SqlSessionFactory

<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!--绑定mybatis配置文件-->
    <property name="configLocation" value="mybatis-config.xml"/>
    <property name="mapperLocations" value="com/yt/dao/*.xml"/>
</bean>

5、在spring-dao.xml配置文件中创建SqlSessionTemplate

<!--SqlSessionTemplate:就是我们使用的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!--只能使用构造器注入-->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

6、编写UserMapper实现类

public class UserMapperImpl implements UserMapper{
    //原来我们所有的操作,都是用sqlSession来执行,现在是SqlSessionTemplate

    private SqlSessionTemplate sqlSession;

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

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


7、将刚刚写好的spring-dao.xml和UserMapper实现类整合到项目的配置文件中

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"
       xmlns:aop="http://www.springframework.org/schema/aop"
  	   xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">


    <import resource="spring-dao.xml"/>
    <bean id="userMapper" class="com.yt.dao.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
</beans>

8、测试

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    List<User> userList = userMapper.getUserList();
    for (User user : userList) {
        System.out.println("user = " + user);
    }
}

10.2.2 、spring整合mybatis开发和原生mybatis开发的区别

  • 在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
  • 在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。而在spring中,使用sqlSessionFactory来创建sqlSessionTemplate,你可以使用它来执行映射了的语句。
  • SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
  • spring需要提供一个UserMapper接口的实现类,实现类组合了一个SqlSessionTemplate对象,有这个对象来对事务进行统一的管理

10.2.3、 使用方式二 基于SqlSessionDaoSupport

步骤:

1、编写实体类User

2、在spring-dao.xml配置文件中配置数据源

3、编写UserMapper和UserMapper.xml 和原生开发一样

4、在spring-dao.xml配置文件中创建SqlSessionFactory

5、编写UserMapper实现类并继承SqlSessionDaoSupport 不用编写SqlSessionTemplate

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> getUserList() {

        return getSqlSession().getMapper(UserMapper.class).getUserList();
    }
}

6、在applicationContext.xml中注册UserMapperImpl2 的bean

<bean id="userMapper2" class="com.yt.dao.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

7、测试方法

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
    List<User> userList = userMapper.getUserList();
    for (User user : userList) {
        System.out.println("user = " + user);
    }
}

11、声明式事务

11.1、回顾事务

  • 要么都成功,要么都失败
  • 事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎
  • 确保完整性和一致性

11.2、Spring中的事务管理

事务管理分类:

  • 声明式事务:使用aop织入事务,对事务进行管理
  • 编程式事务:在代码中显示的执行事务

声明式事务:AOP【推荐】

使用aop织入事务

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd

1、配置声明式事务

<!--    配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

2、配置事务通知 需要配置tx命名空间

<!--    结合AOP实现事务的织入-->
<!--    配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--        给那些方法配置事物-->
    <!--        配置事务的传播特性: new propagation-->
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

3、配置切入点

<!--    配置事物切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.yt.dao.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

编程式事务:需要在代码中,进行事物的管理 【不建议使用】

为什么需要事务?

  • 如果不配置事务,会出现数据提交不一致的情况
  • 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务
  • 事务在项目的开发中,十分重要,涉及到数据的一致性和完整性的问题,不容马虎
posted @ 2021-07-23 00:25  拒绝平庸*  阅读(62)  评论(0编辑  收藏  举报