Spring5学习笔记

SpringFramework5.x

  • Spring IOC工厂
  • Spring AOP编程
  • 持久层集成
  • 事务处理
  • MVC框架继承
  • 注解编程

2002年Rob Jhonson提出EJB存在的缺陷

EJB(Enterprise Java Bean)缺陷

  1. 运行环境苛刻(需要运行在EJB容器中)
  2. 代码移植性差

总结:EJB是重量级框架

什么是Spring

Spring是一个轻量级JavaEE解决方案,整合了众多优秀的设计模式

  • 轻量级

    • 对于运行环境没有额外要求

      ​ 开源 tomcat resion jetty

      ​ 收费 weblogic websphere

    • 代码移植性高

      ​ 不需要实现额外接口

  • JavaEE的解决方案

    面对每一层都有解决方案

在这里插入图片描述

  • 整合设计模式

    工厂、代理、模板、策略

设计模式

1.广义

面向对象设计中,解决特定问题的经典代码

2.狭义

GOF4人定义的23种设计模式

工厂设计模式

  1. 概念:通过工厂类创建对象

  2. 好处:解耦合

    耦合:代码间的强关联关系,一方的改变会影响另一方

    问题:不利于代码维护

    //简单案例:把接口的实现类硬编码在程序中
    UserServiceImpl userService = new UserServiceImpl();
    

设计简单工厂模式

工厂类

public class BeanFactory {

    public static UserService getUserService(){
        return new UserServiceImpl();
    }

}

测试类

import com.liu.pojo.BeanFactory;
import com.liu.pojo.User;
import com.liu.pojo.UserService;
import com.liu.pojo.UserServiceImpl;
import org.junit.Test;

public class TestSpring {
    
    @Test
    public void test01(){

		//UserServiceImpl userService = new UserServiceImpl();

        //通过工厂类创建对象,解耦合
        UserService userService = BeanFactory.getUserService();

        userService.login("liu","123456");

        User user = new User("liushihao","123456");
        userService.register(user);

    }
}

但是这种工厂类的设计过于简单,仍然引入了具体的UserServiceImpl,仍然存在耦合。

改进(反射+配置文件)

对象的创建方式:

  • 直接调用构造方法创建对象

    UserServiceImpl userService = new UserServiceImpl();

  • 通过反射的形式创建对象 解耦合

    Class clazz = Class.forName(“com.liu.pojo.UserServiceImpl”);

    UserService userService = (UserService)clazz.newInstance();

package com.liu.pojo;

public class BeanFactory {

    public static UserService getUserService(){

        UserService userService = null        ;
        try {
            //通过反射一种方式还是无法彻底解决耦合,还是引入了接口实现类的全限定类名
            Class clazz = Class.forName("com.liu.pojo.UserServiceImpl");
            userService =  (UserService)clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
         catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return userService;
    }

}

通过添加小的配置文件解决Class.forName(“com.liu.pojo.UserServiceImpl”);中的耦合问题

配置文件

#Properties类型的集合来存储Properties文件的内容
#特殊的Map key=String value=String
#Properties [userService = com.liu.pojo.UserServiceImpl]
#Properties.getProperty("userService")就可以获取com.liu.pojo.UserServiceImpl

userService = com.liu.pojo.UserServiceImpl

工厂类

package com.liu.pojo;

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

public class BeanFactory {

    private static Properties env = new Properties();

    //IO是系统级资源,我们应该避免重复性地打开IO,最好在程序启动时一次性读取想要的内容
    static{
        //1.获得输入流
        InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");

        //2.文件的内容封装到Properties集合中 key="userService" value="com.liu.pojo.UserServiceImpl"
        try {
            
            env.load(inputStream);
            inputStream.close();
        
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static UserService getUserService(){

        UserService userService = null        ;
        try {
            Class clazz = Class.forName(env.getProperty("userService"));
            userService =  (UserService)clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
         catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return userService;
    }
}

日后业务需要改动,只需将新的实现类写入配置文件applicationContext.properties中

但是这样的设计还是存在问题,只要有新的实现类出现,就需要在工厂类中添加

public static XXXX getXXXX(){

        XXXX xxxx = null;
        try {
            Class clazz = Class.forName(env.getProperty("xxxx"));
            xxxx =  (XXXX)clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
         catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return xxxx;
    }

这样的话会存在大量冗余代码

通用工厂设计

package com.liu.pojo;

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

public class BeanFactory {

    private static Properties env = new Properties();

    static{
        InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
        try {
            env.load(inputStream);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //key代表小配置文件中的key [userDAO, userService]
    public static Object getBean(String key){

        Object ret = null;
        try {

            Class clazz = Class.forName(env.getProperty(key));
            ret =  clazz.newInstance();

        }catch (Exception e){

        }
        return ret;
    }

}

至此,需要创建对象时只需要调用工厂类的getBean()方法,传入配置文件中对应的字符串即可。

//getBean是静态方法,可以直接类名.方法名进行调用UserService userService = (UserService) BeanFactory.getBean("userService");

通用工厂的使用

  1. 定义类型(类)

  2. 通过配置文件的配置告知给工厂(applicationContext.properties)

    key = value

  3. 通过工厂获得类的对象

    Object ret = BeanFactory.getBean(“key”);

总结

Spring本质:工厂 ApplicationContext(applicationContext.xml)

​ 思路与上述工厂的设计一致,只不过Spring的工厂更为强大。

SpringHelloWorld

环境搭建

Spring的jar包(在pom.xml中设置依赖)

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>

Spring的配置文件

  1. 配置文件的路径:任意位置 没有硬性要求

  2. 配置文件的命名:没有硬性要求 建议applicationContext.xml

    一般配置方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CLfY6wNf-1620665224815)(D:\截图\image-20210321211433462.png)]

Spring的核心API

  • ApplicationContext

    1. 作用:Spring提供的ApplicationContext这个工厂用于对象的创建
    2. 好处:解耦合
    3. ApplicationContext是接口类型
    接口:屏蔽实现的差异两种实现:	非web环境:ClassPathXmlApplicationContext(main junit)	web环境:XmlWebApplicationContext
    
    1. 重量级资源
    ApplicationContext工厂的对象(ClassPathXmlApplicationContext和XmlWebApplicationContext)占用大量内存重量级资源特点:故一个应用只会创建一个工厂对象ApplicationContext工厂一定是线程安全的(多线程并发访问)
    

程序开发

  1. 创建类型

  2. 配置applicationContext.xml

    <bean id="person" class="com.liu.pojo.Person"></bean>
    
  3. 通过工厂类获得对象 ClassPathXmlApplicationContext

    //1.获取Spring的工厂	ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");//2.通过工厂类获得对象	Person person = (Person) context.getBean("person");
    

细节分析

名词解释

Spring工厂创建的对象,叫做bean或者组件(component)

Spring工厂相关的方法

	//通过这种方式获得对象就不需要进行强制类型转换
	Person person = context.getBean("person", Person.class);	

	//当前配置文件中只能有一个<bean class是Person类型
	Person person = context.getBean(Person.class);

	//获取当前配置文件中所有bean标签的id值
	String[] beanDefinitionNames = context.getBeanDefinitionNames();

	//获取配置文件中对应类型的id值
	String[] beanNamesForType = context.getBeanNamesForType(Person.class);

	//判断是否存在对应id的bean  返回值是boolean类型
	//containsBeanDefinition 只能根据id判断,无法判断name
	context.containsBeanDefinition("person");
	
	//containsBean id、name都可以判断
	context.containsBean("person");

配置文件中需要注意的细节

  1. 只配置class属性
<bean class="com.liu.pojo.Person"></bean>

​ a)这种没有id值的配置 Spring会自动生成一个id com.liu.pojo.Person#0

​ b)应用场景:如果这个bean只需要使用一次,那么就可以省略id值

​ 如果这个bean需要使用多次或者被其他bean引用,则需要设置id值

  1. name属性

    作用:用于在Spring配置文件中,为bean对象定义别名(小名)

    与id相同的地方

    ​ 1)context.getBean(“id/name”) --> object

    ​ 2)<bean id="" class="" 等效于 <bean name="" class=""

    与id不同的地方

    ​ 1)别名可以定义多个,id是唯一的

    ​ 2)以前XML的id属性的值有命名要求:以字母开头,后面跟字母、数字、下划线、连字符

    ​ 而name属性的值命名没有要求 如/person

    ​ name属性会应用在特殊命名场景下:/person(spring + struts1)

    但是XML发展到今天已经不存在id命名的限制,现在优先使用id

    ​ 3)工厂的方法中id和name

    	//containsBeanDefinition 只能根据id判断,无法判断name
    	context.containsBeanDefinition("person");
    	
    	//containsBean id、name都可以判断
    	context.containsBean("person");
    

Spring工厂的底层实现原理(简易)

Spring工厂是可以调用private修饰的构造方法创建对象的
在这里插入图片描述

思考

问题:Spring的工厂很强大,那么未来开发中是不是所有对象都交给Spring工厂来创建?

回答:理论上是的。但是有特例—实体对象(entity,会封装数据库表中的数据,这些数据会操作数据库),这类对象交给持久层框架来创建

Spring5.x与日志框架整合

作用

Spring与日志框架整合,日志框架就可以在控制台输出Spring运行过程中的一些重要信息(对象的创建、销毁、进行了什么操作等)。便于了解Spring的运行过程,利于程序的调试

如何整合

早期Spring1、2、3都是与commons-logging.jar进行整合

Spring5.x默认整合的日志框架是logback、log4j2

Spring整合log4j

​ 1.引入log4j相关jar包

​ pom.xml

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
  <!-- 日志门面,引入后可以将默认的logback、log4j2摒弃,使之可以支持log4j-->
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>


<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

​ 2.引入log4j.properties配置文件

#resources文件夹目录下
### 配置根
log4j.rootLogger = debug,console

###日志输出到控制台显示
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern =%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

注入(Injection)

通过Spring工厂及配置文件,为所创建对象的成员变量赋值

为什么需要注入

通过编码的方式为成员变量赋值,存在耦合

@Test
    public void test05(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        Person person = (Person) context.getBean("person");

        //原来赋值的方法,通过代码为成员变量赋值,存在耦合
        person.setId(1);
        person.setName("liushihao");

        System.out.println(person);

    }

如何进行注入

1.在类中为成员变量提供set、get方法

2.配置Spring配置文件

<bean id="person" name="p" class="com.liu.pojo.Person">
        <property name="id" value="10"/>
        <property name="name" value="david"/>
</bean>

注入的好处

解耦合

注入的原理

Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值,这种方式也称为set注入

<bean id="person" name="p" class="com.liu.pojo.Person">
  等效于
Account account = new Account();

<property name="id" value="10"/>
  等效于
account.setId(10);

<property name="name" value="david"/>
  等效于
account.setName("david");

Set注入详解

针对于不同类型的成员变量,在标签中需要嵌套不同标签

Set注入类型

JDK内置类型
  • 8种基本类型(byte short int long float double char boolean)+String

    在property标签中嵌套value

    <property name="id">
    	<value>10</value>
    </property>
    
    <property name="name">
    	<value>liush</value>
    </property>
    
  • 数组类型

    			<property name="emails">
                <list>
                    <value>123456@163.com</value>
                    <value>abcdef@163.com</value>
                    <value>qwerty@163.com</value>
                </list>
    			</property>
    
  • 集合类型

    <!-- set集合是无序的 不一定按照赋值顺序存入
     		这里嵌套value标签是因为Person类中定义的是Set<String>
    		假如没有定义泛型,则set标签中也可以嵌套其他标签
    	-->
    <property name="tels">
          <set>
              <value>138123123</value>
              <value>432123432</value>
              <value>132543543</value>
          </set>
    </property>
    
    <!-- list集合底层是以数组完成  和数组一样 property中嵌套list标签 
    		list集合是有序的、可重复的
    -->
    <property name="addresses">
        <list>
            <value>centrypark</value>
            <value>swimmingpool</value>
            <value>blocks</value>
            <value>blocks</value>
            <value>blocks</value>
        </list>
    </property>
    
    <!-- 对于map集合 存储的是键值对 实际上一个键值对就是map中的一个内部类Map.Entry
    	所以赋值时应嵌套Entry标签
     Person类中定义的是Map<String,String> 所以key标签中仍嵌套value标签
    -->
    <property name="qqs">
        <map>
            <entry>
                <key><value>liush</value></key>
                <value>1234324</value>
            </entry>
        </map>
    </property>
    
    
    <!-- Properties类型 特殊的Map  key和value都只能是String类型 
    	一个prop就是一个键值对  value直接写值即可 不用再嵌套value标签
    -->
    <property name="p">
        <props>
            <prop key="key1">value1</prop>
            <prop key="key2">value2</prop>
        </props>
    </property>
    
  • 复杂的JDK类型(Date)

    ​ 需要程序员自定义类型转换器处理。

用户自定义类型
第一种方式
  • 为成员变量提供set get方法

  • 配置文件中进行注入(赋值)

    <!-- userService包含一个private UserDAO userDAO; -->
    <bean id="userService" class="com.liu.pojo.UserServiceImpl">
    	<property name="userDAO">
        <bean class="com.liu.pojo.UserDAOImpl"/>
    	</property>
    </bean>
    
第二种方式
  • 第一种方式存在的问题

    假如有多个类都有UserDAO作为成员变量,那么配置文件中
    <property name="userDAO">
        <bean class="com.liu.pojo.UserDAOImpl"/>
    </property>
    
    这段代码会重复出现多次 冗余
    且会创建很多次userDAO对象 浪费内存(JVM)资源
    
  • 为成员变量提供set get方法

  • 在配置文件中先创建userDAO,然后在有UserDAO作为成员变量的类创建标签中,用ref标签引用这个userDAO对象

    <bean id="userDAO" class="com.liu.pojo.UserDAOImpl"/>
    
    <bean id="userService" class="com.liu.pojo.UserServiceImpl">
        <property name="userDAO">
            <ref bean="userDAO"/>
        </property>
    </bean>
    
    <bean id="orderService" class="com.liu.pojo.orderServiceImpl">
        <property name="userDAO">
            <ref bean="userDAO"/>
        </property>
    </bean>
    
    #Spring4.x 废除了 <ref local=""/> 这个标签和<ref bean=""/>基本等效
    

Set注入的简化写法

基于属性简化
JDK类型注入
<property name="name">
	<value>liush</value>
</property>
简化写法
<property name="name" value="liush"/>
注意:value属性只能简化8中基本类型+String的注入标签

用户自定义类型
<property name="userDAO">
	<ref bean="userDAO"/>
</property>
简化写法
<property name="userDAO" ref="userDAO" />
基于p命名空间简化
p即property的缩写
JDK类型注入
<bean id="person" class="com.liu.pojo.Person">
  <property name="name">
    <value>liush</value>
  </property>
</bean>
简化写法
<bean id="person" class="com.liu.pojo.Person" p:name="xiaodong" p:id="11" />

用户自定义类型
<bean id="person" class="com.liu.pojo.Person">
  <property name="userDAO">
    <ref bean="userDAO"/>
  </property>
</bean>
简化写法
<bean id="userService" class="com.liu.pojo.UserServiceImpl" p:userDAO-ref="userDAO" />

构造注入

注入:通过Spring的配置文件,为成员变量赋值

Set注入:Spring通过调用set方法 通过配置文件 为成员变量赋值

构造注入:Spring调用构造方法 通过配置文件 为成员变量赋值

步骤

  1. 提供有参构造方法
package com.liu.constructor;
import java.io.Serializable;

public class Customer implements Serializable {

    private String name;
    private int age;

    public Customer(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 配置文件
配置文件中参数顺序也要与类种定义的构造器参数顺序一致
<bean id="customer" class="com.liu.constructor.Customer">
  <constructor-arg name="name" value="liushihao"/>
  <constructor-arg name="age" value="21"/>
</bean>

构造方法存在重载时

  1. 参数个数不同时
通过控制<constructor-arg />标签的数量来调用不同的构造方法
  1. 参数个数相同时
当存在相同个数参数的构造方法时,需要在标签中添加type=""来告诉Spring 进行区分
<constructor-arg type="" />

注入的总结

问题:未来实际应用时使用set注入还是构造注入?

答:set注入用得更多。

原因:构造注入麻烦(存在重载)、Spring框架底层用了大量的set注入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RRKU4wLW-1620665224818)(D:\截图\image-20210324221624478.png)]

控制反转与依赖注入

控制反转(IOC:Inverse of Control)

纯概念

控制:对于成员变量赋值的控制权

​ 没有Spring时,直接在代码中完成对成员变量的赋值,控制权在代码中,存在耦合

控制反转:对于成员变量赋值的控制权,从代码中转移到了Spirng配置文件和工厂,解耦合

底层实现:工厂设计模式

依赖注入(DI:Dependency Injection)

一种Spring的编程方式,应理解体会思想并应用于后续开发

注入:通过Spring的工厂及配置文件,为对象(bean、组件)的成员变量赋值

依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以将另一个类作为本类的成员变量,最终通过Spring配置文件进行注入(赋值),解耦合。

​ 例如UserService依赖UserDAO访问数据库来实现业务,那么UserService就可以将UserDAO设为成员 变量,然后通过Spring的配置文件进行赋值注入。

Spring工厂创建复杂对象

此前创建的都是简单对象,简单对象指的就是可以直接通过new 构造方法来创建的对象

复杂对象

复杂对象:指的是不能直接通过new 构造方法创建的对象

例子
//jdbc中的Connection
  Class.forName("com.mysql.jdbc.Driver")
  DriverManager.getConnection();

//Mybatis中的SqlSessionFactory
  	String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		new SqlSessionFactoryBuilder().build(inputStream);

Spring工厂创建复杂对象的3种方式

FactoryBean接口

Spring原生提供 重要

  • 开发步骤

    实现FactoryBean接口和其中的一些方法

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lco0fJFb-1620665224819)(D:\截图\image-20210325212715623.png)]

    在pom.xml中添加依赖

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

    创建FactoryBean

    package com.liu.factorybean;import org.springframework.beans.factory.FactoryBean;
    import java.sql.Connection;import java.sql.DriverManager;
    public class ConnectionFactoryBean implements FactoryBean<Connection> {    
    //用于书写创建复杂对象的代码  类实现接口时引用了泛型,所以这里返回Connection    
    	public Connection getObject() throws Exception {        
    		Class.forName("com.mysql.jdbc.Driver");        
    		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/liush", "root", "123456");        
    		return conn;    
    	}    
    	public Class<?> getObjectType() {        
    		return Connection.class;    
    	}    
    	public boolean isSingleton() {        
    		return false;    
    	}
    }
    

    Spring配置文件的配置

    <bean id="conn" class="com.liu.factorybean.ConnectionFactoryBean"/>
    

    此时context.getBean(“conn”)获得的是ConnectionFactoryBean所创建的复杂对象Connection,对于复杂对象,此时ConnectionFactoryBean变得相对次要了

细节

  • 如果就想获取FactoryBean类型的对象 就写context.getBean("&conn"),获取ConnectionFactoryBean对象

  • isSingleton方法

    返回ture 只会创建一个复杂对象

    返回false 每一次都会创建新的对象

    根据对象的特点来决定是否单例,如果对象能被大家共用就返回true,否则false

    ​ 连接对象重要作用是控制事务,不能被大家共用,返回false比较合理

    ​ SqlSessionFactory是重量级资源,返回true比较合理

  • mysql高版本连接创建时,需要制定ssl证书,控制台会警告报错

    解决:

    在连接的url字符串后添加useSSL=false即可  "jdbc:mysql://localhost:3306/liush?useSSL=false"
    
  • 对于依赖注入的体会

    对FactoryBean进行改进,将jdbc连接中重要的参数抽取为成员变量,提供set get方法

    package com.liu.factorybean;import org.springframework.beans.factory.FactoryBean;import java.sql.Connection;import java.sql.DriverManager;public class ConnectionFactoryBean implements FactoryBean<Connection> {    private String driverClassName;    private String url;    private String user;    private String password;    public String getDriverClassName() {        return driverClassName;    }    public void setDriverClassName(String driverClassName) {        this.driverClassName = driverClassName;    }    public String getUrl() {        return url;    }    public void setUrl(String url) {        this.url = url;    }    public String getUser() {        return user;    }    public void setUser(String user) {        this.user = user;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    public Connection getObject() throws Exception {        Class.forName(driverClassName);        Connection conn = DriverManager.getConnection(url, user, password);        return conn;    }    public Class<?> getObjectType() {        return Connection.class;    }    public boolean isSingleton() {        return false;    }}
    

    在配置文件中赋值注入

    <bean id="conn" class="com.liu.factorybean.ConnectionFactoryBean">        <property name="driverClassName" value="com.liu.factorybean.ConnectionFactoryBean"/>        <property name="url" value="jdbc:mysql://localhost:3306/liush?useSSL=false"/>        <property name="user" value="root"/>        <property name="password" value="ls2000818"/></bean>
    
  • FactoryBean的实现原理(简易版)

    问题

    1. 为什么Spring规定FactoryBean接口让我们实现 并且要把创建复杂对象的代码写在getObject方法当中?
    2. 为什么context.getBean(“conn”)获得的是ConnectionFactoryBean所创建的复杂对象Connection,而不是获取的Factory Bean?

    Java底层基本上都是反射+接口回调 “接口加反射,什么都能做”

    Spring运行流程

    1. 通过conn获得ConnectionFactoryBean类的对象,进而通过instanceof判断出是FactoryBean接口的实现类
    2. Spring按照规定 调用getObject方法 获得Connection对象
    3. 返回Conncetion

在这里插入图片描述

  • FactoryBean总结

    Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续整合其他框架是,会大量应用FactoryBean

实例工厂

应用实例工厂创建复杂对象的原因:

  1. 避免Spring框架的侵入

    选择FactoryBean方式时要implements FactoryBean接口,日后如果不用Spring就失效了

  2. 整合遗留系统

    原有系统遗留了FactoryBean但是只能用.class文件,无法在源码中实现FactoryBean接口,只能new ConnectionFactory().getConnection(); 就是对工厂进行了实例化

//遗留的工厂类package com.liu.factorybean;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;public class ConnectionFactory {    public Connection getConnection(){        Connection conn = null;        try {            Class.forName("com.mysql.jdbc.Driver");            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/liush?useSSL=false","root","ls2000818");        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException throwables) {            throwables.printStackTrace();        }        return conn;    }}
  • 开发

配置文件配置后测试即可

<bean id="connFactory" class="com.liu.factorybean.ConnectionFactory"></bean><bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>

静态工厂

静态即工厂即工厂的方法是static的

public class StaticConnectionFactory {	public static Connection getConnection(){    ...  }}

配置文件

<bean id="conn" class="com.liu.factorybean.StaticConnectionFactory" factory-method="getConnection"/>

与实例工厂的区别就是创建StaticConnectionFactory就可以直接调用getConnection方法,不用再另外配置一个bean标签

Spring工厂创建对象的总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8NgQg97c-1620665224821)(D:\截图\image-20210326171343999.png)]

如果不考虑遗留系统整合,建议使用实现FactoryBean接口方式来创建负责对象

控制Spring工厂创建对象的次数

创建负责对象时如果使用实现FactoryBean接口,其中有isSingleton方法,解决了复杂对象创建次数的控制。

简单对象

只需要在bean标签中添加scope属性即可

singleton(默认):只创建一次

prototype:每次都创建新的对象

<bean id="account" scope="prototype" class="com.liu.scope.Account"/>

复杂对象

implements FactoryBean{  isSingleton(){    return true  只创建一次    return false  每次都创建新的对象  }}实例工厂、静态工厂 没有isSingleton方法,还是通过scope属性控制对象的创建次数

为什么要控制对象的创建次数?

好处:节省不必要的内存浪费

  • 什么样的对象只创建一次 能被共用的、线程安全的

    SqlSessionFactory、DAO、Service

  • 什么样的对象每次都要创建新的 不能被共用的、线程不安全的

    Connection、SqlSession|Session、Action(Struts2中)

对象的生命周期

概念

指的是一个对象创建、存活、消亡的完整过程

为什么要学习对象的生命周期

由Spring负责对象的创建、存活、销毁,了解生命周期,有利于更好地使用Spring为我们创建对象

生命周期的3个阶段

创建阶段

Spring工厂何时创建对象

scope=“singleton”

Spring工厂创建的同时,完成对象的创建

scope=“prototype”

Spring会在获取对象即context.getBean("")的同时,创建对象

如果singleton时也需要在获取时再创建对象,在bean中添加lazy-init属性

<bean id="product" scope="singleton" class="com.liu.life.Product" lazy-init="true"/>

初始化阶段

Spring工厂在**创建完对象后**,调用对象的初始化方法,完成对应的初始化操作

初始化方法提供:由程序员根据需求提供,最终完成初始化

初始化方法调用:Spring工厂调用

初始化方式

  • 实现Spring提供的initializingBean接口
public class Product implements InitializingBean {	//必须继承的初始化方法  根据实际需求在里面做一些初始化操作 Spring就会进行调用	public void afterPropertiesSet() throws Exception {			...	}}

使用这种方法Spring会自行调用,不需要额外配置

  • 对象中提供一个简单方法
public void myInit(){  }然后在配置文件bean标签中添加init-method属性 告诉Spring来调用这个方法来初始化<bean id="product" class="com.liu.life.Product" init-method="myInit"/>

使用这种方式可以不与Spring耦合,但需要额外配置

细节分析
  1. 如果一个对象即实现InitializingBean,同时又提供了普通的初始化方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASpUXM5o-1620665224822)(D:\截图\image-20210326202506983.png)]

​ 执行顺序:IntializingBean ===> 普通初始化方法

  1. 如果同时也给对象注入赋值

    执行顺序:则先注入,再初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNo36bI6-1620665224823)(D:\截图\image-20210326203051216.png)]

  1. 什么是初始化操作

    对于资源的初始化:数据库、IO、网络 … 以后使用较少

销毁阶段

Spring销毁对象前会调用对象的销毁方法,完成销毁操作,即资源释放的操作

  1. Spring什么时候销毁所创建的对象

    工厂关闭时 context.close();

  2. 销毁方法提供:程序员根据实际需求,定义销毁方法,完成销毁操作

    销毁方法调用:Spring工厂

  • DisposableBean
//Product类实现接口  并继承方法public class Product implements InitializingBean, DisposableBean {		public void destroy() throws Exception {        System.out.println("Product.destroy");    }}//测试@Testpublic void test13(){  //close方法是定义在ClassPathXmlApplicationContext中的,所以要把原来的父类声明改为子类声明    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");      Product product = (Product) context.getBean("product");      	context.close();}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cri18L7-1620665224823)(D:\截图\image-20210327110448441.png)]

  • 定义一个普通销毁方法

和初始化类似,在类中定义普通销毁方法后,在配置文件bean标签中添加destroy-method属性指定定义的普通销毁方法即可

<bean id="product" class="com.liu.life.Product" init-method="myInit" destroy-method="myDestroy" />
细节分析
  1. 销毁方法的操作只适用于 scope = “singleton” 的对象

  2. 什么叫做销毁操作

    主要指一些资源的释放 io.close() connection.close() 以后使用较少

总结

在这里插入图片描述

配置文件参数化

  1. Spring的配置文件中存在的经常需要修改的字符串

    与数据库连接相关的参数(driver、url、user、password)为代表

  2. 经常变化的字符串,在Spring的配置文件中直接修改

    不利于项目维护(修改)

把Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中,利于Spring配置文件的维护

一般是 xml ====> properties

开发步骤

  1. 提供一个小的配置文件(.properties)

    文件的名字和放置位置没有要求

jdbc.driverClassName = com.mysql.jdbc.Driverjdbc.url = jdbc:mysql://localhost:3306/liush?useSSL=falsejdbc.userName = rootjdbc.password = ls2000818
  1. Spring配置文件和小配置文件进行整合
<!-- Spring配置文件与小配置文件进行整合    开发时main和resources是同级分开的两个目录  但是编译时其实两者是合并的    classpath即类路径 指的就是target/classes--><context:property-placeholder location="classpath:dataBase.properties"/>
  1. 在Spring配置文件中通过${key}的方式获取小配置文件中对应的值
<bean id="conn" class="com.liu.factorybean.ConnectionFactoryBean">  <property name="driverClassName" value="${jdbc.driverClassName}"/>  <property name="url" value="${jdbc.url}"/>  <property name="user" value="${jdbc.userName}"/>  <property name="password" value="${jdbc.password}"/></bean>

自定义类型转换器

类型转换器

作用:Spring通过类型转换器通过配置文件中 vlaue="" 的字符串数据,转换成了对象中成员变量对应类型的数据,进而完成注入

在这里插入图片描述

自定义类型转换器

为什么需要自定义类型转换器:当Spring内部没有提供特定的类型转换器时,实际应用中又需要使用,那就需要自己定义。

例如:不同国家地区使用的日期格式不一样,Spring没有提供字符串String(“2021-03-27”)转换为日期Date的类型转换器

开发步骤

  1. 实现converter接口
package com.liu.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

//Converter<s, t>  由s转换为t
public class MyDateConverter implements Converter<String, Date> {

    /*
    * convert方法的作用:String ==> Date
    *     SimpleDateFormat sdf = new SimpleDateFormat();
    *     sdf.parset(String) ===> Date
    *
    *  param:source 代表的就是配置文件中的日期字符串 <value>2021-03-27</value>
    *  return:把转换好的Date作为convert方法的返回值后,Spring会自动为Person中的birthday属性进行注入(赋值)
    * */
    public Date convert(String source) {

        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return date;
    }
}
  1. Spring配置文件中进行注册
  • 创建MyDateConverter对象
<bean id="myDateConverter" class="com.liu.converter.MyDateConverter"/> 
  • 类型转换器的注册

    目的:告知Spring框架,自定义的MyDateConverter是一个类型转换器

<!-- 用于注册类型转换器 ConversionServiceFactoryBean中的参数是set   private Set<?> converters;所以<properties>中嵌套<set>--><bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">  <property name="converters">      <set>          <ref bean="myDateConverter"/>      </set>  </property></bean>

细节分析

  • 将MyDateConverter中的日期格式提取为成员变量,通过依赖注入的方式,由配置文件完成赋值
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");public class MyDateConverter implements Converter<String, Date> {    private String dateFormat;    getter and setter    public Date convert(String source) {        Date date = null;        try {            SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);            date = sdf.parse(source);        } catch (ParseException e) {            e.printStackTrace();        }        return date;    }}

​ 配置文件

<bean id="myDateConverter" class="com.liu.converter.MyDateConverter">    <property name="dateFormat" value="yyyy-MM-dd"/></bean>
  • 对于普通的bean,id可以随便写,只要唯一就行

    但是对于ConversionServiceFactoryBean这个类而言,id必须为 conversionService

  • Spring框架内置的日期类型转换器

    日期格式:2021/03/27 (我们更习惯写的是2021-03-27)

后置处理Bean

BeanPostProcessor

  • 是Spring工厂中非常有价值的高级特性,底层进行高级封装时都有这个技术的影子

  • AOP底层实现中BeanPostProcessor是很重要的技术环节

作用:对Spring工厂所创建的对象,进行再加工

​ BeanPostProcessor本身是一个接口,实现接口后,将具体加工操作写在接口规定的方法中即可

后置处理Bean原理运行分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-McFEMz9K-1620665224825)(D:\截图\image-20210327194809913.png)]

BeanPostProcessor接口规定要实现的方法:Object postProcessBeforeInitialization(Object bean, String beanName)作用:Spring创建完对象,进行注入后,可以运行该Before方法进行加工获得Spring创建好的对象:通过方法的参数最后把加工后的对象作为返回值,交给Spring框架Object postProcessAfterInitialization(Object bean, String beanName)作用:Spring执行完对象的初始化操作后,可以运行该After方法进行加工获得Spring创建好的对象:通过方法的参数最后把加工后的对象作为返回值,交给Spring框架

实际应用当中,很少处理Spring的初始化操作,所以后置处理没必要分Before和After,只需要实现After方法即可

​ 但是既然实现了BeanPostProcessor接口,即使postProcessBeforeInitialization方法不对bean进行加工,也必须return bean对象交回给Spring

开发步骤

  1. 类 实现BeanPostProcessor接口
package com.liu.beanpost;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;public class MyBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        return bean;    }      public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        Categroy categroy = (Categroy) bean;        categroy.setName("xiaoming");        return categroy;    }}
  1. Spring的配置文件中进行配置
<bean id="c" class="com.liu.beanpost.Categroy">  	<property name="id" value="10"/>  	<property name="name" value="liush"/></bean><bean id="myBeanPostProcessor" class="com.liu.beanpost.MyBeanPostProcessor"/>
  • 细节

    BeanPostProcessor会对Spring工厂中创建的所有对象进行加工

    所以所有对象都会经过加工方法,应加入类型判断

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {    if (bean instanceof Categroy) {        Categroy categroy = (Categroy) bean;        categroy.setName("xiaoming");    }    return bean;}

静态代理设计模式

为什么需要代理设计模式

问题

  • 在JavaEE分层开发中,哪个层次对于我们来说最重要?
DAO ===> Service ===> ControllerService是最重要的,开发中就是要满足用户业务需求
  • Service层中包含哪些代码?
Service层 = 核心业务功能(几十行 上百行) + 额外(附加)功能1. 核心功能	业务运算、DAO调用2. 额外功能	不属于业务、可有可无、代码量很小	如 事务、日志、性能
  • 额外功能书写在Service层中好不好?
Service层调用者的角度(Controller):需要在Service层中书写额外功能
																	如需要事务来保证操作的完整性
软件设计者的角度:额外功能不应该写在Service层中
								毕竟可有可无,有需求得往里加,没有需求得删,不利于代码维护
  • 现实生活中的解决方案

比如房东(Service)想要出租房屋,需要打广告、带人看房(额外功能)和签合同、收房租(核心业务功能),但是打广告和带人看房自己做太累了,对于房客(调用者)来说又必须有这两项才能决定租房东的房子,此时,就出现了中介(Proxy),即代理。

通过引入代理(中介)类,由代理类来完成额外功能,并调用目标类(房东)的核心功能(签合同、收房租),就同时解决了房客、房东的问题。后续如果对代理类附加功能的完成不满意,直接更换代理。

代理设计模式

通过代理类,为原始类(目标类)增加额外的功能
好处:利于原始(目标)类的维护,额外功能的变化不影响原始类的代码

名词解释

  • 目标类 原始类

    指的是业务类(核心功能)

  • 目标方法 原始方法

    目标(原始)类中的方法

  • 额外(附加)功能

    以日志、性能、事务为代表

代理开发的核心要素

代理类 = 和原始类实现相同的接口 + 目标(原始类)类的核心功能方法 + 额外功能房东 ===> public interface UserService{								m1								m2					}					UserServiceImpl implements UserService{								m1  ===> DAO调用								m2  ===> 业务运算					}					UserServiceProxy implements UserService{					      m1					      m2					}

开发步骤

静态代理:每有一个新的原始类,就要手写一个新的代理类

目标(原始)类

package com.liu.proxy;public class UserServiceImpl implements UserService{    public void register(User user) {        System.out.println("UserServiceImpl.register 业务运算 + DAO调用");    }    public boolean login(String name, String password) {        System.out.println("UserServiceImpl.login 业务运算 + DAO调用");        return true;    }}

代理类

package com.liu.proxy;public class UserServiceProxy implements UserService{    private UserServiceImpl userService = new UserServiceImpl();    public void register(User user) {        System.out.println("-----log-----");        userService.register(user);    }    public boolean login(String name, String password) {        System.out.println("-----log-----");        return userService.login(name, password);    }}

存在的问题

1. 静态代理文件过多,不利于项目管理  随着原始类增加,类文件数量成倍增长		UserServiceImpl  UserServiceProxy		OrderServiceImpl OrderServiceProxy  2. 额外功能维护性非常差		代理类中 额外功能修改起来很麻烦

动态代理开发

概念上和静态代理一样,不同点在于开发方式和底层实现。

开发步骤

搭建环境

pom.xml文件中引入依赖<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-aop</artifactId>    <version>5.1.14.RELEASE</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --><dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjrt</artifactId>    <version>1.8.8</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjweaver</artifactId>    <version>1.8.3</version></dependency>

创建原始类对象

​ 原始类UserServiceImpl

package com.liu.proxy;public class UserServiceImpl implements UserService{    public void register(User user) {        System.out.println("UserServiceImpl.register 业务运算 + DAO调用");    }    public boolean login(String name, String password) {        System.out.println("UserServiceImpl.login 业务运算 + DAO调用");        return true;    }}

​ 在工厂配置文件中配置创建对象

<bean id="userService" class="com.liu.proxy.UserServiceImpl"></bean>

额外功能

​ 实现Spring提供的MethodBeforeAdvice接口

package com.liu.dynamic;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class Before implements MethodBeforeAdvice {    /*    * before方法作用:在原始类的核心业务方法执行前,运行该方法中的额外功能    * */    public void before(Method method, Object[] objects, Object o) throws Throwable {        System.out.println("--method before advice log--");    }}

​ 配置文件中创建该类

<bean id="before" class="com.liu.dynamic.Before"/>

定义切入点

切入点:额外功能加入的位置(哪个方法)

目的:由程序员根据实际需求,决定将额外功能加入给哪个原始方法

简单测试:所有方法都作为切入点,都加入额外的功能配置文件中加入<aop:config>    <!-- 所有的方法都作为切入点,加入额外功能 -->    <aop:pointcut id="pc" expression="execution(* *(..))"/></aop:config>

组装(整合前两步)

在刚才的<aop:config></aop:config>标签中添加:<!-- 组装:把切入点与额外功能进行整合 --><aop:advisor advice-ref="before" pointcut-ref="pc"/>表达的含义:对于切入点pc(所有方法)加入before这个额外功能

调用

目的:获得Spring工厂创建的动态代理对象,并对其进行调用

细节

UserService userService = (UserService) context.getBean("userService");
  1. 此时 通过原始类的id 从Spring的工厂中获得的是**代理对象**
  2. 获得代理对象后,可以通过声明接口类型,进行对象的存储(因为代理类实现了和原始类一样的接口)
@Testpublic void test02(){    ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");    UserService userService = (UserService) context.getBean("userService");    userService.register(new User());    userService.login("liush","123456");}

输出结果
在这里插入图片描述

细节分析

  1. Spring所创建的动态代理类在哪里?
Spring框架在运行时,通过动态字节码技术在JVM创建的,运行在JVM内部,等程序结束后回合JVM一起消失。动态代理类 ===> 动态字节码普通过程: .java ==> .class(字节码) ==> 加载到虚拟机中运行* 动态字节码技术:通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码也跟着消失。* 不需要去定义类文件,JVM运行过程中动态创建,所以不会造成静态代理中类文件成倍增长的问题。
  1. 动态代理编程简化代理的开发
在额外功能不改变的情况下,创建其他目标(原始)类的代理对象时,只需要在配置组装过的配置及文件中添加该bean即可。
  1. 动态代理中额外功能的维护性大大改善
需要修改额外功能时直接写新的,在配置文件中修改即可

Spring动态代理详解

额外功能详解

MethodBeforeAdvice分析

作用:额外功能运行在原始方法执行之前public class Before implements MethodBeforeAdvice {    /*    * 参数    * Method:增加额外功能所的那个原始方法    * Object[]:增加额外功能的那个原始方法对应的参数集合  如login方法的就是String name, String password    * Object:增加额外功能的原始对象    * */    public void before(Method method, Object[] objects, Object o) throws Throwable {        System.out.println("--method before advice log--");    }}

​ debug观察在这里插入图片描述

before方法的三个参数在实际应用中如何使用?  不一定使用,会根据实际需要来进行使用

MethodInterceptor(方法拦截器)分析

  • MethodBeforeAdvice ==> 只能运行在方法执行之前

  • MethodInterceptor更为灵活,可以根据需要运行在之前,也可以在之后,还可以在前后都同时运行。实际应用中建议实现这个接口。

package com.liu.dynamic;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;public class Around implements MethodInterceptor {    /*    * 将额外功能写在invoke方法当中,额外功能就可以运行在原始方法之前、之后、前后同时运行    *    * 参数MethodInvocation(底层是对Method的更高级的封装):增加额外功能的原始方法    *   methodInvocation.proceed() ==> 对应的原始方法运行    *  在这句代码的前后就可以自定义额外功能    *    * 返回值Object:原始方法的返回值    * */    public Object invoke(MethodInvocation methodInvocation) throws Throwable {        //额外功能        System.out.println("-----额外功能运行在原始方法之前-----");             		Object ret = methodInvocation.proceed();            	//额外功能				System.out.println("-----额外功能运行在原始方法之后-----");        return ret;    }}//什么样的功能需要在原始方法前后都运行?		事务。

​ 额外功能运行在原始方法抛出异常时

public Object invoke(MethodInvocation methodInvocation) throws Throwable {    Object ret = null;    try {        ret = methodInvocation.proceed();    } catch (Throwable throwable) {        System.out.println("---原始方法抛出异常  执行额外的功能---");        throwable.printStackTrace();    }      return ret;}
  • MethodInterceptor影响原始方法的返回值
原始方法的返回值直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值	Object ret = methodInvocation.proceed();	return ret;MethodInterceptor影响原始方法的返回值:修改return的值,不直接返回原始方法的运行结果即可

切入点详解

切入点决定额外功能的加入位置(方法)<aop:pointcut id="pc" expression="execution(* *(..))"/>execution(* *(..))  ===> 匹配了所有方法1. excution()  切入点函数2. * *(..)	切入点表达式

切入点表达式

public void add(String a, String b)	  *        *(..)  *即通配符  第一个* ==> 修饰符没有要求  两个*之间的空格 ==> 返回值  () ==> 参数表  括号中的.. ==> 对有没有参数、参数类型和参数数量都没有要求
  • 定义login方法作为切入点
* login(..)其他方法修改方法名即可
  • 定义login方法且定义login方法有两个字符串类型的参数作为切入点
* login(String,String)此时,String是java.lang包下的,只写String即可如果不是java.lang包下的,要写全限定类名* login(String,..) 只限定第一个参数为String,后面的参数数量及类型都没有要求
  • 以上的切入点表达式都不够精准
精确定位方法: * 包.类.方法(参数)
类切入点
对指定的类中所有方法都加入额外功能语法1:	* 包.类.*(..)语法2(将不同包中的同名类作为切入点):	* *.类.*(..)  类只存在一级包	* *..类.*(..)  类存在多级包
包切入点
指定包中外额外功能加入的位置语法:* 包.*.*(..) 	* com.liu.proxy.*.*(..)	必须在proxy包中,不能在proxy的子包中当前包及其子包:* com.liu.proxy..*.*(..)# 包切入点更具实际应用价值

切入点函数

作用:用于执行切入点表达式

execution
最为重要的切入点函数,功能最全可以执行 方法、类、包切入点表达式* 弊端:书写比较冗长麻烦注意:其他切入点函数,功能上和execution完全一致,只是简化其书写复杂程度
args
作用:主要用于函数(方法)参数的匹配如:切入点要求方法参数是两个字符串类型的参数exeition写法: execution(* *(String,String))args写法: args(String,String)
within
作用:用于进行类、包切入点表达式的匹配如:切入点要求是UserServiceImpl这个类execution写法: execution(* *..UserServiceImpl.*(..))within写法: within(*..UserServiceImpl)
@annotation
作用:为具有特殊注解的方法加入额外的功能 1. 自定义注解package com.liu;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Log {}2. 在方法上加上注解package com.liu.proxy;import com.liu.Log;public class UserServiceImpl implements UserService{    @Log  public void register(User user) {      System.out.println("UserServiceImpl.register 业务运算 + DAO调用");  }  public boolean login(String name, String password) {      System.out.println("UserServiceImpl.login 业务运算 + DAO调用");      return true;  }}3. 配置文件<aop:pointcut id="pc" expression="@annotation(com.liu.Log)"/>
切入点函数的逻辑运算
指的是整合多个切入点函数,一起配合工作,完成更加复杂的需求
  • and 与操作
案例:想选择方法名为login,参数为两个字符串的方法作为切入点1. execution(* login(String,String))2. execution(* login(..)) and args(String,String)# 注意:与操作不能用于同种类型的切入点函数案例:需要增强一个类中的register和login方法execution(* login(..)) and execution(* register(..))是无法完成需求的表达的是一个方法同时函数名为login和register应为execution(* login(..)) or execution(* register(..))
  • or 或操作
execution(* login(..)) or execution(* register(..))

AOP编程

Spring工厂封装了工厂设计模式,AOP编程封装的是代理模式

概念

AOP:Aspect Oriented Programing,面向切面编程

本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能

​ 好处:利于原始类的维护

​ 以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建

​ 切面 = 切入点 + 额外功能

OOP:Object  Oriented Programing,面向对象编程  Java	以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建POP:Producer  Oriented Programing,面向过程编程  C	以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建# AOP编程是OOP编程的有意补充

开发步骤

就是Spring动态代理开发的步骤1. 原始对象2. 额外功能(MethodInterceptor)3. 切入点4. 组装切面(额外功能 + 切入点)

切面的名词理解

切面 = 切入点 + 额外功能对于“切面”理解1. 几何学	面 = 一些相同性质的点切面 = 具有相同额外功能的切入点2. 有多个ServiceImpl,每个ServiceImpl都有一个切入点,多个点就组成了一个面,切入到Service中

AOP的底层实现原理

也是Spring动态代理的底层实现原理

核心问题

1. AOP如何创建动态代理类(动态字节码技术)2. Spring的工厂如何加工创建代理对象   通过原始对象的id,获得的是代理对象

动态代理类的底层创建

代理创建3要素1. 原始对象2. 额外功能3. 代理对象和原始对象实现相同的接口

JDK的动态代理

创建原始对象
UserService userService = new UserServiceImpl();
JDK创建动态代理
Proxy.newProxyInstance(classLoader, interfaces, invocationhandler);
  • 参数classLoader
借用一个类加载器,创建代理类的Class对象,进而创建代理对象

 类加载器作用
 1. 把对应类的字节码文件加载到JVM中
 2. 创建类的Class对象,进而创建这个类的对象
 例如创建一个User,先要写User.java,编译后生成User.class,再经过以上两步,才能创建出一个对象
	如何获得类加载器:JVM会为每个.class文件自动分配与之对应的ClassLoader

而动态代理是通过动态字节码技术,将动态代理类字节码直接写到JVM中,没有具体的.java和.class文件,所以JVM不会为之分配CL,但是创建动态代理对象又需要CL来创建其Class对象,此时,就需要借用一个ClassLoader。这就是newProxyInstance方法第一个参数的含义。

在这里插入图片描述

  • 参数interfaces
原始对象实现的接口,userService.getClass().getInterfaces();
  • 参数invocationhandler
invoke方法:用于书写额外功能,额外功能可以运行在原始方法执行前、后、前后、抛出异常时
和MethodInterceptor的Object invoke(MethodInvocation invocation)类似
	
	Object:原始方法的返回值
	参数:Proxy可以忽略,代表的是代理对象 通过Proxy.newProxyInstance(...)创建
			 Method额外功能所增加给的那个原始方法
			 Object[]原始方法的参数		 
Object invoke(Object proxy, Method method, Object[] args){
   System.out.println("---log---");
	 //运行原始对象的原始方法
   Object ret = method.invoke(userService, args);
}
编码
package com.liu.jdk;import com.liu.proxy.User;import com.liu.proxy.UserService;import com.liu.proxy.UserServiceImpl;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class TestJDKProxy {    public static void main(String[] args) {        /*        * 1.创建原始对象        *        * jdk8.x前 需要声明为final        *   final UserService userService = new UserServiceImpl();        * */        final UserService userService = new UserServiceImpl();        //2.JDK创建动态代理      	//内部类创建InvocationHandler        InvocationHandler handler = new InvocationHandler() {            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                System.out.println("---proxy log---");                //原始方法运行                Object ret = method.invoke(userService, args);                return ret;            }        };        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), handler);				      	//运行        userServiceProxy.login("liush", "123456");        userServiceProxy.register(new User());    }}

Cglib的动态代理

实际应用场景:需要给一个没有实现任何接口的原始类创建代理类Cglib创建动态代理的原理	父子继承关系创建代理对象,原始类作为父类,代理类作为子类。	这样既可以保证二者方法一致,又可以在代理类中提供原始方法新的实现(额外功能+原始方法调用)

在这里插入图片描述

package com.liu.cglib;

import com.liu.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {
        //1.创建原始对象
        final UserService userService = new UserService();

        /*
        * 2.Cglib创建动态代理对象
        *
        *   JDK创建动态代理
        *   Proxy.newProxyInstance(classLoader, interfaces, invocationhandler)
        *
        *   Cglib创建动态代理
        *   设置类加载器:Enhancer.setClassLoader()
        *   设置父类:Enhancer.setSuperClass()
        *   设置额外功能:Enhancer.setCallback()  ===> 需要MethodInterceptor(cglib)  与invocationHandler相似
        *   Enhancer.create() ===> 创建代理
        * */

        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            //等同于InvocationHandler中的invoke方法
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("---cglib log---");
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };
        enhancer.setCallback(interceptor);

        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.login("liush", "123456");
        userServiceProxy.register(new User());

    }
}

总结

1. JDK动态代理  Proxy.newProxyInstance()  通过接口创建代理的实现类2. Cglib动态代理  Enhance									通过继承父类创建的代理类

Spring工厂如何加工原始对象创建代理类

BeanPostProcessor + JDK/Cglib动态代理

image-20210407204910872

编码(模拟Spring加工原始对象)

模拟Spring的AOP原理,没有引入切入点,比较粗糙,只关注其核心——代理类的创建

ProxyBeanPostProcessor类

package com.liu.factory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class ProxyBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        return bean;    }    /*    * 在方法中通过Proxy.newProxyInstance()创建代理类    * */    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {        InvocationHandler handler = new InvocationHandler() {            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                System.out.println("---new log---");                Object ret = method.invoke(bean, args);                return null;            }        };        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);    }}

配置文件

<bean id="userService" class="com.liu.factory.UserServiceImpl"></bean>    <!--    1.实现BeanPostProcessor接口  在其中进行加工    2.配置文件中对BeanPostProcessor进行配置    -->    <bean id="proxyBeanPostProcessor" class="com.liu.factory.ProxyBeanPostProcessor"/></beans>

Test类

package com.liu.factory;import com.liu.proxy.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test {    public static void main(String[] args) {        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext1.xml");        UserService userService = (UserService) context.getBean("userService");        userService.login("liushi", "123545");        userService.register(new User());    }}

基于注解的AOP编程

基于注解的AOP编程开发步骤

  1. 原始对象

  2. 额外功能

  3. 切入点

  4. 组装切面

  • 之前的MethodInterceptor开发
1. 额外功能  public  class Around implements MethodInterceptor{      public Object invoke(MethodInvocation invocation){          Object ret = invocation.proceed();          return ret;      }  }  配置文件中创建:  <bean id="around" class="com.liu.dynamic.Around"/>2. 切入点  <aop:config>       <aop:pointcut id="pc" expression="..."/>  </aop:config>3. 组装  <aop:advisor advice-ref="around" pointcut-ref="pc"/>
  • 基于注解进行AOP编程

    原始对象的创建步骤不变

    通过切面类完成 额外功能、切入点、组装切面

    ​ @Aspect 切面类

    ​ 定义额外功能 @Around

    ​ 定义切入点 @Around(“execution(* login(…))”)

package com.liu.aspect;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class MyAspect {    @Around("execution(* login(..))") //不需要再implements MethodInterceptor    //around方法相当于invoke 参数变为ProceedingJoinPoint    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {        System.out.println("---Aspect Log---");        Object ret = joinPoint.proceed();        return ret;    }}

​ 在配置文件中配置,创建切面类并告知Spring使用注解进行AOP开发

<bean id="around" class="com.liu.aspect.MyAspect"/><!--告知Spring现在基于注解进行AOP开发-->   <aop:aspectj-autoproxy />

细节

切入点复用

​ 在切面类中定义一个函数,加上@PointCut注解,通过这种方式抽取出切入点表达式,利于后续维护。

@Aspect
public class MyAspect {

    //提取冗余的切入点表达式 
  	//对于具有相同切入点的around、around1方法,后续只需修改该函数的注解即可
    @Pointcut("execution(* login(..))")
    public void myPointCut(){

    }

    @Around(value = "myPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---Aspect Log---");
        Object ret = joinPoint.proceed();
        return ret;
    }

    @Around(value = "myPointCut()")
    public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---Aspect Tx---");
        Object ret = joinPoint.proceed();
        return ret;
    }
}

动态代理的创建方式

AOP底层实现 
1. JDK 通过实现接口 创建代理对象
2. Cglib 通过继承父类 创建代理对象
  • 默认情况下,AOP编程底层应用的是JDK方式创建动态代理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unmYDV8Z-1620665224828)(D:\截图\image-20210408132006376.png)]

  • 基于注解AOP开发,让Spring使用Cglib方式创建动态代理
<aop:aspectj-autoproxy proxy-target-class="true"/>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yPfwZOdq-1620665224828)(D:\截图\image-20210408132249317.png)]

  • 传统(非注解)AOP开发,改为Cglib方式创建动态代理
<aop:config proxy-target-class="true">	...</aop:config>

AOP开发中的坑

当原始类中存在不同方法间的互相调用时,this.XXX方法(普通调用)不会被加上额外功能

package com.liu.aspect;import com.liu.Log;import com.liu.proxy.User;public class UserServiceImpl implements UserService {    public void register(User user) {        System.out.println("UserServiceImpl.register 业务运算 + DAO调用");        //同一个类中,不同业务方法之间相互调用        this.login("liush", "123456");    }    public boolean login(String name, String password) {        System.out.println("UserServiceImpl.login 业务运算 + DAO调用");        return true;    }}
测试类中调用register方法,结果为---Aspect Log------Aspect Tx---UserServiceImpl.register 业务运算 + DAO调用UserServiceImpl.login 业务运算 + DAO调用* this.login没有被加上额外功能因为this指向的是原始对象UserServiceImpl,此时调用的是原始对象的login方法,即没有加上额外功能的核心方法* 那么如何解决?通过工厂的getBean()才能拿到代理对象	工厂又是重量级资源,应该只创建一次,那么就要从测试类中拿到工厂# 此时原始类需要实现ApplicationContextAware接口
package com.liu.aspect;import com.liu.Log;import com.liu.proxy.User;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;public class UserServiceImpl implements UserService, ApplicationContextAware {  	//将工厂设置为一个成员属性    private ApplicationContext context;    public void register(User user) {        System.out.println("UserServiceImpl.register 业务运算 + DAO调用");				      	//还是通过工厂来调用代理对象,就可以解决        UserService userService = (UserService) context.getBean("userService");        userService.login("liush", "234234");        //同一个类中,不同业务方法之间相互调用//        this.login("liush", "123456");    }    public boolean login(String name, String password) {        System.out.println("UserServiceImpl.login 业务运算 + DAO调用");        return true;    }    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        this.context = applicationContext;    }}

AOP总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NJZBGJCh-1620665224828)(D:\截图\image-20210408224256347.png)]

AOP编程概念

实际上就是Spring动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护,日后只需修改代理类

AOP编程开发步骤

原始对象 ==> 额外功能 ==> 切入点 ==> 组装切面

底层实现

1. JDK动态代理(默认)  通过接口创建代理的实现类Proxy.newProxyInstance(classLoader, interfaces, invocationhandler);2. Cglib动态代理  通过继承父类创建的代理类Enhancer enhancer = new Enhancer();enhancer.setClassLoader();enhancer.setSuperclass();MethodInterceptor interceptor = new MethodInterceptor() {     ...};enhancer.setCallback(interceptor);Object proxy = enhancer.create();如果想切换为Cglib,在配置文件中的<aop:config>、<aop:aspect-autoproxy>标签添加proxy-target-class="true"即可# 加工原始类从而创建代理对象 底层还依附于BeanPostProcessor

编程方式

创建完原始对象后1. 传统方式实现MethodInterceptor接口,并实现其中的invoke(MethodInvocation)方法配置文件中	<aop:config>		<aop:pointcut id="" expression=""/>		<aop:advisor pointcut-ref="" advice-ref="MethodInterceptor的实现类"/>	</aop:config>	切入点表达式	方法 * login(..)	类 *..UserServiceImpl.*(..)	包 *.com.liu..*.*(..)切入点函数	execution within args @annotation2. 基于注解方式切面类完成额外功能、切入点和组装切面	@Aspect	public class MyAspect{		@PoinCut()		public void myPoinCut(){}				@Around		public Object around(ProceedingJoinPoint joinpoint){			-----加入额外功能-----			Object ret = jounPoint.proceed();					return ret;		}	}配置文件中	<bean id="MyAspect" class="" />		<aop:aspect-autoproxy />

持久层整合

Spring框架为什么要与持久层整合?

  1. JavaEE开发需要持久层进行对数据库的访问
  2. JDBC、Hibernate、MyBatis进行持久层开发过程中都存在大量的冗余代码
  3. Spring基于模板设计模式对这些持久层技术进行了封装

Spring能与哪些持久层技术进行整合?

  1. JDBC ——JDBCTemplate
  2. Hibernate(JPA) ——HibernateTemplate
  3. MyBatis ——SqlSessionFactoryBean、MapperScannerConfigure

Spring整合MyBatis

对MyBatis中的不足进行改进

MyBatis开发步骤回顾

1. 搭建环境(pom.xml中引入依赖、建立全局配置文件、映射文件)
2. 实体
3. 实体别名
4. 建表
5. 创建DAO接口
6. 编写Mapper文件
7. 注册Mapper文件
8. MyBatis API调用

搭建环境

1. pom.xml文件中引入依赖
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

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>

    <typeAliases></typeAliases>

    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatis?useSSL=false" />
                <property name="username" value="root" />
                <property name="password" value="ls2000818" />
            </dataSource>
        </environment>
    </environments>

    <mappers></mappers>
</configuration>

3. 建立Mapper映射文件
<?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="">
</mapper>

实体类

package com.liu.mybatis;

import java.io.Serializable;
                            //序列化:便于存储(完整性)、便于传输(可传递性)
public class User implements Serializable {
    private Integer id;
    private String name;
    private String password;

    public User() {
    }

    public User(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

实体类别名

全局配置文件config.xml中
<typeAliases>
    <typeAlias type="com.liu.mybatis.User" alias="user"></typeAlias>
</typeAliases>

数据库建表

use mybatis;
create table t_users(
		id int primary key auto_increment,
    name varchar(12),
    password varchar(12)
);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toOQETFH-1620665224829)(D:\截图\image-20210411162347392.png)]

创建DAO接口

package com.liu.mybatis;

public interface UserDAO {
    public void save(User user);
}

配置Mapper.xml文件

​ 添加namespace并编写mysql语句

<?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.liu.mybatis.UserDAO">
    <insert id="save" parameterType="user">
        insert into t_users(name,password) values (#{name},#{password})
    </insert>

</mapper>

注册Mapper.xml文件

在全局配置文件Config.xml中<mappers>    <mapper resource="UserDAOMapper.xml" /></mappers>

API调用

package com.liu.mybatis;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.InputStream;public class TestMyBatis {    public static void main(String[] args) throws IOException {        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);        SqlSession session = sqlSessionFactory.openSession();        UserDAO userDAO = session.getMapper(UserDAO.class);        User user = new User();        user.setName("liush");        user.setPassword("123456");        userDAO.save(user);        session.commit();    }}

MyBatis在开发过程中存在的问题

配置繁琐 代码冗余

实体别名注册Mapper文件API调用# 以上三个步骤,在实体类、Mapper文件多了之后需要写大量重复代码

Spring与MyBatis整合思路分析

两个核心

  • 创建SqlSessionFactory 通过SqlSessionFactoryBean完成
  • 创建DAO对象 通过MapperScannerConfigure完成
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);# 封装为SqlSessionFactoryBean,用于创建SqlSessionFactory<bean id="dataSource" class="" /><bean id="ssfb" class="...SqlSessionFactory">	<!-- 通过属性的注入,替代mybatis-config.xml -->	<property name="dataSource" ref="dataSource" ... />	<property name="typeAliasesPackage" ... />   ===> 实体别名		指定实体所在的包名,Spring会自动创建别名  User=>User	<property name="mapperLocations" ... />      ===> 注册Mapper文件		通配设置 *Mapper.xml</bean>SqlSession session = sqlSessionFactory.openSession();UserDAO mapper = session.getMapper(UserDAO.class);# 封装为MapperScannerConfigurer<bean id="scanner" class="...MapperScannerConfigure">	<property name="sqlSessionFactoryBeanName" value="ssfb"/>	<property name="basePackage" />  ===> 设置DAO接口所在的包</bean># 最终创建DAO对象MapperScannerConfigure所创建的DAO对象,其id值(context.getBean("id"))是接口的首单词首字母小写  UserDAO ===> userDAO  ProductDAO ===> productDAO

开发步骤

搭建环境

<!--在原有基础上添加三个依赖-->

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

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

<!-- 连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.18</version>
</dependency>

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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!-- 创建SqlSessionFactory  SqlSessionFactoryBean -->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.liu.entity"/>
        <property name="mapperLocations">
            <list>
                <value>classpath:com.liu.mapper/*Mapper.xml</value>
            </list>
        </property>
    </bean>

    <!-- 创建DAO对象  MapperScannerConfigure -->
    <bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
        <property name="basePackage" value="com.liu.dao"/>
    </bean>
  
</beans>

编码

# 整合之后,实际开发中经常根据需求写的代码
1. 实体
2. 表
3. 创建DAO接口
4. 实现Mapper文件
  • 实体
package com.liu.entity;
import java.io.Serializable;

public class User implements Serializable {
    private Integer id;
    private String name;
    private String password;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

使用之前建的表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-doXMBWfC-1620665224830)(file://D:/%E6%88%AA%E5%9B%BE/image-20210411162347392.png?lastModify=1618279891)]

  • 创建DAO接口
package com.liu.dao;import com.liu.entity.User;public interface UserDAO {    public void save(User user);}
  • 实现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.liu.dao.UserDAO">    <insert id="save" parameterType="User">        insert into t_users(name,password) values (#{name},#{password})    </insert></mapper>
  • API调用
package com.liu;import com.liu.dao.UserDAO;import com.liu.entity.User;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.lang.annotation.Target;public class TestMyBatisSpring {    /*    * 用于测试Spring与MyBatis的整合    * */    @Test    public void test01(){        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");        UserDAO userDAO = (UserDAO) context.getBean("userDAO");        User user = new User();        user.setName("liushihao");        user.setPassword("987654321");        userDAO.save(user);    }}

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Duwvm3yU-1620665224830)(D:\截图\image-20210413121520516.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Ud92RZV-1620665224831)(D:\截图\image-20210413122106939.png)]

细节分析

  • 整合Spring和Mybatis之后,DAO没有提交事务,但是数据能够插入数据库中
# 核心 Connection ===> tx本质上控制连接对象的是连接池dataSource1. 之前单独使用Mybatis提供的连接池对象	Connection.setAutoCommit(false);  会将true改为false,需要手工提交2. 整合之后使用Druid(C3P0 DBCP)作为连接池	Connection.setAutoCommit(true);  true为默认值,保持自动提交事务 一条sql 自动提交	注意:在实际应用中,为了保证自主操作的完整性(多条sql一起成功,一起失败),我们还是使用手工提交,后续通过Spring的事务控制来解决。

Spring的事务处理

1. 概念
保证业务操作完整性的一种数据库机制

2. 事务的4个特点:ACID
Atomicity:原子性
Consistency:一致性
Isolation:隔离性
Durability:持久性

如何控制事务

1. JDBC  核心 Connection
		开启手动提交:Connection.setAutoCommit(false);
		Connection.commit();
		Connection.rollback();
		
2. MyBatis
		MyBatis会自动开启事务
		sqlSession.commit();
		sqlSession.rollback();
	Session底层封装的是Connection对象
	
# 结论:控制事务的底层都是Connection对象完成的

Spring控制事务开发的分析

事务——额外功能
Spring是通过AOP的方式进行事务开发
  • 原始对象
public class XXXUserServiceImpl{}

1. 原始对象 ===> 原始方法 ===> 核心功能(业务处理 + DAO调用)
2. DAO作为Service的成员变量,通过依赖注入的方式进行赋值
  • 额外功能
1. 实现MethodInterceptor
public Object invoke(MethodInvocation invocation){
		try{
			Connection.setAutoCommit(false);
			Object ret = invocation.proceed();
			Connection.commit();
		}catch(Exception e){
    	Connection.rollback();
		}
		return ret;
}
	
2. 切面类@Aspect
		@Around
		
# 在Spring中,事务封装为org.springframework.jdbc.datasource.DataSourceTransactionManager
需要为其注入DataSource(注入Connection)
  • 切入点
注解@Transactional

1. 加在类上:类种所有方法都会加上事务功能
2. 加在方法上,该方法加上事务功能
  • 组装切面
组装切入点和切面

配置文件中的标签
<tx:annotation-driven transaction-manager=""/>  自动扫描@Transactional

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BcEqS4J7-1620665224831)(D:\截图\image-20210414212935183.png)]

编码

  • 搭建环境
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.1.14.RELEASE</version>
</dependency>
  • 编码
1. 原始对象
<bean id="userService" class="com.liu.service.UserServiceImpl">
    <!-- 使用Spring与MyBatis整合时的scanner为我们创建的DAO对象 -->
    <property name="userDAO" ref="userDAO"/>
</bean>

2. 额外功能(事务)
<!-- DataSourceTransactionManager -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 使用Spring与MyBatis整合时的dataSource进行注入 -->
    <property name="dataSource" ref="dataSource"/>
</bean>

3. 切入点
@Transactional
public class UserServiceImpl implements UserService {
	private UserDAO userDAO;

4. 组装切面
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
  • 细节
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>
进行动态代理底层实现的切换
	默认false  JDK
	true  Cglib

事务属性(Transaction Attribute)

什么是事务属性

属性:描述物体特征的一系列值

事务属性:描述事务特征的一系列值

  • 隔离属性
  • 传播属性
  • 只读属性
  • 超时属性
  • 异常属性

如何添加事务属性

在事务注解中添加属性
@Transactional(isolation=, propagation=, readOnly=, timeOut=, rollbackFor=, noRollbackFor=,)

事务属性详解

隔离属性Isolation

描述了事务解决并发问题的特征

1. 什么是并发
		多个事务(用户)在同一时间访问操作了相同的数据
			同一时间:不是精确的同一时刻,可能有0.000几秒的微小差异

2. 并发会产生哪些问题
		脏读
		不可重复读
		幻影

3. 并发问题如何解决
		通过在隔离属性中设置不同的值来解决
事务并发产生的问题
  • 脏读
# 一个事务读取了另一个事务中还没有提交的数据,假如另一个事务rollback,会产生数据不一致的问题

解决方案
@Transactional(isolation = Isolation.READ_COMMITTED)
  • 不可重复读
# 一个事务中,多次读相同的数据,但是读取的结果不一样(被其他事务修改)
注意: 1.不是脏读  2.一个事务中

解决方案
@Transactional(isolation = Isolation.REPEATABLE_READ)
	本质:加上一个行锁(有事务在读的时候其他事务只能读,要改只能等)
  • 幻影读
# 一个事务中,多次对整表进行查询统计,但是结果不一样,产生数据不一致问题

解决方案
@Transactional(isolation = Isolation.SERIALIZABLE)
	本质:加上一个表锁
  • 总结
并发安全程度:SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED
系统运行效率:READ_COMMITTED > REPEATABLE_READ > SERIALIZABLE 
数据库对于隔离属性的支持
隔离属性的值MySQLOracle
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE
Oracle不支持REPEATABLE_READ  如何解决不可重复读?# 采用多版本比对的方式来解决
默认的隔离属性
ISOLATION_DEFAULT:会调用不同数据库所设置的默认隔离属性1. MySQL:REPEATABLE_READ2. Oracle:READ_COMMITTED
  • 查看数据库的默认隔离属性

    MySQL:select @@transaction_isolation

    Oracle:麻烦 多表连接 + 动态视图

隔离属性在实际应用中的建议
# 优先保证效率,推荐使用Spring指定的ISOLATION_DEFAULT,方便实际应用中,并发访问的可能性比较低	必须有海量用户作为前提,即使同时点击,到数据库也不一定是同时如果真遇到并发问题,使用乐观锁,隔离属性是物理锁	Hibernate(JPA):Version	MyBatis:通过拦截器自定义开发

传播属性Propagation

描述了解决事务嵌套问题的特征

存在的问题:一个类的方法中调用了其他方法,两者都有事务,大事务中嵌套了很多小事务,它们彼此影响,最终导致外部的大事务丧失原子性解决:传播属性
传播属性的值及其用法

核心:保证同一时间只有一个事务存在

传播属性的值外部不存在事务外部存在事务用法备注
REQUIRED开启新事务融合到外部事务中@Transactional(propagation = Propagation.REQUIRED)应用在增删改方法
SUPPORTS不开启新事务融合到外部事务中@Transactional(propagation = Propagation.SUPPORTS)应用在查询方法
REQUIRES_NEW开启新事务挂起外部事务,创建新事务@Transactional(propagation = Propagation.REQUIRES_NEW)应用在日志中
NOT_SUPPORTED不开启新事务挂起外部事务@Transactional(propagation = Propagation.NOT_SUPPORTED)极其不常用
NEVER不开启新事务抛出异常@Transactional(propagation = Propagation.NEVER)极其不常用
MANDATORY抛出异常融合到外部事务中@Transactional(propagation = Propagation.MANDATORY)极其不常用
默认的传播属性
REQUIRED是传播属性的默认值# 实际应用建议增删改方法:直接使用默认值REQUIRED查询操作:显示地指定SUPPORTS

其他属性

只读属性readOnly
针对于只进行查询操作的业务方法,可以加入只读属性,提高其运行效率默认值为false使用:@Transactional(readOnly = true)
超时属性timeOut
# 指定了事务等待的最长时间1. 为什么事务要进行等待?  当前事务访问数据时,该数据被别的事务进行了加锁处理,那么该事务就必须等待2. 等待时间   单位:秒3. 使用  @Transactional(timeout = 2)4. 超时属性的默认值:-1  含义:最终由对应的数据库来指定  实际应用中很少指定超时属性
异常属性Exception
Spring事务处理过程中1. 默认对于RuntimeException及其子类,采用的是回滚的策略		 默认对于Exception及其子类,采用的是提交的策略2. 使用  @Transactional(rollbackFor = {java.lang.Exception.class}, noRollbckFor = {...})  实际应用中一般使用默认值,且异常使用RuntimeException及其子类

事务属性常见配置总结

1. 隔离属性   默认值2. 传播属性   增删改:Required(默认值)   查询:Supports3. 只读属性   增删改:readOnly(false)   查询readOnly(true)4. 超时属性   默认值 -15. 异常属性   默认值实际应用:增删改操作  @Transactional查询操作  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

基于标签的事务配置方式(事务开发的第二种形式,了解)

# 基于注解 @Transactional的事务配置1. 原始对象<bean id="userService" class="com.liu.service.UserServiceImpl">    <!-- 使用Spring与MyBatis整合时的scanner为我们创建的DAO对象 -->    <property name="userDAO" ref="userDAO"/></bean>2. 额外功能(事务)<!-- Spring中的DataSourceTransactionManager封装了事务处理的额外功能 --><bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    <!-- 使用Spring与MyBatis整合时的dataSource进行注入 -->    <property name="dataSource" ref="dataSource"/></bean>3. 切入点@Transactionalpublic class UserServiceImpl implements UserService {	private UserDAO userDAO;4. 组装切面<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/># 基于标签的事务配置方式与基于注解方式的差异在于第3、4步1. 原始对象2. 额外功能3. 事务属性  <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager" >    <tx:attributes>      <tx:method name="register" isolation="", propagation=""></tx:method>    </tx:attributes>  </tx:advice>4. 组装切面<aop:config>	<aop:pointcut id="pc" expression="execution(* com.liu.service.UserServiceImpl.register(..))"></aop:pointcut>	<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor></aop:config>
  • 实际应用
普通的标签事务配置存在冗余,给新的方法添加事务就得新增一个tx:method标签1. 实际应用中将进行增删改的方法都以modify开头,然后在标签中使用通配符   查询方法命名无所谓2. 实际应用中组装时expression指定为包名1. 原始对象2. 额外功能3. 事务属性  <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager" >    <tx:attributes>      <tx:method name="modify*"></tx:method>      <tx:method name="*"  propagation="SUPPORTS",read-only="true"></tx:method>    </tx:attributes>  </tx:advice>4. 组装切面<aop:config>	<aop:pointcut id="pc" expression="execution(* com.liu.service..*.*(..))"></aop:pointcut>	<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor></aop:config>

Spring整合MVC

环境搭建

  1. 在项目中添加新的模块,以webapp构建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNf536zf-1620665224832)(D:\截图\image-20210422110929801.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OkI3JRGp-1620665224833)(D:\截图\image-20210422111021217.png)]

BUID SUCCESS构建成功

  1. 在src/main下创建java、resource包,并设置为source root
image-20210422111333213 image-20210422111511126
  1. 配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>com.liu</groupId>  <artifactId>Spring5-04-Web</artifactId>  <version>1.0-SNAPSHOT</version>  <packaging>war</packaging>  <name>Spring5-04-Web Maven Webapp</name>  <!-- FIXME change it to the project's website -->  <url>http://www.example.com</url>  <properties>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    <maven.compiler.source>1.8</maven.compiler.source>    <maven.compiler.target>1.8</maven.compiler.target>  </properties>  <dependencies>    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->    <dependency>      <groupId>javax.servlet</groupId>      <artifactId>javax.servlet-api</artifactId>      <version>3.1.0</version>      <scope>provided</scope>    </dependency>    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->    <dependency>      <groupId>javax.servlet.jsp</groupId>      <artifactId>javax.servlet.jsp-api</artifactId>      <version>2.3.1</version>      <scope>provided</scope>    </dependency>    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl -->    <dependency>      <groupId>javax.servlet</groupId>      <artifactId>jstl</artifactId>      <version>1.2</version>    </dependency>    <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-web</artifactId>      <version>5.1.14.RELEASE</version>    </dependency>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-core</artifactId>      <version>5.1.14.RELEASE</version>    </dependency>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-beans</artifactId>      <version>5.1.14.RELEASE</version>    </dependency>    <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>RELEASE</version>      <scope>test</scope>    </dependency>    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-context</artifactId>      <version>5.1.4.RELEASE</version>    </dependency>    <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis</artifactId>      <version>3.4.6</version>    </dependency>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-jdbc</artifactId>      <version>5.1.14.RELEASE</version>    </dependency>    <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis-spring</artifactId>      <version>2.0.2</version>    </dependency>    <dependency>      <groupId>com.alibaba</groupId>      <artifactId>druid</artifactId>      <version>1.1.18</version>    </dependency>    <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-tx</artifactId>      <version>5.1.14.RELEASE</version>    </dependency>    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->    <dependency>      <!-- 日志门面,引入后可以将默认的logback、log4j2摒弃,使之可以支持log4j-->      <groupId>org.slf4j</groupId>      <artifactId>slf4j-log4j12</artifactId>      <version>1.7.25</version>    </dependency>    <!-- https://mvnrepository.com/artifact/log4j/log4j -->    <dependency>      <groupId>log4j</groupId>      <artifactId>log4j</artifactId>      <version>1.2.17</version>    </dependency>    <dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>      <version>5.1.48</version>    </dependency>  </dependencies>  <build>  </build></project>

为什么要整合MVC框架

# MVC框架1. 提供了控制器(Controller)    用于调用Service2. 提供请求响应的处理3. 接收请求参数4. 控制程序的运行流程5. 视图解析(JSP JSON Freemarker Thymeleaf)

Spring可以整合哪些MVC框架

1. struts12. webwork3. jsf4. struts25. springMVC  应用广泛

Spring整合MVC框架的核心思路

工厂

工厂-Spring核心1. Web开发过程中如何创建工厂   ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");	使用WebXmlApplicationContext()2. 如何保证工厂(重量级资源)唯一、可以被共用   被共用:Web开发中用于存储对象的作用域 request/session/ServletContext(application)	 把工厂存储在ServletContext这个作用域中 ServletContext.setAttribute("xxx", context);	   唯一:ServletContext对象(只会被创建一次)创建的同时	 运行ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");   如何知道ServletContext对象什么时候创建?	 使用ServletContextListener监听,在ServletContext创建时就会被调用(只会被调用一次),把创建工厂的代码写在ServletContextListener中,则只会被创建一次	 # 总结既要创建工厂,又要保证其唯一、能被共用。1. ServletContextListener(只会被创建一次)中创建工厂保证了唯一2. ServletContext.setAttribute("xxx", context)保证了工厂能被共用Spring将上述两步封装为ContextLoaderListener 1. 创建工厂 2. 把工厂存储在ServletContext中
ContextLoaderListener使用方式在web.xml中<listener>	<listener-class>org.springfamework.web.context.ContextLoaderListener</listener-class></listener><!-- 告诉配置文件的路径 --><context-param>	<param-name>contextConfigLocation</param-name>  <param-value>classpath:applicationContext.xml</param-value></context-param>

控制器

依赖注入:把Service对象注入控制器对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hwZ6Xt9l-1620665224833)(D:\截图\image-20210425110933038.png)]

注解基础

概念及原因

1. 概念   指的是在类或方法上加入特定的注解(@XXX),完成特定功能的开发2. 为什么使用注解编程   代码简洁,开发速度大大提高   Spring2.x引入注解  Spring3.x完善注解  SpringBoot普及推广注解编程

作用

  1. 替换XML这种配置方式,简化配置
@Componentpublic class User{       UserDAO ==> userDAO    }即可实现原本在XML配置文件中需要配置标签来完成的工作<bean id="user" class="com.liu.annotation.User"/>
  1. 替换接口,实现调用双方的契约性
调用者如果不知道被调用者的具体类名和方法名,则需要使用一个接口来建立契约,规定被调用者中必须实现并提供给调用者的方法。	例如之前额外功能中需要实现MethodInterceptor接口,额外功能需要按规定写在invoke方法中,而使用注解可以替换接口,额外功能的方法没有要求,只需要加上@Around注解即可。	更为灵活和方便。

Spring注解的发展历程

1. Spring2.x开始支持注解编程  @Component、@Service、@Scope...   目的:为了在某些情况下简化XML的配置,作为XML开发的有益补充2. Spring3.x  @Configuration、@Bean...   目的:彻底替换XML开发,基于纯注解编程3. Spring4.x  SpringBoot   提倡使用注解编程进行常见的开发

Spring注解开发问题思考

Spring基于注解进行配置后,代码还能否方便维护呢?	基于注解开发,修改时需要直接修改代码。# 在Spring应用注解是,如果对注解配置的内容不满意,可以通过Spring配置文件进行覆盖

Spring基础注解(Spring2.x)

# 这个阶段的注解,仅仅是简化XML的配置,并不能完全替代XML

对象创建相关注解

搭建开发环境

<context:component-scan base-package="com.liu"/>作用:让Spring框架在指定的包及其子包中扫描对应的注解,使其生效。

@Component

作用:替换原有Spring配置文件中的<bean id="..." class="...">标签1. 通过反射获取class——类的全限定类名2. 默认id值为首单词首字母小写   UserDAO ==> userDAO
细节
  • 如何显式地指定工厂创建对象的id值
@Component("u")
  • Spring配置文件如何覆盖注解配置内容
applicationContext.xml<bean id="u" class="com.liu.bean.User">...</bean>id、class的值要和注解中设置的保持一致,才能进行覆盖
@Component的衍生注解
@Repository@Service@Controller这些衍生注解本质上就是@Component,作用、细节都是完全一样的# 提供衍生注解的目的:更加准确地表达某个类型的作用@Repository用在DAO  @Service用在Service  @Controller用在Controller* 注意:Spring整合MyBatis时不使用@Repository、@Component,因为DAO是UserDAOMapper.xml动态创建的

@Scope

作用:控制简单对象的创建次数@Scope("singleton(默认)/prototype")替换<bean id="" class="" scope="singleton/prototype"/>

@Lazy

作用:延迟创建单例对象@Lazy替换<bean id="" class="" lazy="true"/>

生命周期方法相关注解

1. 初始化相关方法  @PostConstruct   实现InitializingBean接口 或 <bean init-method="" />2. 销毁方法  @PreDestroy   实现DisposableBean 或 <bean destroy-method="" />   注意1. 以上两个注解并不是Spring提供的,是JSR(JavaEE规范)520提供的2. 再一次验证,注解替换了接口,实现了契约性

注入相关注解

用户自定义类型注入@Autowired

xml配置方式是创建userDAO然后再在userService的bean中通过<property>注入,实际上是调用set方法,所以@Autowired加在set方法上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nL650PbM-1620665224834)(D:\截图\image-20210427111756065.png)]

# 细节1. Autowired注解基于类型进行注入【推荐】   基于类型注入指的是注入对象的类型,必须与目标成员变量类型相同或是其子类(实现类)。保证了不会注入其他的加了@Repository注解的类型2. 如果是基于id值进行注入(了解即可) 需要@Autowired和@Qualifier配合使用  @Autowired  @Qualifier("userDAOImpl")  public void setUserDAO(UserDAO userDAO) {      this.userDAO = userDAO;  }3. @Autowired注解放置位置   a)放置在对应成员变量的set方法上  # b)直接把注解放在成员变量上,Spring此时会通过反射直接对成员变量进行注入【推荐】4. JavaEE规范中类似功能的注解   JSR520 @Resource(name="userDAOImpl") 基于id进行注入    与@Autowired @Qualifier("userDAOImpl")配合使用效果一样   注意:    如果应用@Resource注解时没有指定name或者name没有配对成功,那么会按照类型进行注入      JSR330 @Inject 作用和@Autowired完全一致 基于类型进行注入 应用在EJB3.0中    需要引入依赖才能使用    <dependency>    	<groupId>javax.inject</groupId>    	<artifactId>javax.inject</artifactId>    	<version>1</version>    </dependency>

JDK类型注入@Value

  • 开发步骤
类似之前的配置文件参数化1. 创建xxx.properties文件   在其中设置id=...  name=...2. 配置Spring的工厂来读取这个参数配置文件   在applicationContext.xml中添加   	<context:property-placeholder location="classpath:init.properties"/>3. 在类型的属性上加上@Value注解   @Value("${key}")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nqy0Q65T-1620665224834)(D:\截图\image-20210427155408774.png)]

  • @PropertySource
作用:用于替换Spring配置文件中的<context:property-placeholder location="..."/># @PropertySource("...")加在类型上,替换开发步骤中的第二步
  • @Value注解使用细节
1. @Value注解不能使用在静态成员变量上2. @Value + @Properties这种方式不能注入集合类型   Spring提供了新的配置形式 YAML YML(SpringBoot)

注解扫描详解

<context:component-scan base-package="com.liu"/>默认扫描策略:扫描当前指定的包及其子包 # 不够灵活
  1. 排除方式
排除不想进行操作的类型<context:component-scan base-package="com.liu">   <context:exclude-filter type="" expression="" />   	type: assignable  排除特定类型   				annotation   排除特定的注解					aspectj   				regex          				custom</context:component-scan>
  1. 包含方式
<context:component-scan base-package="com.liu" use-default-filters="false">   <context:include-filter type="" expression="" /></context:component-scan>和排除方式的区别1. use-defalt-filters="false"	作用:让Spring默认的注解扫描方式失效2. <context:include-filter type="" expression="" />	type: assignable  包含特定类型   				annotation   包含特定的注解					aspectj   				regex          				custom	  自定义策略框架底层开发

注解开发的思考

  • 配置互通
Spring基于注解、配置文件的配置是可以互通的@Repositorypublic class UserDAOImpl{}public class UserServiceImpl{  public UserDAO userDAO;  set get}<bean id="userService" class="com.liu.UserServiceImpl">  <property name="userDAO" ref="userDAOImpl"/></bean>
  • 什么情况下使用注解 什么情况下使用配置文件
@Component 替换 <bean>标签1. 基础注解(@Component @Autowired @Value)只能用于程序员开发的类型		User、UserService、UserDAO2. 使用其他非程序员开发的类型时,使用配置文件<bean>进行配置		SqlSessionFactoryBean、MapperScannerConfigure

Spring高级注解(Spring3.x及以上)

# Spring在3.x提供新的注解,用于替换XML配置文件。

配置Bean-@Configuration

@Configurationpublic class AppConfig{  }加上@Configuration注解之后,AppConfig这个类就成了配置Bean

问题

  1. 配置Bean替换了XML文件的什么内容?
    原来由applicationContext.xml内完成的创建bean、属性(依赖)注入、注解扫描配置等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiCwxqCn-1620665224835)(D:\截图\image-20210504133317554.png)]

  1. 原来的ClassPathXmlApplication替换为AnnotationConfigApplicationContext
1. 创建工厂代码   ApplicationContext context = new AnnotationConfigApplicationContext();2. 指定配置文件 两种方式   1)指定配置Bean的Class   	ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class);   2)指定配置Bean的所在路径   	ApplicationContext context = new AnnotationConfigApplicationContext("com.liu");

相关细节

  1. 基于注解开发使用日志
    不能集成Log4j ?

    后续都使用logback

pom.xml文件中引入相关jar包   	<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->    <dependency>        <groupId>org.slf4j</groupId>        <artifactId>slf4j-api</artifactId>        <version>1.7.25</version>    </dependency>    <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->    <dependency>        <groupId>org.slf4j</groupId>        <artifactId>jcl-over-slf4j</artifactId>        <version>1.7.25</version>    </dependency>    <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->    <dependency>        <groupId>ch.qos.logback</groupId>        <artifactId>logback-classic</artifactId>        <version>1.2.3</version>        <scope>test</scope>    </dependency>    <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->    <dependency>        <groupId>ch.qos.logback</groupId>        <artifactId>logback-core</artifactId>        <version>1.2.3</version>    </dependency>    <!-- https://mvnrepository.com/artifact/org.logback-extensions/logback-ext-spring -->    <dependency>        <groupId>org.logback-extensions</groupId>        <artifactId>logback-ext-spring</artifactId>        <version>0.1.4</version>    </dependency>    
  1. @Configuration注解的本质
本质:是@Component注解的衍生注解
image-20210504203635176

@Bean注解

@Bean注解在配置Bean内进行使用,等同于XML配置文件中的<bean>标签

基本使用

对象的创建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUeC2T2j-1620665224835)(D:\截图\image-20210504204810226.png)]

1. 简单对象
   能够直接通过new的方式进行创建的对象
   User、UserService、UserDAO
2. 复杂对象
   不能直接通过new的方式进行创建的对象
   Connection、SqlSessionFactory
  • 注意事项

如果使用FactoryBean的方式封装了创建复杂对象的过程,可以在AppConfig的@Bean中对其进行整合

实际开发中在@Bean中直接创建即可,整合主要运用在遗留系统当中

@Bean
public Connection conn1(){
    Connection conn = null;
    try {
        ConnectionFactoryBean connectionFactoryBean = new ConnectionFactoryBean();
        conn = connectionFactoryBean.getObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return conn;
}
自定义id值
@Bean("u")
控制对象的创建次数
@Bean
@Scope("prototype/singleton") 默认为singleton

注入

用户自定义类型
@Configuration
public class AppConfig1 {
    @Bean
    public UserDAO userDAO(){
        return new UserDAOImpl();
    }

    @Bean
    public UserService userService(UserDAO userDAO){
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
}
//简化写法
也可以在userService.setUserDAO()中直接调用userDAO方法
    @Bean
    public UserService userService(){
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO());
        return userService;
    }
JDK类型
手动setXXX
  
@Bean
public Customer customer(){
    Customer customer = new Customer();
    customer.setId(1);
    customer.setName("liu");
    return customer;
}
  • 细节分析
# 如果直接在代码中手动setXXX,存在耦合问题,此时可以使用之前的@PropertySource和@Value注解,配合参数配置文件

@Configuration
@PropertySource("classpath:/init.properties")
public class AppConfig1 {
  @Value("${id}")
  private Integer id;
  @Value("${name}")
  private String name;

@ComponentScan注解

1. 在配置Bean中使用,等同于原来xml文件中的<context:component-scan>标签
2. 目的:进行相关注解的扫描(@Component、@Value...)

@Configuration
@ComponentScan(basePackages = "com.liu.scan")
public class AppConfig_Scan {
}

排除和包含的使用

  • 排除
1. xml配置文件方式
<context:component-scan base-package="com.liu">
   <context:exclude-filter type="" expression="" />
</context:component-scan>

2. 注解方式
@Configuration
@ComponentScan(basePackages = "com.liu.scan",
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class}),
                @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "*..User1")})
public class AppConfig_Scan {
}

type = FilterType.ANNOTATION          value
  							 .ASSIGNABLE_TYPE     value
  							 .ASPECTJ             pattern
  							 .REGEX               pattern
  							 .CUSTOM              value  
  • 包含
<context:component-scan base-package="com.liu" use-default-filters="false">	<context:include-filter type="" expression="" /></context:component-scan>  @ComponentScan(basePackages = "com.liu.scan", useDefaultFilters = false,        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class})})public class AppConfig_Scan {}

Spring工厂创建对象的多种配置方式

多种配置的应用场景

主要使用的是@Component和@Bean注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3EdNjkd-1620665224835)(D:\截图\image-20210507212002944.png)]

配置优先级

@Component及其衍生注解 < @Bean < 配置文件Bean标签
# 优先级高的可以覆盖优先级低的,id值必须保持一致

将配置文件与注解整合,在配置bean上添加
@ImportResource("applicationContext.xml")
  • 解决基于注解进行配置的耦合问题
如对原来@Configuration中的@Bean不满意
@Bean
public UserDAO userDAO(){
   return new UserDAOImpl();
}
# 直接创建新的UserDAOImpl类型
然后整合配置Bean和配置文件,在applicationContext.xml对其进行覆盖

那么此时需要修改配置Bean的源码(加上@ImportResource),也存在耦合
# 可以直接创建新的配置Bean,然后在创建工厂时引用多个
ApplicationContext context = new AnnotationConfigAoolicationContext(AppConfig1.class, AppConfig2.class);

整合多个配置信息

  • 为什么会有多个配置信息
拆分多个配置Bean的开发,是一种模块化开发的形式,也体现面向对象、各司其职的设计思想
  • 多配置信息整合的方式
1. 多个配置Bean的整合2. 配置Bean与@Component相关注解的整合3. 配置Bean与SpringXML配置文件的整合
  • 整合多种配置需要关注的要点
1. 如何使多配置信息汇总成为一个整体2. 如何实现跨配置的注入

多个配置Bean的整合

多配置的信息汇总
1. base-package进行多个配置Bean的整合   在创建工厂时直接扫描整个包,进行整合    ApplicationContext context = new AnnotationConfigApplicationContext("com.liu.config");2. 将一个配置Bean作为主配置,引入其他的配置Bean   @Configuration   @Import(AppConfig2.class)   public class AppConfig1 {   3. 创建工厂时指定多个配置Bean的Class对象(少用)   AnnotationConfigApplicationContext(AppConfig1.class, AppConfig2.class);
跨配置进行注入

在应用配置Bean的过程中,不管使用哪种方式进行配置信息的汇总,注入都是通过成员变量上添加@Autowired注解完成

@Configuration@Import(AppConfig2.class)public class AppConfig1 {    @Autowired    private UserDAO userDAO;    @Bean    public UserService userService(){        UserServiceImpl userService = new UserServiceImpl();        userService.setUserDAO(userDAO);        return userService;    }}@Configurationpublic class AppConfig2 {    @Bean    public UserDAO userDAO(){        return new UserDAOImpl();    }}

配置Bean与@Component相关注解的整合

在配置Bean中添加**@ComponentScan(basePackages = “com.liu.xxx”)**即可

@Component(@Repository)
public class UserDAOImpl implements UserDAO{
  
}

@Configuration
@ComponentScan(basePackages = "com.liu.xxx")
public class AppConfig{
  @Autowired
  private UserDAO userDAO;
  
  @Bean
  public UserService userService(){
    UserServiceImpl userService = new UserServiceImpl();
    userService.setUserDAO(userDAO);
    return userService;
  }
}

配置Bean与配置文件整合

应用场景
1. 遗留系统的整合
2. 配置覆盖

配置Bean引入@ImportResource("applicationContext.xml")即可

配置Bean的底层实现原理

Spring在配置Bean中加入@Configuration注解后,底层就会通过Cglib的方式,进行对象相关的配置、处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nW3X3MNk-1620665224836)(D:\截图\image-20210509172846456.png)]

四维一体的开发思想

什么是四维一体
Spring在开发一个功能有四种方式,虽然开发方式不同,但最终效果是一样的
# 且底层使用的都是PropertySourcesPlaceholderConfigurer这个类
1. 基于Schema
2. 基于特定功能注解
3. 基于原始bean标签
4. 基于@Bean注解
四维一体开发案例
案例:小配置文件进行注入
1. <context:property-placeholder location="classpath:four.properties"/>
2. @PropertySource 【推荐】
3. <bean id="" class="PropertySourcePlaceholderConfigure"/>
4. @Bean 【推荐】

纯注解AOP编程

搭建环境

1. 应用配置Bean
2. 注解扫描
@Configuration
@ComponentScan(basePackages = "com.liu.aop")
public class AppConfig {
}

开发步骤

1. 原始对象
   @Service(@Component)
   public class UserServiceImpl implements UserService{
   
   }
2. 创建切面类(额外功能、切入点、组装切面)
   @Aspect
   @Component
   public class MyAspect {
        @Around("execution(* login(..))") 
				public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---Aspect Log---");
        Object ret = joinPoint.proceed();
        return ret;
    }
}
   
3. @EnableApectjAutoProxy加在配置Bean上
		替换原Spring配置文件中<aop:aspectj-autoproxy/>

细节分析

1. 代理创建方式的切换 JDK Cglib
   原来:<aop:aspectj-autoproxy proxy-target-class="true/false" />
   @EnableAspectjAutoProxy(proxyTargetClasss = true)
   
2. SpringBoot AOP的开发方式
   程序员只用完成原始对象创建、切面类(额外功能、切入点、组装切面)两步
   第三步SpringBoot已经设置好了
	Spring AOP代理默认实现是JDK,SpringBoot默认的是Cglib

纯注解Spring整合MyBatis

基础配置(放在配置Bean当中)

1. 连接池
  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;allowPublicKeyRetrieval=true"/>
      <property name="username" value="root"/>
      <property name="password" value="123456"/>
  </bean>

@Bean
public DataSource dataSource(){
   DruidDataSource dataSource = new DruidDataSource();
   dataSource.setDriverClassName("");
   dataSource.setUrl("");
   ...
   return dataSource;
}
    
2. SqlSessionFactoryBean
  <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource"/>
      <property name="typeAliasesPackage" value="com.liu.entity"/>
      <property name="mapperLocations">
          <list>
              <value>classpath:com.liu.mapper/*Mapper.xml</value>
          </list>
      </property>
  </bean>

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
   SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
   sqlSessionFactoryBean.setDataSource(dataSource);
   sqlSessionFactoryBean.setTypeAliasesPackage("com.liu.mybatis");
   sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserDAOMapper.xml"));
   return sqlSessionFactoryBean;
}

3. 创建DAO对象 MapperScannerConfigure
  <bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
      <property name="basePackage" value="com.liu.dao"/>
  </bean>

Spring提供了专属的注解@MapperScan(),会自动扫描SqlSessionFactoryBean,只用设置basePackages
配置Bean添加@MapperScan(basePackages="com.liu.dao")
  • 编码
1. 实体
2. 表
3. DAO接口
4. Mapper文件

MapperLocations编码时通配的写法

//设置Mapper文件的路径
sqlSessionFactoryBean.setMapperLocations(Resource...可变长参数);

sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserDAOMapper.xml"));

实际应用中,有一组Mapper.xml文件
//路径通配解析
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("com.liu.mapper/*Mapper.xml");

sqlSessionFactoryBean.setMapperLocations(resources)

配置Bean中数据耦合的问题

dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("123456");
sqlSessionFactoryBean.setTypeAliasesPackage("com.liu.mybatis");
Resource[] resources = resolver.getResources("com.liu.mapper/*Mapper.xml");

过多具体的字符串硬编码在代码中,存在耦合
将这些字符串信息提取到配置文件中 然后进行注入
  
1. mybatis.properties文件
mybatis.driverClassName = com.mysql.jdbc.Driver
mybatis.url = jdbc:mysql://localhost:3306/mybatis?useSSL=false
mybatis.username = root
mybatis.password = 123456
mybatis.typeAliasesPackages = com.liu.mybatis
mybatis.mapperLocations = com.liu.mapper/*Mapper.xml

2. 创建一个实体类来封装配置信息
@Component
@PropertySource("classpath:mybatis.properties")
public class MybatisProperties {
    @Value("${mybatis.driverClassName}")
    private String driverClassName;
    @Value("${mybatis.url}")
    private String url;
    @Value("${mybatis.username}")
    private String username;
    @Value("${mybatis.password}")
    private String password;
    @Value("${mybatis.typeAliasesPackages}")
    private String typeAliasesPackages;
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    getter、setter
}

3. 将封装了配置信息的实体类注入到配置Bean中,然后替换硬编码的具体字符串
   @Autowired
   private MybatisProperties mybatisProperties;
   
   dataSource.setDriverClassName(mybatisProperties.getDriverClassName());
   dataSource.setUrl(mybatisProperties.getUrl());
   dataSource.setUsername(mybatisProperties.getUsername());
   dataSource.setPassword(mybatisProperties.getPassword());
sqlSessionFactoryBean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackages());
   Resource[] resources = resolver.getResources(mybatisProperties.getMapperLocations());

纯注解事务编程

1. 原始对象 XXXService
   <bean id="userService" class="com.liu.service.UserServiceImpl">
     <property name="userDAO" ref="userDAO"/>
   </bean>
   
   @Service
   public class UserServiceImpl implements UserService{
      @Autowired
      private UserDAO userDAO;
   }
   
2. 额外(事务)功能
  <!-- DataSourceTransactionManager -->
  <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
  </bean>
  
  @Bean
  public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
     DataSourceTransactionManager dstm = new DataSourceTransactionManager();
     dstm.setDataSource(dataSource);
     reutrn dstm;
  }

3. 事务属性(切入点)
@Transactional
@Service
public class UserServiceImpl implements UserService {
	@Autowired
	private UserDAO userDAO;

4. 基于Schema的事务配置
   <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
   
   配置Bean加上@EnableTransactionManagement
Spring整合MyBatis ---> DAO
事务基于注解 ---> Service

Spring框架中YML的使用

YML(YAML)是一种新形式的配置文件,比XML简单,比Properties强大
  • properties进行配置存在的问题
1. 表达过于繁琐且无法表达数据的内在联系
2. 无法表达对象、集合类型

YML语法简介

1. 定义yml文件
	 xxx.yml  xxx.yaml
	 
2. 语法
   1)基本语法
     key: value
   2)对象  通过缩进表达从属关系
     account:
     	id: 1
     	password: 123456
   3)定义集合
   	 service:
   	 	- 1111
   	  - 2222

Spring整合YML

思路分析

xxx.properties ==> Properties集合 ==> PropertySourcePlaceholderConfigurer ==> 注入给相应成员变量

# 整合思路
解决怎么让Spring读取yml、将读取的yml转换为Properties被PropertySourcePlaceholderConfigurer读取

使用YamlPropertiesFactoryBean

1. 准备yml配置文件
   init.yml
   name: liush
   password: 123456
   
2. 读取yml 转换成Properties
   YamlPropertiesFactoryBean.setResources(new ClassPathResource(yml文件的路径));
   YamlPropertiesFactoryBean.getObject() ==> Properties
   
3. 将Properties设置给PropertySourcePlaceholderConfigurer 【核心】
   PropertySourcePlacholderConfigurer.setProperties();
   
4. 类中@Value注解进行注入

开发步骤

  • 环境搭建
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.25</version>
</dependency>
最低版本 1.18
  • 编码
1. 准备yml配置文件
#name: liush
#password: 123456
account:
  name: lius
  password: 1234567

2. 配置Bean中完成 YAML读取、PropertySourcePlaceholderConfigurer的创建
public PropertySourcesPlaceholderConfigurer configurer(){
        YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
        yamlPropertiesFactoryBean.setResources(new ClassPathResource("init.yml"));
        Properties properties = yamlPropertiesFactoryBean.getObject();

        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setProperties(properties);
        return configurer;
    }
  
3. 类加入@Value注解
@Component
public class Account {
    @Value("${account.name}")
    private String name;
    @Value("${account.password}")
    private String password;

Spring与YML集成的问题

1. 集合集成问题
   如果是注入list集合
   list:
   	- 111
   	- 222
   会报错,YamlPropertiesFactoryBean无法解析
解决:使用SpringEL表达式,先拿整个字符串再拆分
   list: 111,222
   @Value("#{'${list}'.split(',')}")
   
2. 对象类型的YAML进行配置时过于繁琐
@Value("${account.name}")
private String name;
@Value("${account.password}")
private String password;
   Spring目前无法解决

# SpringBoot中通过@ConfigurationProperties注解可以解决以上两个问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值