spring基础(四万字爆更)

Spring

  • 简化开发
    • IOC(反转控制)
    • AOP(面向切面编程)
      • 事务处理

提供了展现层 SpringMVC 和持久层 Spring JDBCTemplate 以及业务层事务管理等众多的企业级应用技术
,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。

Spring Framework 基础的框架

Spring Boot 加速开发,提高开发的速度

Spring Cloud 分布式开发

Spring Framework系统架构

是最基础的项目,是其他项目的根基

  • Core Container(核心容器)

  • AOP(面向切面编程)

  • Aspects(AOP思想实现)

  • Data Access / Integration

    • Data Access(数据访问)
    • Data Integration(数据集成)
  • Web:web开发

  • Test:单元测试与集成测试

核心容器

核心概念:IOC / DI Ioc核心容器 Bean

要追求程序的低耦合度。

使用对象时, 在程序中不要主动使用 new 产生对象,转为由外部提供对象

IoC(Inversion of Control)控制反转

  • 对象的创建控制权由程序转移到外部,这就是控制反转

Spring技术对IoC思想进行了实现

  • Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的外部
  • IoC容器负责对象的创建、初始化等一系列工作,被创建或者被管理的对象在IoC容器中统一被称为Bean

DI(Dependency Injection)依赖注入

  • 在容器中建立Bean与Bean之间的依赖关系的整个过程,成为依赖注入。

目标:充分解耦

  • 使用IoC容器管理Bean(IoC)
  • 在IoC容器内将依赖关系的bean进行关系绑定(DI)

最终效果:

  • 使用对象时不仅可以直接从IoC容器中获取,并且获取的bean已经绑定了所以的依赖关系

IoC入门案例思路分析

  1. 管理什么?(案例中是Service和Dao)
  2. 如何将被管的对象告知IoC容器(配置)
  3. 被管理的对象交给IoC容器,如何获取到IoC容器(接口)
  4. IoC容器得到后,如何从容器中获取bean?
  5. 使用spring导入哪些坐标?(pom.xml)

在pom文件中导入spring的坐标spring-context对应版本是5.2.1.RELEASE,先要将spring管理的类(接口)写好

类似如下代码:

public interface UserService{
	public void save();
}
public static UserServiceImpl implements UserService{
	private UserDao userDao = new UserDaoImpl();
	public void save(){
		userDao.save();
	}
}

然后在资源文件夹中创建applicationContext.xml文件,在文件中配置bean,bean标签表示配置bean,id属性表示给bean起名字,class属性表示给bean定义类型

 <bean  id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
 <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>

创建一个demo文件进行测试,先要获取IoC容器,获取过容器后获取bean,最后调取方法。

package com;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class app01 {
    public static void main(String[] args) {
        //3.获取IoC容器
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        //4.获取bean
     /*   UserDao userDao = (UserDao) ioc.getBean("userDao");
        userDao.save();*/
        UserService userService = (UserService) ioc.getBean("userService");
        userService.save();
        
    }
}

DI入门案例思路分析

  1. 基于IoC管理bean
  2. Service中使用new形式创建Dao对象是否保留?(否)
  3. Service中需要的Dao对象如何进入到Service中?(提供方法)
  4. Service和Dao间的关系如何描述?(配置)

将ServiceImpl中,new的userDao方法修改;

package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.service.UserService;

public class UserServiceImpl implements UserService {
    //5.删除业务层中使用new方法创建的对象
    //private UserDao userDao = new UserDaoImpl();
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        System.out.println("Hello service");
        userDao.save();
    }
}

在applicationContext.xml 文件中修改,配置service和dao之间的关系

	<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
        
        
        <property name="userDao" ref="userDao">
        property>
    bean>

bean配置

bean别名配置

当在测试的时候,获取bean时,里面的参数和applicationContext.xml文件中的bean标签中的对应的id不同时,会报错NoSuchBeanDefinitionException No bean named '获取bean时里面的参数' available错误信息

这时候需要考虑的是

1、要么是自己在获取bean时里面的参数错误

2、要么是在配置文件中name中值的错误

定义bean的别名,可以定义多个,使用逗号(,)分号(;)空格( )分隔

bean作用范围配置

默认创建的bean是的bean被成为单例对象,每次使用bean对象都会重新调用,不用重新创建,可以测试一下,在测试代码中创建两个BookService的bookService然后分别输入,得到两个一样的地址值。

public class app03 {
    public static void main(String[] args) {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("AapplicationContext.xml");
        BookService bookService1 = (BookService) ioc.getBean("BookService");
        BookService bookService2 = (BookService) ioc.getBean("BookService");
        System.out.println(bookService1);
        System.out.println(bookService2);
    }
}

这样的单例对象是在配置文件中bean标签里,用scope定义的,默认为singleton,可以改为prototype,这是非单例对象。

继续用上面那个测试代码进行测试会发现生成的地址不同。

默认为bean:

每次使用的时候都可以直接调用。

适合使用单例:

  • 表现层对象
  • 业务层对象
  • 数据层对象
  • 工具对象

不适合单例:

  • 封装实体类的域对象

bean实例化

bean实际上就是对象,创建bean使用构造方法完成。

构造方法实例化对象

在Bookdaompl中写constructor构造梦方法,权限修饰符无论是public还是private都能创建出对象,因为反射,里面有参数则没办法找到并创建对象,所以可知,spring创建bean的时候调用的是无参的构造方法。

正确的构造方法:

    private BookDaoImpl() {
        System.out.println("book dao constructor... ");
    }

错误的构造方法:

    private BookDaoImpl(int i) {
        System.out.println("book dao constructor... ");
    }

对于spring的报错信息一般都是从下向上读的,

.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.()没有这样的一个方法,无参构造方法

配置

 <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

使用静态工厂创建对象

首先需要创建工厂,并在工厂中写创建BookDaoImpl的方法。

    public class BookDaoFactory {
        public static BookDao getBookDao(){
            System.out.println("factory ...");
            return new BookDaoImpl();
        }
    }

配置:

<bean id="BookDao" class="com.itheima.factory.BookDaoFactory" factory-method="getBookDao"/>

用测试代码测试,发现依旧创建了Dao对象。

实例工厂初始化bean

在工厂的代码中进行修改,将static删除,变为非静态的方法。

然后在测试代码中,先要创建实例工厂对象,使用工厂的对象调用getUserDao方法,最后调用save()方法。

        //创建实例化对象
        BookDaoFactory bookDaoFactory = new BookDaoFactory();
        //通过实例工厂对象创建对象
        BookDao bookDao = bookDaoFactory.getBookDao();
        bookDao.save();

在配置中想要创建实例化bean需要先创建出工厂的对象,即先配置工厂的bean

<bean id="bookFactory" class="com.itheima.factory.BookDaoFactory" />
<bean id="BookDao" factory-method="getBookDao" factory-bean="bookFactory"/>

进行测试发现能成功创建工厂对象和Dao对象。

使用FactoryBean实例化bean

在工厂中创建一个bean,实现的FactoryBean接口,这是一个泛型,所以需要在<>中写入需要创建的对象。

然后实现接口中所有方法,代码如下:

public class BookDaoFactoryBean implements FactoryBean<BookDao> {
    //代替原始实例工厂中创建对象的方法
    public BookDao getObject() throws Exception {
        return new BookDaoImpl();
    }
    //
    public Class<?> getObjectType() {
        return BookDao.class;
    }
}

在配置中:

<bean id="BookDao" class="com.itheima.factory.BookDaoFactoryBean"/>

继续测试发现依旧能创建出对象。

这样创建的对象是单例的,想要变为非单例的,需要在FactoryBean中加上isSingleton的方法,返回true是单例的,返回false则是非单例的。这里改bean的scope一样能起到这种作用,会以isSingleton为主。

bean生命周期

  • 生命周期:从创建到消亡的完整过程
  • bean生命周期: bean从创建到销毁的整体过程
  • bean生命周期控制:在bean创建后到销毁前做一些事情

配置的格式写生命周期

在BookDaoImpl中写初始化代码和销毁代码:

    //表示bean初始化对应的操作
    public void init(){
        System.out.println("dao init..");
    }
    //表示destory销毁对应的操作
    public void destory(){
        System.out.println("dao destory...");
    }

写好方法以后需要在配置中进行添加:

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl  " init-method="init" destroy-method="destory"/>
    <bean id="BookService" class="com.itheima.service.impl.BookServiceImpl" scope="singleton">
        <property name="BookDao" ref="bookDao">
        property>
    bean>

进行测试后会发现销毁操作并没有执行,我们的java文件是用虚拟机运行的,初始化创建的时候会执行初始化方法,但是虚拟机运行完退出的时候没有进行销毁的操作。

销毁的方式:

1、在虚拟机退出前将容器手动关闭,ApplicationContext接口不具备close()方法,但是在里面的ClassPathXmlApplicationContext接口有close()方法,所以需要将代码改为:

        //3.获取IoC容器
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        //4.获取bean
        UserDao userDao = (UserDao) ioc.getBean("userDao");
        userDao.save();
        ioc.close();

2、设计钩子,是在虚拟机退出之前将所有的容器关闭才退出。

        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注册关闭钩子
        ioc.registerShutdownHook();
        UserDao userDao = (UserDao) ioc.getBean("userDao");
        userDao.save();

实现InitializingBean和DisposableBean接口

在实现接口的类中实现spring提供的接口,InitializingBean和DisposableBean

public class BookServiceImpl implements BookService,InitializingBean,DisposableBean{

    public void save() {
        System.out.println("bookService save..");
        bookDao.save();
        userDao.save();
    }

      public void destroy() throws Exception {
        System.out.println("service destory...");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("service init...");
    }
}

这种方式就不需要在配置文件中写初始化和销毁属性;

  • 初始化容器
    • 创建对象(内存分配)
    • 执行构造方法
    • 执行属性注入(set操作)
    • 执行bean初始化方法
  • 使用bean
    • 执行业务操作
  • 关闭销毁容器
    • 执行bean销毁方法

依赖注入

setter注入

依赖注入方式:

  • setter注入
    • 简单类型
    • 引用类型
  • 构造器注入
    • 简单类型
    • 引用类型

setter注入—引用类型

  • 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
	private UserDao userDao;
   	public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save() {
        System.out.println("bookService save..");
        bookDao.save();
        userDao.save();
    }
  • 配置中使用property标签ref属性注入引用类型对象
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
 <bean id="BookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    bean>

setter注入—简单类型

  • 在bean中定义引用类型属性并提供可访问的set方法
public class BookDaoImpl implements BookDao{

    private int connectNum;
    private String databaseName;

    public void setConnectNum(int connectNum) {
        this.connectNum = connectNum;
    }

    public void setDatabaseNan(String databaseNan) {
        this.databaseName = databaseNan;
    }

    public void save() {
        System.out.println("bookDao save..."+connectNum+","+databaseName);
    }
}

  • 配置中使用property标签value属性注入简单类型数据
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <property name="connectNum" value="100"/>
    <property name="databaseNan" value="mysql"/>
bean>

构造器注入

因为是构造器注入,所以需要将setter方法删除,直接生成构造方法。

  • 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService {
    private BookDao bookDao;

    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    
    public void save() {
        System.out.println("book Service save..");
        bookDao.save();
    }
}

在配置文件中的配置(标准书写):因为修改变量的名称也需要在配置文件中修改,耦合度高不符合初衷。

  • 配置中使用constructor-arg标签value属性注入简单类型数据
    <bean id="bookDao1" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg name="connectionNum" value="100"/>
        <constructor-arg name="databaseName" value="mysql"/>
    bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="BookService" class="com.itheima.service.impl.BookServiceImpl">
        
        <constructor-arg name="bookDao" ref="bookDao1"/>
        <constructor-arg name="userDao" ref="userDao"/>
    bean>

可以将配置文件中的变量名字删除,采用type的格式来写。

 <bean id="bookDao1" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg type="int" value="100"/>
        <constructor-arg type="java.lang.String" value="mysql"/>
    bean>

这样解决了形参名称改变的问题,但是当如果出现相同的类型就无法用这个方法。

可以利用构造器中定义的顺序的下标来表示,index=0,则是第一个变量

<bean id="bookDao1" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg index="1" value="100"/>
        <constructor-arg index="0" value="mysql"/>
    bean>

这样解决了参数类型重复的问题,使用位置来解决参数匹配。

依赖注入方式选择

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
  2. 苟选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

依赖自动装配

  • IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配

  • 自动装配的方式

    • 按类型
    • 按名称
    • 按构造方法
    • 不启用自动装配

在bookService中的bean里添加autowire属性,里面的值为byTpe,这是自动装配,

 
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

但是进行测试会出错,说明没自动装配成功。如果bookDao创建两个bean,满足dao接口的有两个bean,自动装配已经无法完成。

当autuwire属性的值为byName,bookDao的id不符合时,会报空指针的错误。NullPointerException

一般使用按类型,配置为:

 <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
 <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

测试发现可以正常输出。

依赖自动装配特征

  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  2. 使用按类型装配时( byType )必须保障容器中相同类型的bean唯一,推荐使用
  3. 使用按名称装配时( byName )必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
  4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

集合注入

在DaoImpl里面定义各种类型的集合,并在使用setter方法,最后在配置中赋值。

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <property name="array">
        <array>
            <value>100value>
            <value>200value>
            <value>300value>
        array>
    property>
    <property name="list">
        <list>
            <value>zpdvalue>
            <value>lloovalue>
            <value>sjyvalue>
        list>
    property>
    <property name="set">
        <set>
            <value>qfvalue>
            <value>pxvalue>
            <value>itheimavalue>
            <value>itheimavalue>
        set>
    property>
    <property name="map">
        <map>
            <entry key="001" value="zpd"/>
            <entry key="002" value="sjy"/>
        map>
    property>
    <property name="properties">
        <props>
            <prop key="country">henanprop>
            <prop key="province">henanprop>
        props>
    property>

bean>

如果是引用类型的话使用

<ref bean="beanId"/>

案例:数据源对象管理

  • 导入druid坐标
		<dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.16version>
        dependency>
  • 配置数据源对象作为spring管理的bean
	 <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/spring_db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    bean>

c3p0连接池的使用:

1、导入坐标

 		<dependency>
            <groupId>c3p0groupId>
            <artifactId>c3p0artifactId>
            <version>0.9.1.2version>
        dependency>

2、配置数据源对象作为spring管理的bean

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
    bean>

3、测试发现有异常显示com.mysql.jdbc.Driver未找到,导入mysql

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>5.1.6version>
dependency>

加载properties文件

1、开启context命名空间,


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
beans>

2、使用context空间加载properties文件

 <context:property-placeholder location="jdbc.properties"/>

3、使用属性占位符${ },读取properties文件中的属性

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    bean>

注:在jdbc:properties中添加 username属性

输出后会出现itcast

因为内部有username属性,想要输出的话需要在加载文件的时候,添加system-properties-mode=“NEVER”

这样再输出后就会出现username的属性值。

当想加载多个properties文件时,可以用逗号隔开。也可以在里面写***.properties**,代表所有的properties文件都会被加载。但是最标准的书写方式是在前面加classpath:,加载的是本工程的文件,不会含有jar包里面的。*classpath*代表可以从jar包中读取。

  • 不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
  • 加载多个properties文件
<context:property-placeholder location="jdbc.properties,jdbc2.properties"/>
  • 加载所有的properties文件
<context:property-placeholder location="*.properties"/>
  • 加载properties文件的标准格式
<context:property-placeholder location="classpath:*.properties"/>
  • 从类路径或jar包中搜索并加载properties文件
<context:property-placeholder location="classpath*:*.properties"/>

容器

创建容器

  • 获取配置文件都是加载类路径下的配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  • 从文件路径加载配置文件
ApplicationContext ctx1 = new FileSystemXmlApplicationContext("D:\\大学\\前后端\\后\\中级\\spring\\manage\\src\\main\\resources\\applicationContext.xml");

加载多个配置文件

ApplicationContext ctx = new ClassPathXmlApplicationContext( "bean1.xml","bean2.xml");

获取bean的方式

使用bean名称获取

		BookDao bookDao = ctx.getBean("bookDao",BookDao.class);

使用bean名称获取并指定类型

		BookDao bookDao = (BookDao) ctx.getBean("BookDao");

使用bean类型获取

		BookDao bookDao = (BookDao) ctx.getBean(BookDao.class);

容器类层次结构

BeanFactory

BeanFactory是延迟加载bean,ApplicationContext是立即加载bean,想要让ApplicationContext延迟加载则需要在bean中添加lazy-init属性

<bean name="bookDao" class="com.zpd.dao.impl.BookDaoImpl" lazy-init="true"/>
  • 类路径加载配置文件
        Resource resources = new ClassPathResource("applicationContext.xml");
        BeanFactory bf= new XmlBeanFactory(resources);
        BookDao dao = bf.getBean(BookDao.class);
        dao.save();
  • BeanFactory创建完后,所以的bean均为延迟加载

核心容器总结

容器相关

  • BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
  • ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
  • ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
  • ApplicationContext接口常用初始化类
    • classPathXmlApplicationContext
    • FileSystemXmlApplicationContext

bean相关

spring基础(四万字爆更)_第1张图片

依赖注入

spring基础(四万字爆更)_第2张图片

spring强项:注解开发

注解开发定义bean

在一个原始模块中进行修改,BookDaoImpl中只有sava()方法,BookServiceImpl中调用了BookDao,使用了setter方法,还有一个save()方法,测试代码中只打印bookDao。

@Component 是通用的,如果有表现层,数据层,业务层的开发

@Controller 表现层

@Service 业务层

@Reposotory 数据层

需要配合组件扫描才可以读取

在配置中将bookDao的bean改造成注解开发,配置的是BookDao所以在实现类中添加注解,

  • 使用@Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
@Component
public class BookServiceImpl implements BookService {
}
  • 配置中进行扫描
<context:component-scan base-package="com.zpd"/>

进行测试:

public class app {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao);
        BookService bookService = (BookService) ctx.getBean(BookService.class);
        System.out.println(bookService);

    }
}

BookDao是按照名称访问参数是id名,BookService是按类型访参数是class类型。

纯注解开发

  • Spring3.0升级了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道

  • Java类代替Spring核心配置文件,

@Configuration
@ComponentScan("com.zpd")
public class SpringConfig {   
}
  • @Configuration注解用于设定当前类为配置类
  • @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
@ComponentScan({"com.zpd.service","com.zpd.dao"})
  • 读取spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

bean作用范围

在测试代码中生成两个BookDao类型,都输入打印会发现地址相同,说明生成的是单例。想要生成非单例需要在BookDaoImpl上方添加@Scope注解,里面的内容是singleton就是单例,是prototype就是非单例。

@Scope("singleton")
public class BookDaoImpl implements BookDao {
}

bean生命周期

在BookDao中定义初始化和销毁的方法,采用注解的方法,初始化用@PostConstruct,销毁用@PreDestroy注解。

    @PostConstruct
    public void init(){
        System.out.println("bookDao init...");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("bookDao destroy...");
    }

在测试的时候需要调用close方法,或者新建钩子。

调用close方法时,将类型改为AnnotationConfigApplicationContext

public class AppForAnnotation {
    public static void main(String[] args) {
        //加载配置类初始化容器
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao1    = (BookDao) ctx.getBean("bookDao");
        BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao1);
        System.out.println(bookDao2);
        ctx.close();
    }
}

依赖注入—自动装配

在BookService定义的BookDao上方加一个@Autowired注解即可,甚至可以不用写setter方法,因为这里用的是暴力反射方法。

@Autowired
private BookDao bookDao;
  • 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据, 因此无需提供setter方法
  • 注意:自动装配建议使用无参构造方法创建对象( 默认) , 如果不提供对应构造方法,请提供唯一的构造方法

并且是按类型装配,但是如果有两个相同类型的文件,则需要为两个不同文件区分,在BookServiceImpl中使用@Qualifier区分。

//BookDaoImpl1文件
@Repository("bookDao1")
public class BookDaoImpl implements BookDao {

    public void save(){
        System.out.println("book dao save...1");
    }

}

//BookDaoImpl2文件
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao {

    public void save(){
        System.out.println("book dao save...1");
    }

}

使用@Qualifier注解区分。

    @Autowired
    @Qualifier("bookDao")
    private BookDao bookDao;
  • 注意: @Qualifier注解无法单独使用,必须配合@Autowired注解使用

  • 使用@Value实现简单类型注入

	@Value("zpd")
    private String name;
    @Value("20")
     private int age;

从properties文件中取值:

先在jdbc.properties中对应的属性和值

jdbc.username=root
jdbc.password=root
  • 使用@PropertySource注解加载
@Configuration
@ComponentScan("com.zpd")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
  • 路径仅支持单一文件配置,多文件请使用数组格式不允许使用通配符 *

在调用的时候应该使用${ }符号

    @Value("${jdbc.username}")
    private String name;
    @Value("${jdbc.password}")
    private String  pass;

第三方bean

第三方bean管理

在SpringConfig配置类中写,需要在pom文件中导入druid依赖。

在配置文件中写:

@Configuration
public class SpringConfig {
    //1.定义一个方法获得要管理的对象
    //2.添加@bean,表示当前方法的返回值是一个bean
    @Bean("dataSource")
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }
}

进行测试,通过获取类型进行测试

//加载配置类初始化容器
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println(dataSource);

优化:

创建一个jdbcConfig,将获得要管理的对象写入jdbc配置类中:

  • 将独立的配置加入核心配置
  • 方式一:导入式
public class jdbcConfig {
    //1.定义一个方法获得要管理的对象
    //2.添加@bean,表示当前方法的返回值是一个bean
    @Bean("dataSource")
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }
}
  • @Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
@Configuration
@Import(jdbcConfig.class)
public class SpringConfig {
}

第三方bean依赖注入

导入BookDao,在配置类中添加@ComponentScan标签,里面写BookDao目录

@ComponentScan("com.zpd.dao")

在jdbcConfig文件中的dataSource方法中添加BookDao参数

@Bean
public DataSource dataSource(BookDao bookDao){
    //自动装配
    System.out.println(bookDao);
    DruidDataSource ds = new DruidDataSource();
	//属性设置
    return ds;
}
  • 自动装配的时候是按类型装配的,引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象

注解开发总结

XML配置与注解配置比较

spring基础(四万字爆更)_第3张图片

整合

Spring整合MyBatis

第一步:在pom中导入坐标



<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.0modelVersion>

  <groupId>com.itheimagroupId>
  <artifactId>spring_15_spring_mybatisartifactId>
  <version>1.0-SNAPSHOTversion>

  <dependencies>
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>5.2.10.RELEASEversion>
    dependency>
    <dependency>
      <groupId>com.alibabagroupId>
      <artifactId>druidartifactId>
      <version>1.1.16version>
    dependency>

    <dependency>
      <groupId>org.mybatisgroupId>
      <artifactId>mybatisartifactId>
      <version>3.5.6version>
    dependency>

    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
      <version>5.1.47version>
    dependency>

    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifctId>spring-jdbcartifactId>
      <version>5.2.10.RELEASEversion>
    dependency>

    <dependency>
      <groupId>org.mybatisgroupId>
      <artifactId>mybatis-springartifactId>
      <version>1.3.0version>
    dependency>

  dependencies>
project>

创建Config工具,在里面有jdbcConfig,MyBatisConfig(整合MyBatis),springConfig三个工具类。jdbcConfig和springConfig以前都写过,但是在springConfig里面需要注意的是注释需要添加完全。

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({jdbcConfig.class,MybatisConfig.class})
public class springConfig {package com.itheima.config;


import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

/**
 * @Description
 * @Author zpd
 * @Date 2022/9/18
 */
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setTypeAliasesPackage("com.itheima.domain");
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.itheima.dao");
        return mapperScannerConfigurer;
    }
}

}

​ 在MyBatisConfig里面写MyBaties创建sqlSessionFactory工厂,然后MyBatiesConfig.xml文件中标签的写入。

package com.itheima.config;


import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {
    @Bean
    //创建sqlSessionFactory工厂
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setTypeAliasesPackage("com.itheima.domain");
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
    @Bean
    //原来Mybatis整合的文件现在用spring整合
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.itheima.dao");
        return mapperScannerConfigurer;
    }
}

固定格式!!!

Spring整合Junit

1、引入依赖pom.xml

<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.10.RELEASEversion>
dependency>

2、编写测试类

在test\java下创建一个AccountServiceTest,这个名字任意

//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类
//@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件
public class AccountServiceTest {
//支持自动装配注入bean
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
} 
@Test
public void testFindAll(){
System.out.println(accountService.findAll());
	}
}

运行后发现可以正常运行。

需要记

//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类

AOP

核心概念

AOP是在不改原有代码的前提下对其进行增强。

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程
    序结构。
    • OOP(Object Oriented Programming)面向对象编程

作用:在不惊动原始设计的基础上为其功能进行增强。

spring理念:无侵入式编程。

1、追加功能的点被称为切入点

2、追加的功能被称为通知

3、通知和切入点的连接被称为切面

  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 在SpringAOP中,理解为方法的执行
  • 切入点(Pointcut):匹配连接点的式子
    • 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
      • 一个具体的方法:如com.itheima.dao包下的BookDao接口中的无形参无返回值的save方
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意
        方法,所有带有一个参数的方法
    • 连接点范围要比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法就不一定要被增强,所以可能不是切入点。
  • 通知(Advice):在切入点处执行的操作,也就是共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面(Aspect):描述通知与切入点的对应关系。

AOP入门案例

思路分析:

1、导入坐标(pom.xml)

Spring-context坐标里面包含有AOP

2、制作连接点方法(原始操作,dao接口及其实现类)

Dao里面的sava方法或者定义的updata方法等;

3、制作共性功能(通知类与通知)

新建一个MyAdvice类用来制作共性功能,新建一个方法来表示共性功能

public void method(){
	System.out.println(System.currentTimeMillis());
	System.out.println("book dao sava...")
}

4、定义切入点

在上方随便添加一个方法,然后加入切入的注解。

说明︰切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑

    @Pointcut("execution(void com.itheima.dao.BookDao.updata())")
    private void pt(){}

5、绑定切入点与通知的关系(切面)

在共性功能的方法上添加注解进行绑定。

@Before("pt()")
public void method(){
    //内容
}

这样就会在执行方法前执行切入点。

切面制作完毕,但是不受spring控制,所以在类名上方添加注解:

//受spring控制
@Component
//告诉spring扫描到以后按照aop处理而不是按bean处理,去找下面的@Pointcut注解,和执行先后的注解
@Aspect
public class MyAdvice { }

最后在总的springConfig上添加注解:让spring清楚aop开发是注解开发。

@Configuration
@ComponentScan("com.itheima")
//告诉spring这里面有注解开发的aop,去找@Aspect注解
@EnableAspectJAutoProxy
public class SpringConfig {
}

归根到底修改的只有springConfig和新建了一个切面方法、切入点。

AOP工作流程

1、spring容器启动

2、读取所有切面配置中的切入点

3、初始化bean,判定bean对应的类中的方法是否匹配到切入点

  • 匹配失败,创建对象
  • 匹配成功,创建原始对象(目标对象)的代理对象

4、 获取bean执行方法

  • 获取bean,调用方法并执行,完成操作
  • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

AOP核心概念

  • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
  • 代理(Proxy )∶目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

AOP切入点表达式

语法格式

  • 切入点:要增强的方法
  • 切入点表达式:要增强的方法的描述方式

描述方式一:执行com.itheima.dao包下的BookDao接口中的无参数update方法

execution(void com.itheima.dao.BookDao.update())

描述方式二︰执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法

execution(void com.itheima.dao.impl.BookDaoImpl.update())
  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)

execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
public:访问修饰符,还可以是public,private等,可以省略
User:返回值,写返回值类型
com.itheima.service:包名,多级包使用点连接
UserService:类/接口名称
findById:方法名
int:参数,直接写参数的类型,多个类型用逗号隔开
异常名:方法定义中抛出指定异常,可以省略

通配符

  • 可以使用通配符描述切入点,快速描述
    • * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))  

​ 匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的
​ 方法

  • :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

  • +:专用于匹配子类类型
execution(* *..*Service+.*(..))

这个使用率较低,描述子类的,咱们做JavaEE开发,继承机会就一次,使用都很慎重,所以很少
用它。*Service+,表示所有以Service结尾的接口的子类。

execution(void com.itheima.dao.BookDao.update())
匹配接口,能匹配到
execution(void com.itheima.dao.impl.BookDaoImpl.update())
匹配实现类,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update())
返回值任意,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update(*))
返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加
参数
execution(void com.*.*.*.*.update())
返回值为void,com包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
execution(void com.*.*.*.update())
返回值为void,com包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
execution(void *..update())
返回值为void,方法名是update的任意包下的任意类,能匹配
execution(* *..*(..))
匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..u*(..))
匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
execution(* *..*e(..))
匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
execution(void com..*())
返回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
execution(* com.itheima.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配
execution(* com.itheima.*.*Service.save*(..))
将项目中所有业务层方法的以save开头的方法匹配

后面两种更符合我们平常切入点表达式的编写规则。

书写技巧

所有代码按照标准规范开发,否则以下技巧全部失效
描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述
返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用通配快速描述
包名书写==尽量不使用…匹配==,效率过低,常用
做单个包描述匹配,或精准匹配
接口名**/类名书写名称与模块相关的采用匹配,例如UserService书写成Service,绑定业务
层接口名
方法名书写以
动词进行精准匹配**,名词采用匹配,例如getById书写成getBy,selectAll书写成
selectAll
参数规则较为复杂,根据业务方法灵活调整
通常不使用异常作为匹配规则

AOP通知类型

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合
    理的位置
  • AOP通知共分为5种类型
    • 前置通知
    • 后置通知
    • 环绕通知(重点)
    • 返回后通知(了解)
    • 抛出异常后通知(了解)

@Before

@After

@Around 中间有表示对原始操作的调用

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
           //调用原始操作
           pjp.proceed();
}

需要抛出异常!

原始方法select如果有int返回值:

  • 修改MyAdvice,对BookDao中的select方法添加环绕通知
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
@Around("pt2()")
public void aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
pjp.proceed();
System.out.println("around after advice ...");
}
}
  • 修改App类,调用select方法
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
int num = bookDao.select();
System.out.println(num);
	}
}

运行后会报错,错误内容为:
Exception in thread “main” org.springframework.aop.AopInvocationException:
Null return value from advice does not match primitive return type for:
public abstract int com.itheima.dao.BookDao.select()
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopP
roxy.java:226) at com.sun.proxy.$Proxy19.select(Unknown Source) at
com.itheima.App.main(App.java:12)

错误大概的意思是:空的返回不匹配原始方法的int 返回

  • void就是返回null
  • 原始方法就是BookDao下的select方法

所以如果我们使用环绕通知的话,要根据原始方法的返回值来设置环绕通知的返回值,具体解决方案
为:

@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
@Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
	}
}

说明:
为什么返回的是Object而不是int的,主要原因是Object类型更通用。
在环绕通知中是可以对原始方法返回值就行修改的。

@AfterReturning

注意:返回后通知是需要在原始方法select正常执行后才会被执行,如果select()方法执行的过程
中出现了异常,那么返回后通知是不会被执行。后置通知是不管原始方法有没有抛出异常都会被执
行。

@AfterThrowing

注意:异常后通知是需要原始方法抛出异常,可以在select()方法中添加一行代码int i = 1/0即
可。如果没有抛异常,异常后通知将不会被执行。

案例:测量业务层接口万次执行效率

1、在springConfig配置文件上添加@EnableAspectJAutoProxy 注解

2、创建AOP的通知类

  • 该类要被Spring管理,需要添加@Component
  • 要标识该类是一个AOP的切面类,需要添加@Aspect
  • 配置切入点表达式,需要添加一个方法,并添加@Pointcut
@Component
@Aspect
public class ProjectAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
public void runSpeed(){
	}
}

3、添加环绕

在runSpeed()方法上添加@Around

@Component
@Aspect
public class ProjectAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
@Around("servicePt()")
public Object runSpeed(ProceedingJoinPoint pjp){
Object ret = pjp.proceed();
return ret;
}
}

注意:目前并没有做任何增强!!!

4、完成核心业务,记录万次执行的时间

@Component
@Aspect
public class ProjectAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
@Around("servicePt()")
public void runSpeed(ProceedingJoinPoint pjp){
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
} l
ong end = System.currentTimeMillis();
System.out.println("业务层接口万次执行时间: "+(end-start)+"ms");
	}
}

5、运行单元测试类

会显示运行10000次的时长。

6、程序优化

目前程序所面临的问题是,多个方法一起执行测试的时候,控制台都打印的是:
业务层接口万次执行时间:xxxms
我们没有办法区分到底是哪个接口的哪个方法执行的具体时间。

@Component
@Aspect
public class ProjectAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
@Around("servicePt()")
public void runSpeed(ProceedingJoinPoint pjp){
//获取执行签名信息
Signature signature = pjp.getSignature();
//通过签名获取执行操作名称(接口名)
String className = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
} l
ong end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +
(end-start) + "ms");
}
}

7、运行单元测试

能清楚的看到不管运行的哪种方法,都可以清楚的看到所用的时间。只是一个理论值,不是完整的过程。

AOP通知获取数据

获取参数

  • 获取切入点方法的参数
    • JionPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedJointPoint:适用于环绕通知
    • 获取切入点方法返回值
    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息
    • 抛出异常后通知
    • 环绕通知

通过测试来获取数据:

在方法上添加JoinPoint,通过JoinPoint来获取参数

@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint jp)
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
} /
/...其他的略
}

参数的个数是不固定的,所以使用数组更通配些,当将获取的参数改为两个的时候返回的也是数组的格式。

环绕通知获取方式

环绕通知使用的是ProceedingJoinPoint,因为ProceedingJoinPoint是JoinPoint类的子
类,所以对于ProceedingJoinPoint类中应该也会有对应的getArgs()方法。

@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed();
return ret;
} /
/其他的略
}

获取返回值

@AfterReturning

@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(String ret) {
System.out.println("afterReturning advice ..."+ret);
}

returning 返回的返回值和方法里面的参数必须一致。如果在方法中JoinPoint和他都存在的话都错在,必须的JoinPont在前。

@Around 上面已经说过,甚至可以修改返回值;

public Object around(ProceedingJoinPoint pjp)throws Throwable {
Object ret = pjp.proceed(args);
return ret;
} 

对于Around来说调用完就可以得到返回值。

获取异常

@Around

@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try{
ret = pjp.proceed(args);
}catch(Throwable throwable){
t.printStackTrace();
} r
eturn ret;
}

try…catch获取异常

@AfterThrowing

@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@AfterThrowing(value = "pt()",throwing = "t")
//形参接异常的返回对象
public void afterThrowing(Throwable t) 
	{
System.out.println("afterThrowing advice ..."+t);
} /
/其他的略
}

让原始方法抛出异常:

@Repository
public class BookDaoImpl implements BookDao {
public String findName(int id,String password) {
System.out.println("id:"+id);
if(true){
throw new NullPointerException();
} 
return "itcast";
}
}

案例:百度网盘密码数据兼容处理

需求: 对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理。

分析:

1、在业务方法执行之前对所有的输入参数进行格式处理——trim()。

2、使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用

  • 添加pom依赖
  • 添加ResourcesService,ResourcesServiceImpl,ResourcesDao和ResourcesDaoImpl
public interface ResourcesDao {
boolean readResources(String url, String password);
}
@Repository
public class ResourcesDaoImpl implements ResourcesDao {
public boolean readResources(String url, String password) {
//模拟校验
return password.equals("root");
}
} 
public interface ResourcesService {
public boolean openURL(String url ,String password);
} 
@Service
public class ResourcesServiceImpl implements ResourcesService {
@Autowired
private ResourcesDao resourcesDao;
public boolean openURL(String url, String password) {
return resourcesDao.readResources(url,password);
}
}
  • 创建Spring的配置类
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
  • 编写App运行类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);
ResourcesService resourcesService = ctx.getBean(ResourcesService.class);
	boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", "root");
	System.out.println(flag);
	}
}

现在项目的效果是,当输入密码为"root"控制台打印为true,如果密码改为"root "控制台打印的是
false
需求是使用AOP将参数进行统一处理,不管输入的密码root前后包含多少个空格,最终控制台打印的
都是true。

实现:

1、开启SpringAop注解功能

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

2、编写通知类

@Component
@Aspect
public class DataAdvice {
	@Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
	private void servicePt(){}
}

3、添加环绕通知

@Component
@Aspect
public class DataAdvice {
	@Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
	private void servicePt(){}
	@Around("DataAdvice.servicePt()")
	//@Around("servicePt()")这两种写法都对
	public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
		Object ret = pjp.proceed();
		return ret;
	}
}

4、完成核心业务,处理空格

@Component
@Aspect
public class DataAdvice {
@Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
private void servicePt(){}
@Around("DataAdvice.servicePt()")
// @Around("servicePt()")这两种写法都对
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
//获取原始方法的参数
	Object[] args = pjp.getArgs();
	for (int i = 0; i < args.length; i++) {
	//判断参数是不是字符串
		if(args[i].getClass().equals(String.class)){
			args[i] = args[i].toString().trim();
		}
	} 
		//将修改后的参数传入到原始方法的执行中
		Object ret = pjp.proceed(args);
		return ret;
	}
}

5、运行程序

发现不管有没有空格都能返回true

AOP总结

  • 概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式

  • 作用:在不惊动原始设计的基础上为方法进行功能增强

  • 核心概念

    • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
    • 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
    • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
    • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
    • 切面(Aspect):描述通知与切入点的对应关系
    • 目标对象(Target):被代理的原始对象成为目标对象
  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名**./接口名.**方法名(参数)异常名)

    • execution(* com**.itheima.service.Service.**(…))
  • 切入点表达式描述通配符

    • 作用:用于快速描述,范围描述
    • *:匹配任意符号(常用)
    • :匹配多个连续的任意符号
    • +:匹配子类类型
  • 切入点表达式书写技巧

    1.按标准规范开发

    2.查询操作的返回值建议使用*匹配

    3.减少使用**…**的形式描述包

    4.对接口进行描述,使用 ***** 表示模块名,例如UserService的匹配描述为 ***** Service

    5.方法名书写保留动词,例如get,使用 ***** 表示名词,例如getById匹配描述为getBy *****

    6.参数根据实际情况灵活调整

  • 通知类型

    • 前置通知
    • 后置通知
    • 环绕通知(重点)
      • 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用(必须在形参的第一位)
      • 环绕通知可以隔离原始方法的调用执行
      • 环绕通知返回值设置为Object类型
      • 环绕通知中可以对原始方法调用过程中出现的异常进行处理
    • 返回异常后通知
    • 抛出异常后通知
  • 获取切入点方法的参数,所有的通知类型都可以获取参数

    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedingJoinPoint:适用于环绕通知
  • 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究

    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究

    • 抛出异常后通知
    • 环绕通知

Spring事务

Spring事务简介

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败

接口:

public interface PlatformTransactionManager{
	void commit(Transactionstatus status) throws TransactionException;
	void rollback(Transactionstatus status) throws TransactionException;
}

实现类:

public class DataSourceTransactionManager{
		...
}

模拟银行账户间转账业务

需求: 实现任意两个账户间转账操作
需求微缩: A账户减钱,B账户加钱

为了实现上述的业务需求,我们可以按照下面步骤来实现下:

①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
②:业务层提供转账操作(transfer),调用减钱与加钱的操作
③:提供2个账号和操作金额执行转账操作
④:基于Spring整合MyBatis环境搭建上述操作

事务实用开发

1、在业务层接口上添加spring事务管理

public interface AccountService {
    @Transactional
    public void transfer(String out,String in ,Double money) ;
}

Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

2、设置事务管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }

3、开启注解式事务驱动

//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

Spring事务角色

  • 事务角色

    • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务,的方法

    接口中的transfer(String out,String in ,Double money)方法

    • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
    accountDao.outMoney(out,money);
    //int i = 1/0;
    accountDao.inMoney(in,money);
    

事务相关配置

事务配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xleo3hC7-1663986030425)(C:%5CUsers%5Czpd%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20220920164113644.png)]

并不是所有的异常都会回滚事务,

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) throws IOException {
        accountDao.outMoney(out,money);
        if (true){
            throw new IOException();
        }
        accountDao.inMoney(in,money);
    }

}
  • 出现这个问题的原因是,Spring的事务只会对Error异常(Error通常是虚拟机出错,如内存溢出、StackOverFlow)和RuntimeException异常(NullPointException、算数运算符)及其子类进行事务回顾,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚。

这时进行转账测试的话A减少钱,但是B没收到钱

  • 此时就可以使用rollbackFor属性来设置出现IOException异常不回滚
    @Transactional(rollbackFor = {IOException.class})
    public void transfer(String out,String in ,Double money) throws IOException;

这时进行转账测试的话A不减少钱,B也没收到钱

案例:转账业务追加日志

在前面的转案例的基础上添加新的需求,完成转账后记录日志。
需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
需求微缩:A账户减钱,B账户加钱,数据库记录日志

分析:

①:基于转账操作案例添加日志模块,实现数据库中记录日志
②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能

实现预期效果为:

无论转账操作是否成功,均进行转账操作的日志留痕

1、在业务层接口上添加spring事务,设置事务传播行为REQUIRES_NEW(需要新事务)

事务传播行为

  • 事务协调员对事务管理员所携带事务的处理态度

在事务管理员的方法里,@Transactional注解中有一个事务属性用来做事务传播行为。

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void log(String out, String in, Double money);

接口中而不会添加到业务层实现类中,降低耦合

注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

2、设置事务管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }

3、开启注解式事务驱动

//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

Spring事务角色

  • 事务角色

    • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务,的方法

    接口中的transfer(String out,String in ,Double money)方法

    • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
    accountDao.outMoney(out,money);
    //int i = 1/0;
    accountDao.inMoney(in,money);
    

事务相关配置

事务配置

[外链图片转存中…(img-Xleo3hC7-1663986030425)]

并不是所有的异常都会回滚事务,

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) throws IOException {
        accountDao.outMoney(out,money);
        if (true){
            throw new IOException();
        }
        accountDao.inMoney(in,money);
    }

}
  • 出现这个问题的原因是,Spring的事务只会对Error异常(Error通常是虚拟机出错,如内存溢出、StackOverFlow)和RuntimeException异常(NullPointException、算数运算符)及其子类进行事务回顾,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚。

这时进行转账测试的话A减少钱,但是B没收到钱

  • 此时就可以使用rollbackFor属性来设置出现IOException异常不回滚
    @Transactional(rollbackFor = {IOException.class})
    public void transfer(String out,String in ,Double money) throws IOException;

这时进行转账测试的话A不减少钱,B也没收到钱

案例:转账业务追加日志

在前面的转案例的基础上添加新的需求,完成转账后记录日志。
需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
需求微缩:A账户减钱,B账户加钱,数据库记录日志

分析:

①:基于转账操作案例添加日志模块,实现数据库中记录日志
②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能

实现预期效果为:

无论转账操作是否成功,均进行转账操作的日志留痕

1、在业务层接口上添加spring事务,设置事务传播行为REQUIRES_NEW(需要新事务)

事务传播行为

  • 事务协调员对事务管理员所携带事务的处理态度

在事务管理员的方法里,@Transactional注解中有一个事务属性用来做事务传播行为。

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void log(String out, String in, Double money);

spring基础(四万字爆更)_第4张图片

你可能感兴趣的:(Spring,spring,java,系统架构)