Spring

Spring

前言

以下内容多为狂神Spring课程上课笔记,一起“食用”味道更佳哟_

以下实例的所有代码会上传至本人github,欢迎访问!

一、简介

1.1 什么是Spring

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

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

  2. Spring是一个开源的轻量级的框架,是非入侵式的。

  3. Spring的核心主要有两部分:

    • AOP:面向切面编程
    • IoC:控制反转(就是实例化不是new出来,而是交给Spring配置创建)

    概括:Spring是一个轻量级的控制反转和面向切面的框架(容器)

1.2 Spring组成

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

在这里插入图片描述

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • Core核心容器:核心容器提供 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 异常层次结构,JDBC封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的POJO都适用。
  • 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。

二、IoC(Inversion of Control,控制反转)

2.1 IoC基础

2.1.1 分析实现

1、写一个UserDao接口

package com.fy.dao;

public interface UserDao {
    void getUser();
}

2、编写接口的实现类UserDao

package com.fy.dao;

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

3、编写UserService接口

package com.fy.service;

public interface UserService {
    void getUser();
}

4、写Userservice的实现类UserServiceImpl

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

以上1~5是我们原来的方式步骤 , 如果我们现在在Userdao的实现类中增加一个UserDaoMySqlImpl

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

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

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

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

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

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

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

那我们如何去解决呢 ?
我们可以在需要用到它的地方 , 不去实现它 , 而是留出一个接口 , 利用set 方式实现 。

public class UserServiceImpl implements UserService {
   private UserDao userDao;
   //利用set进行动态实现值的注入
   public void setUserDao(UserDao userDao) {
       this.userDao = userDao;
  }

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

现在我们在测试类里 , 进行测试 ;(有了set方法就可以在调用的时候由用户选择调用的接口

@Test
public void test(){
   UserServiceImpl service = new UserServiceImpl();
   service.setUserDao( new UserDaoMySqlImpl() );
   service.getUser();
   //那我们现在又想用Oracle去实现呢
   service.setUserDao( new UserDaoOracleImpl() );
   service.getUser();
}

思考:

以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 。程序不用去管怎么创建,怎么实现了 , 它只负责提供一个接口 。

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

2.1.2 本质

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

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

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

在这里插入图片描述

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

2.2 实例

2.2.1 导入jar包

注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项

<dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
2.2.2 编写代码

1、编写一个Hello实体类

package com.fy.pojo;

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

2、编写spring配置文件,命名为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
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--使用Spring来创建对象,在Spring这些都称为bean
    类型 变量名=new 类型();

    bean=对象 new Hello();
    id=变量名
    class=new 的对象
    property相当于给对象中属性设置一个值

    -->
    <!--bean就是java对象 , 由Spring创建和管理-->
    <bean id="hello" class="com.fy.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>

</beans>

3、测试Spring框架是否搭好


public class MyTest {
    public static void main(String[] args) {
        //解析beans.xml文件 , 生成管理相应的Bean对象
        //获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //getBean : 参数即为spring配置文件中bean的id .
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

思考:

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

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : **程序本身不创建对象 , 而变成被动的接收对象 **

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

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

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

2.2.3 使用Spring修改2.2.1中代码

1、新增一个Spring配置文件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">

    <bean id="MysqlImpl" class="com.fy.dao.UserDaoMysqlImpl">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="OracleImpl" class="com.fy.dao.UserOracleImpl">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->
     <!--
        ref:引用Spring容器中创建好的对象
        value:具体的值,基本数据类型
        -->
    <bean id="UserServiceImpl" class="com.fy.service.UserServiceImpl">
        <property name="userDao" ref="MysqlImpl"/>
        <!-- collaborators and configuration for this bean go here -->
    </bean>

</beans>

2、测试

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //容器在手,天下我有,需要什么,get什么
   UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
    //这里相当于将原来的Service层也IoC了,不需要再在代码中写出调用哪个接口,只需要在配置文件中指明调用的接口即可。

   serviceImpl.getUser();
    
    //原来的步骤
    //UserService userService = new UserServiceImpl();
    //userService.setUserDao(new UserDaoMysqlImpl());//原先需要在代码中调用特定的方法
	//userService.getUser();
}

于此 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改

所谓的IoC,即为 : 对象由Spring 来创建 , 管理 , 装配 !

2.3 Ioc创建对象的方式

2.3.1 无参构造方法

1、User.java

public class User {

   private String name;

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

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

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

}

2、beans1.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.fy.pojo.User">
       <property name="name" value="fy"/>
   </bean>

</beans>

3、测试

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

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

2.3.2 有参构造方法

1、UserTwo . java

package com.fy.pojo;

public class UserTwo {
    private String name;

    public UserTwo(String name){
        this.name=name;
        System.out.println("User2的有参构造!");
    }

    public String getName() {
        return name;
    }

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

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

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

<!-- 第一种根据index参数下标设置 -->
<bean id="userT" class="com.fy.pojo.UserT">
   <!-- index指构造方法 , 下标从0开始 --><?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">

    <!--
     id:bean的唯一标识符,也就相当于之前学的对象名
     class:bean对象所对应的全限定名:包名+类名
    -->
    <bean id="user" class="com.fy.pojo.User">
        <!-- collaborators and configuration for this bean go here -->
        <!--若存在无参构造方式
        <property name="name" value="FY"/>
        -->

        <!--有参构造方式的写法-->
        <!--方式一:下标赋值
        <constructor-arg index="0" value="FY"/>
        -->
        <!--方式二:类型赋值,用于某个参数的类型唯一时使用,适用面小,不建议使用
        <constructor-arg type="java.lang.String" value="FY"/>
        -->
        <!--方式三:参数名赋值-->
        <constructor-arg name="name" value="FY"/>

    </bean>
    <!--<constructor-arg name="name" value="FYER"/>-->
    <bean id="userTwo" class="com.fy.pojo.UserTwo" name="user2">
        <constructor-arg name="name" value="FYER"/>

    </bean>
    
</beans>
   <constructor-arg index="0" value="fy2"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="userT" class="com.fy.pojo.UserT">
   <!-- name指参数名 -->
   <constructor-arg name="name" value="fy2"/>
</bean>
<!-- 第三种根据参数类型设置(不推荐使用) -->
<bean id="userT" class="com.fy.pojo.UserT">
   <constructor-arg type="java.lang.String" value="fy2"/>
</bean>

3、写一个总的配置文件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="beans1.xml"/>
    <import resource="beans2.xml"/>
    

</beans>

4、测试

@Test
public void testT(){
   ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
   UserTwo user = (UserTwo) context.getBean("userTwo");
   user.show();
}

三、DI(Dependency Injection,依赖注入)

  • 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源
  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配

3.1 构造器注入

方式如2.3所示

3.2 set方式注入

要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is

编写pojo类

package com.fy.pojo;

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 + '\'' +
                '}';
    }
}
package com.fy.pojo;

import java.util.*;

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;

    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[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    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 String getWife() {
        return wife;
    }

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

    public Properties getInfo() {
        return info;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address=" + address +
                ", books=" + Arrays.toString(books) +
                ", hobbys=" + hobbys +
                ", card=" + card +
                ", games=" + games +
                ", wife='" + wife + '\'' +
                ", info=" + info +
                '}';
    }
}
3.2.1 普通值注入
 <!--第一种:普通值注入,value-->

<bean id="student" class="com.fy.pojo.Student">
   
    <property name="name" value="FY"/>
</bean>
3.2.2 Bean注入

注意点:这里的值是一个引用,ref

<!--第二种:Bean注入,ref-->
<bean id="address" class="com.fy.pojo.Address">
    <property name="address" value="SWU"/>
</bean>

<bean id="student" class="com.fy.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
 </bean>
3.2.3、数组注入
 <!--第三种:数组注入 array-->
 <bean id="student" class="com.fy.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
     <property name="books">
         <array>
             <value>西游记</value>
             <value>红楼梦</value>
             <value>水浒传</value>
         </array>
     </property>
 </bean>
3.2.4 List注入
<!--第四种:List注入,list-->
<bean id="student" class="com.fy.pojo.Student">
 <property name="hobbys">
     <list>
         <value>听歌</value>
         <value>看电影</value>
         <value>爬山</value>
     </list>
 </property>
</bean>
3.2.5 Map注入
<!--第五种:Map注入,map-->
<bean id="student" class="com.fy.pojo.Student">
 <property name="card">
        <map>
            <entry key="身份证" value="123456789987654321"/>
            <entry key="银行卡" value="123456789987654321"/>
        </map>
  </property>
</bean>
3.2.6 set注入
<!--第六种:set注入,set-->
<bean id="student" class="com.fy.pojo.Student">
    <property name="games">
        <set>
          <value>LOL</value>
           <value>CS</value>
        </set>
    </property>
</bean>
3.2.7 null注入
<!--第七种:null注入-->
<bean id="student" class="com.fy.pojo.Student">
    <property name="wife">
        <null/>
    </property>
</bean>
3.2.8 properties注入
<!--第八种:properties注入-->
<bean id="student" class="com.fy.pojo.Student">
    <property name="info">
        <props>
            <prop key="学号">123</prop>
            <prop key="email">123@123.com</prop>
        </props>
    </property>
</bean>

3.3 P命名和C命名注入

编写pojo

package com.fy.pojo;

public class User {
    private String name;
    private int age;
    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = 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 +
                '}';
    }
    ```
    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = 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 +
                '}';
    }
}

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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--p命令空间注入,可以直接注入属性的值:property-->
    <bean id="user" class="com.fy.pojo.User" p:name="FY" p:age="1"/>

    <!--c命令空间注入,需要在头文件中加入约束文件,可以通过构造器注入:construct-args-->
    <!--singleton 单例模式:Spring默认机制,只有一个实例-->
    <!--prototype 原型模式:每次从容器中get的时候都会产生一个新对象-->
    <bean id="user2" class="com.fy.pojo.User" c:name="FY" c:age="2" scope="prototype" />

四、Bean

4.1 Bean的作用域

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

在这里插入图片描述

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

4.1.1 Singleton

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

 <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

测试:

 @Test
 public void test03(){
     ApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml");
     User user = (User) context.getBean("user");
     User user2 = (User) context.getBean("user");
     System.out.println(user==user2);
 }
4.1.2 Prototype

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

 <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
  或者
 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
4.1.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.1.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也会被废弃掉。

4.2 Bean的自动装配

4.2.1 自动装配说明
  • 自动装配是使用Spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean有三种装配机制,分别是:

  1. 在xml中显式配置;
  2. 在java中显式配置;
  3. 隐式的bean发现机制和自动装配。

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

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

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

4.2.2 实例

1、编写实体类

package com.fy.pojo;

public class Cat {
    public void shout(){
        System.out.println("miao~");
    }
}
package com.fy.pojo;

public class Dog {
    public void shout(){
        System.out.println("wang~");
    }
}
package com.fy.pojo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.annotation.Resource;

public class Person {
    //如果显示的定义了Autowired的required属性为false,说明这个对象值可以为null,否则不可以为空
    //@Autowired(required = false)
    @Resource
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog2")
    private Dog dog;
    private String name;

    public Person() {
    }

    public Person(Cat cat, Dog dog, String name) {
        this.cat = cat;
        this.dog = dog;
        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;
    }

    public String getName() {
        return name;
    }

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

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

2、编写Spring的配置文件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
       http://www.springframework.org/schema/beans/spring-beans.xsd">

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

   <bean id="user" class="com.fy.pojo.User">
       <property name="cat" ref="cat"/>
       <property name="dog" ref="dog"/>
       <property name="name" value="fy"/>
   </bean>
</beans>

3、测试

public class MyTest {
   @Test
   public void testMethodAutowire() {
       ApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml");
       User user = (User) context.getBean("user");
       user.getCat().shout();
       user.getDog().shout();
  }
}
4.2.3 autowire–byName

autowire byName (按名称自动装配)

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

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

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

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

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

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

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

小结:

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

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去Spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。
4.2.4 autowire–byType

autowire byType (按类型自动装配)

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

NoUniqueBeanDefinitionException

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

2、测试,正常输出

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

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

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

4、测试,报错:NoUniqueBeanDefinitionException

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

这就是按照类型自动装配

4.2.4 使用注解

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

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

1、在spring配置文件中引入context文件头

xmlns:context="http://www.springframework.org/schema/context"

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

2、开启属性注解支持!

<context:annotation-config/>

@Autowired

  • @Autowired是按类型自动转配的,不支持id匹配。
  • 需要导入 spring-aop的包!

测试:

1、将User类中的set方法去掉,使用@Autowired注解

public class User {
   @Autowired
   private Cat cat;
   @Autowired
   private Dog dog;
   private String str;

   public Cat getCat() {
       return cat;
  }
   public Dog getDog() {
       return dog;
  }
   public String getStr() {
       return str;
  }
}

2、此时配置文件内容

<?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.fy.pojo.Cat"/>
    <bean id="dog" class="com.fy.pojo.Dog"/>
    <bean id="dog2" class="com.fy.pojo.Dog"/>
    <!--
    byName:会自动在容器上下文中查找和自己对象set方法后面的值对应的bean id
           所以需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致
    byType:会自动在容器上下文中查找和自己对象属性类型相同的bean,
           所以需保证所有bean的类型(class)唯一,并且这个bean需要和自动注入的属性的类型一致

    <bean id="person" class="com.fy.pojo.Person" autowire="byName">
        <property name="name" value="FY"/>
    </bean>
    -->
    <bean id="person" class="com.fy.pojo.Person"/>

</beans>

3、测试,成功输出结果!

注:

@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
@Qualifier
  • @Autowired是根据类型自动装配的,加上**@Qualifier则可以根据byName的方式自动装配**
  • @Qualifier不能单独使用。

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

<bean id="dog1" class="com.fy.pojo.Dog"/>
<bean id="dog2" class="com.fy.pojo.Dog"/>
<bean id="cat1" class="com.fy.pojo.Cat"/>
<bean id="cat2" class="com.fy.pojo.Cat"/>

2、没有加Qualifier测试,直接报错

3、在属性上添加Qualifier注解

@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;

4、测试,成功输出!

@Resource
  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配
  • 都不成功,则报异常。

1、实体类:

public class User {
   //如果允许对象为null,设置required = false,默认为true
   @Resource(name = "cat2")
   private Cat cat;
   @Resource
   private Dog dog;
   private String str;
}

2、applicationContext.xml

<bean id="dog" class="com.fy.pojo.Dog"/>
<bean id="cat1" class="com.fy.pojo.Cat"/>
<bean id="cat2" class="com.fy.pojo.Cat"/>

<bean id="user" class="com.kuang.pojo.User"/>

3、测试:结果OK

4、配置文件2:applicationContext.xml , 删掉cat2

<bean id="dog" class="com.fy.pojo.Dog"/>
<bean id="cat1" class="com.fy.pojo.Cat"/>

5、实体类上只保留注解

@Resource
private Cat cat;
@Resource
private Dog dog;

结果:OK

结论:先进行byName查找,失败;再进行byType查找,成功。

4.2.5 小结

@Autowired与@Resource异同:

1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

2、@Autowired默认按类型装配(属于Spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

4、它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

五、注解开发

在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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

5.1 Bean的实现

我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!

1、配置扫描哪些包下的注解

<!--指定注解扫描包-->
<context:component-scan base-package="com.fy.pojo"/>

2、在指定包下编写类,增加注解

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
   public String name = "fy";
}

3、测试

@Test
public void test(){
   ApplicationContext applicationContext =
       new ClassPathXmlApplicationContext("applicationContext.xml");
   User user = (User) applicationContext.getBean("user");
   System.out.println(user.name);
}

5.2 属性注入

使用注解注入属性

1、可以不用提供set方法,直接在直接名上添加@value(“值”)

@Component
//Component组件,等价于<bean id="user" class="com.fy.pojo.User"/>
public class User {
    @Value("FY")
    //value,相当于<property name="name",value="FY"/>
    public String name;
}

2、如果提供了set方法,在set方法上添加@value(“值”);

@Component("user")
public class User {

   public String name;

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

5.3 衍生注解

以下这些注解,就是替代了在配置文件当中配置步骤而已,更加的方便快捷。

@Component三个衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层

写上这些注解,就相当于将这个类交给Spring管理装配了

5.4 自动装配注解

在Bean的自动装配已经讲过了

5.5 作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
   @Value("fy")
   public String name;
}

5.6 小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>  

作用:

  • 进行注解驱动注册,从而使注解生效
  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
  • 如果不扫描包,就需要手动配置bean
  • 如果不加注解驱动,则注入的值为null!

六、使用Java的方式配置Spring

完全不使用xml配置

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。

6.1 编写一个实体类

@Component  //将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
   public String name = "dog";
}

6.2 编写一个MyConfig配置类

@Configuration  //代表这是一个配置类
public class MyConfig {

   @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
   public Dog dog(){
       return new Dog();
  }

}

6.3 测试

@Test
public void test2(){
   ApplicationContext applicationContext =
           new AnnotationConfigApplicationContext(MyConfig.class);
   Dog dog = (Dog) applicationContext.getBean("dog");
   System.out.println(dog.name);
}

导入其他配置如何做呢?

1、我们再编写一个配置类!

@Configuration  //代表这是一个配置类
public class MyConfig2 {
}

2、在之前的配置类中我们来选择导入这个配置类

@Configuration
@Import(MyConfig2.class)  //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {

   @Bean
   public Dog dog(){
       return new Dog();
  }

}

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!

七、AOP(Aspect Oriented Programming,面向切面编程)

https://baijiahao.baidu.com/s?id=1613310315603029991&wfr=spider&for=pc

AOP是面向切面的编程,其编程思想是把散布于不同业务但功能相同的代码从业务逻辑中抽取出来,封装成独立的模块,这些独立的模块被称为切面,切面的具体功能方法被称为关注点。在业务逻辑执行过程中,AOP会把分离出来的切面和关注点动态切入到业务流程中,这样做的好处是提高了功能代码的重用性和可维护性

在这里插入图片描述

7.1 案例引入

Spring框架的AOP机制可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。

例如,在一个业务系统中,用户登录是基础功能,凡是涉及到用户的业务流程都要求用户进行系统登录。如果把用户登录功能代码写入到每个业务流程中,会造成代码冗余,维护也非常麻烦,当需要修改用户登录功能时,就需要修改每个业务流程的用户登录代码,这种处理方式显然是不可取的。比较好的做法是把用户登录功能抽取出来,形成独立的模块,当业务流程需要用户登录时,系统自动把登录功能切入到业务流程中。下图是用户登录功能切入到业务流程示意图。

在这里插入图片描述

7.2 相关知识点

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

以下名词需要了解下:

● Aspect

表示切面。切入业务流程的一个独立模块。一个应用程序可以拥有任意数量的切面。

● Join point

表示连接点。也就是业务流程在运行过程中需要插入切面的具体位置。

● Advice

表示通知。是切面的具体实现方法。可分为前置通知(Before)后置通知(AfterReturning)异常通知(AfterThrowing)、**最终通知(After)环绕通知(Around)**五种。实现方法具体属于哪类通知,是在配置文件和注解中指定的。

在这里插入图片描述

● Pointcut

表示切入点。用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上。

● Target

表示目标对象。被一个或者多个切面所通知的对象。

● Proxy

表示代理对象。将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象为目标对象的业务逻辑功能加上被切入的切面所形成的对象。

● Weaving

表示切入,也称为织入。将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期。

7.3 使用Spring实现AOP

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

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
 </dependency>
7.3.1 Spring API方式

1、编写实现类和业务接口

package com.fy.service;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
package com.fy.service;

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

    }
}

2、编写增强类 , 我们编写两个 , 一个前置增强 一个后置增强

public class Log implements MethodBeforeAdvice {

   //method : 要执行的目标对象的方法
   //objects : 被调用的方法的参数
   //Object : 目标对象
   @Override
   public void before(Method method, Object[] objects, Object o) throws Throwable {
       System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
  }
}
public class AfterLog implements AfterReturningAdvice {
   //returnValue 返回值
   //method被调用的方法
   //args 被调用的方法的对象的参数
   //target 被调用的目标对象
   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
       System.out.println("执行了" + target.getClass().getName()
       +"的"+method.getName()+"方法,"
       +"返回值:"+returnValue);
  }
}

3、在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-->
   <bean id="userService" class="com.fy.service.UserServiceImpl"/>
   <bean id="log" class="com.kuang.log.Log"/>
   <bean id="afterLog" class="com.fy.log.AfterLog"/>

   <!--aop的配置-->
   <aop:config>
       <!--切入点 expression:表达式匹配要执行的方法-->
       <aop:pointcut id="pointcut" expression="execution(* com.fy.service.UserServiceImpl.*(..))"/>
       <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
       <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
       <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
   </aop:config>

</beans>

4、测试

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.search();
  }
}

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

7.3.2 自定义类实现AOP

目标业务类不变依旧是userServiceImpl

1、写我们自己的一个切入类

public class DiyPointcut {

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

2、Spring配置文件中进行配置

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

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

3、测试

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.add();
  }
}
7.3.3 使用注解实现AOP

1、编写一个注解实现的增强类

package com.fy.diy;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.fy.service.UserServiceImpl.*(..))")
    public void before(){
            System.out.println("=======方法执行前======");
    }
    @After("execution(* com.fy.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("==========方法执行后==========");
    }

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

        //获得签名
        Signature signature = jp.getSignature();
        System.out.println("signature"+signature);

        //执行方法
        Object proceed = jp.proceed();

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

        System.out.println(proceed);
    }

}

2、在Spring配置文件中,注册bean,并增加支持注解的配置

<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.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动态代理。

八、整合MyBatis

8.1 导入相关jar包

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

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

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

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

<dependency>
    <groupId>org.myBatis</groupId>
    <artifactId>myBatis</artifactId>
    <version>3.5.4</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.3</version>
</dependency>

 <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

<--!配置Maven静态资源过滤问题!-->

<build>
   <resources>
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>true</filtering>
       </resource>
   </resources>
</build>

8.2 实例

1、编写实体类

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

2、编写MyBatis的配置文件

<?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核心配置文件-->
<configuration>

    <!--引入外部配置文件-->
    <properties resource="db.properties"/>


    <settings>
        <!--配日志-SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--是否开启驼峰命名自动映射,
        即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <!--起别名-->
    <!--方式一,实体类少的时候使用,一个个起别名-->
    <typeAliases>
        <typeAlias type="com.fy.pojo.User" alias="User"/>

    </typeAliases>
    <!--方式二,
    <typeAliases>
        <package name="com.fy.pojo.User" />
    </typeAliases>
    实体类多的时候使用,但这一种不能DIY,如果非要改,需要在实体类上加注解-->
    
</configuration>

3、UserDao接口编写

package com.fy.dao;

import com.fy.pojo.User;

import java.util.List;

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

}

4、接口对应的Mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.fy.dao.UserMapper">
    <select id="selectuser" resultType="User">
        select * from mybatis.user;
    </select>

</mapper>

5、测试

@Test
public void selectUser() throws IOException {

   String resource = "mybatis-config.xml";
   InputStream inputStream = Resources.getResourceAsStream(resource);
   SqlSessionFactory sqlSessionFactory = new        SqlSessionFactoryBuilder().build(inputStream);
   SqlSession sqlSession = sqlSessionFactory.openSession();

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

   List<User> userList = mapper.selectUser();
   for (User user: userList){
       System.out.println(user);
  }

   sqlSession.close();
}

8.3 mybatis-spring

引入Spring之前需要了解mybatis-spring包中的一些重要类;

http://www.mybatis.org/spring/zh/index.html

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

1、知识基础

在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要

MyBatis-Spring 需要以下版本:

MyBatis-SpringMyBatisSpring 框架Spring BatchJava
2.03.5+5.0+4.0+Java 8+
1.33.4+3.2.2+2.1+Java 6+

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

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

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource" />
</bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。

需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
 <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

public class UserDaoImpl implements UserDao {

 private SqlSession sqlSession;

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

 public User getUser(String userId) {
   return sqlSession.getMapper...;
}
}

按下面这样,注入 SqlSessionTemplate:

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
 <property name="sqlSession" ref="sqlSession" />
</bean>

8.4 整合一

1、引入Spring配置文件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
       http://www.springframework.org/schema/beans/spring-beans.xsd">

2、配置数据源替换MyBaits的数据源

<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<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=false&amp;useUnicode=true&amp;characterEncoding=utf8"/>
   <property name="username" value="root"/>
   <property name="password" value="123456"/>
</bean>

3、配置SqlSessionFactory,关联MyBatis

<!--配置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/fy/dao/*.xml"/>
</bean>

4、注册sqlSessionTemplate,关联sqlSessionFactory;

<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
   <!--利用构造器注入-->
   <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

5、增加Dao接口的实现类;私有化sqlSessionTemplate

public class UserDaoImpl implements UserMapper {

   //sqlSession不用我们自己创建了,Spring来管理
   private SqlSessionTemplate sqlSession;

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

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

6、注册bean实现

<bean id="userDao" class="com.fy.dao.UserDaoImpl">
   <property name="sqlSession" ref="sqlSession"/>
</bean>

7、测试

   @Test
   public void test2(){
       ApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml");
       UserMapper mapper = (UserMapper) context.getBean("userDao");
       List<User> user = mapper.selectUser();
       System.out.println(user);
  }

结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!

<?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.fy.pojo"/>
   </typeAliases>
</configuration>

8.5 整合二

mybatis-spring1.2.3版以上的才有这个 .

官方文档截图 :

dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看

在这里插入图片描述

1、将我们上面写的UserDaoImpl修改一下

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
   public List<User> selectUser() {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.selectUser();
  }
}

2、修改bean的配置

<bean id="userDao" class="com.fy.dao.UserDaoImpl">
   <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

3、测试

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

总结

整合到Spring以后可以完全不要mybatis的配置文件

九、声明式事务

9.1 事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  1. 原子性(atomicity)

    • 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
  2. 一致性(consistency)

    • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  3. 隔离性(isolation)

    • 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
  4. 持久性(durability)

    • 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

9.2 实例

1、将上面的代码拷贝到一个新项目中

在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;

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

//根据id删除用户
int deleteUser(int id);

2、mapper文件,我们故意把 deletes 写错

<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>

<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>

3、编写接口的实现类

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {

   //增加一些操作
   public List<User> selectUser() {
       User user = new User(4,"小明","123456");
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       mapper.addUser(user);
       mapper.deleteUser(4);
       return mapper.selectUser();
  }

   //新增
   public int addUser(User user) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.addUser(user);
  }
   //删除
   public int deleteUser(int id) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.deleteUser(id);
  }

}

4、测试

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

结果 :

报错:sql异常,delete写错了,但插入成功!

原因:没有进行事务的管理;

我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!

以前我们都需要自己手动管理事务,十分麻烦!

但是Spring给我们提供了事务管理,我们只需要配置即可;

9.3 使用Spring配置事务

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务,注意头文件的约束导入 : tx

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

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

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="dataSource" />
</bean>

配置好事务管理器后我们需要去配置事务的通知

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
       <!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
       <tx:method name="add" propagation="REQUIRED"/>
       <tx:method name="delete" propagation="REQUIRED"/>
       <tx:method name="update" propagation="REQUIRED"/>
       <tx:method name="search*" propagation="REQUIRED"/>
       <tx:method name="get" read-only="true"/>
       <tx:method name="*" propagation="REQUIRED"/>
   </tx:attributes>
</tx:advice>

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

配置AOP

导入aop的头文件!

<!--配置aop织入事务-->
<aop:config>
   <aop:pointcut id="txPointcut" expression="execution(* com.fy.dao.*.*(..))"/>
   <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

进行测试

删掉刚才插入的数据,再次测试,结果不能成功删除和插入了。

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

法中来控制事务的提交和回滚

  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务,注意头文件的约束导入 : tx

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

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

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="dataSource" />
</bean>

配置好事务管理器后我们需要去配置事务的通知

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
       <!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
       <tx:method name="add" propagation="REQUIRED"/>
       <tx:method name="delete" propagation="REQUIRED"/>
       <tx:method name="update" propagation="REQUIRED"/>
       <tx:method name="search*" propagation="REQUIRED"/>
       <tx:method name="get" read-only="true"/>
       <tx:method name="*" propagation="REQUIRED"/>
   </tx:attributes>
</tx:advice>

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

配置AOP

导入aop的头文件!

<!--配置aop织入事务-->
<aop:config>
   <aop:pointcut id="txPointcut" expression="execution(* com.fy.dao.*.*(..))"/>
   <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

进行测试

删掉刚才插入的数据,再次测试,结果不能成功删除和插入了。

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值