Spring框架

Spring的体系结构

spring框架整体架构,核心:IOC控制反转、AOP面向切面编程

IOC 控制反转

什么是IOC

原来:我们在获取对象时,都是采用new的方式,是主动的

现在:我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。 是被动的。

这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一。

IOC的作用:消减计算机程序的耦合

spring 中工厂的类结构图

我们常用的3个ApplicationContext接口的实现类:

  • ClassPathXmlApplicationContext :它是从类的根路径下加载配置文件,推荐使用这种

  • FileSystemXmlApplicationContext :它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

  • AnnotationConfigApplicationContext :当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

    依赖关系如下:

基于XML配置IOC

测试配置bean.xml

在类的根路径下创建一个任意名称的xml文件,这里创建一个bean.xml

在bean.xml文件中对spring配置,用于配置让 spring 创建对象,并且存入 ioc 容器之中

<!-- bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
		id 属性:对象的唯一标识。
		class 属性:指定要创建对象的全限定类名
-->

<!-- 配置 service -->
<bean id="accountService" class="com.t yut.service.impl.AccountServiceImpl">
</bean>

<!-- 配置 dao -->
<bean id="accountDao" class="com.tyut.dao.impl.AccountDaoImpl"></bean>

测试配置是否成功

public class Client {
  /**
  * 使用 main 方法获取容器测试执行
  */

public static void main(String[] args) {

  //1.使用 ApplicationContext 接口,就是在获取 spring 容器
  ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

  //2.根据 bean 的 id 获取对象
  IAccountService aService = (IAccountService) ac.getBean("accountService");
  System.out.println(aService);

  IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
  System.out.println(aDao);
}

IOC中bean标签和管理对象的细节

bean标签配置

作用:
	用于配置对象让 spring 来创建的。
	默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

属性:
	id:给对象在容器中提供一个唯一标识。用于获取对象。
	class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
	scope:指定对象的作用范围。
		* singleton :默认值,单例的.
		* prototype :多例的.
		* request:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
		* session:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
		* global session:WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session.

	init-method:指定类中的初始化方法名称。
	destroy-method:指定类中销毁方法名称。

实例化Bean的三种方式

  • 第一种方式:使用默认无参数构造函数

    <!--在默认情况下:
    它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。
    -->
    <bean id="accountService" class="com.tyu t.service.impl.AccountServiceImpl"/>
    
  • 第二种方式:spring管理静态工厂-使用静态工厂的方法创建对象

    /**
      * 模拟一个静态工厂,创建业务层实现类	
    */
    public class StaticFactory {
    	public static IAccountService createAccountService(){
    		return new AccountServiceImpl();
    	}
    }
    
    <!-- 此种方式是:
      使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
        id 属性:指定 bean 的 id,用于从容器中获取
        class 属性:指定静态工厂的全限定类名
        factory-method 属性:指定生产对象的静态方法、
    -->
    
    <bean id="accountService" class="com.tyut.factory.StaticFactory"
    factory-method="createAccountService"></bean>
    
  • 第三种方式:spring管理实例工厂-使用实例工厂的方法创建对象

    /**
      * 模拟一个实例工厂,创建业务层实现类
      * 此工厂创建对象,必须现有工厂实例对象,再调用方法
    */
    public class InstanceFactory {
      public IAccountService createAccountService(){
        return new AccountServiceImpl();
    	}	
    }
    
    <!-- 此种方式是:
      先把工厂的创建交给 spring 来管理。
      然后在使用工厂的 bean 来调用里面的方法
      factory-bean 属性:用于指定实例工厂 bean 的 id。
      factory-method 属性:用于指定实例工厂中创建对象的方法。
    -->
    
    <bean id="instancFactory" class="com.tyut.factory.InstanceFactory"></bean>
    
    <bean id="accountService" factory-bean="instancFactory" factory-method="createAccountService"></bean>
    

Sping中的依赖注入

构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入

public class AccountServiceImpl implements IAccountService {
  private String name;
  private Integer age;
  private Date birthday;
  public AccountServiceImpl(String name, Integer age, Date birthday){
    this.name = name;
    this.age = age;
    this.birthday = birthday;
  }

@Override
  public void saveAccount() {
    System.out.println(name+","+age+","+birthday);
  }
}
<!-- 使用构造函数的方式,给 service 中的属性传值
  要求:
  类中需要提供一个对应参数列表的构造函数。
  涉及的标签:constructor-arg
  属性:
  index:指定参数在构造函数参数列表的索引位置
  type:指定参数在构造函数中的数据类型
  name:指定参数在构造函数中的名称,用这个找给谁赋值

  =======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
  value:它能赋的值是基本数据类型和 String 类型
  ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">

  <constructor-arg name="name" value="张三"></constructor-arg>
  <constructor-arg name="age" value="18"></constructor-arg>
  <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>

<bean id="now" class="java.util.Date"></bean>

set方法注入

在类中提供需要注入成员的 set 方法。具体代码如下:

public class AccountServiceImpl implements IAccountService {
  private String name;
  private Integer age;
  private Date birthday;
  public void setName(String name) {
    this.name = name;
  }
  public void setAge(Integer age) {
    this.age = age;
  }
  public void setBirthday(Date birthday) {
    this.birthday = birthday;
  }

  @Override
  public void saveAccount() {
    System.out.println(name+","+age+","+birthday);
  }

}
<!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式

  涉及的标签:property
  属性:
	name:找的是类中 set 方法后面的部分
  ref:给属性赋值是其他 bean 类型的
  value:给属性赋值是基本数据类型和 string 类型的
  实际开发中,此种方式用的较多。
-->

<bean id="accountService" class="com.tyut.service.impl.AccountServiceImpl">
  <property name="name" value="test"></property>
  <property name="age" value="21"></property>
  <property name="birthday" ref="now"></property>
</bean>

<bean id="now" class="java.util.Date"></bean>

注入集合属性

<!-- 注入集合数据
	List 结构的:
		array,list,set
	Map 结构的
		map,entry,props,prop
-->

<bean id="accountService" class="com.t yut.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="myStrs">
  <set>
    <value>AAA</value>
    <value>BBB</value>
    <value>CCC</value>
  </set>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
  <array>
  <value>AAA</value>
  <value>BBB</value>
  <value>CCC</value>
  </array>
</property>

<!-- 注入 set 集合数据 -->
<property name="mySet">
  <list>
    <value>AAA</value>
    <value>BBB</value>
    <value>CCC</value>
  </list>
</property>

<!-- 注入 Map 数据 -->
<property name="myMap">
  <props>
    <prop key="testA">aaa</prop>
    <prop key="testB">bbb</prop>
  </props>
</property>

<!-- 注入 properties 数据 -->
<property name="myProps">
  <map>
    <entry key="testA" value="aaa"></entry>
    <entry key="testB">
    <value>bbb</value>
    </entry>
  </map>
</property>
</bean>

基于注解的IOC配置

Bean.xml要扫描的包

在spring.xml文件中配置扫描包,才可以在对应包中使用相应的注解配置内容

<!-- 告知 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="com.tyut"></context:component-scan>

注解IOC创建对象

相当于在xml配置文件中创建bean对象,并交给IOC容器管理: <bean id="" class="">

  • @Component

作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。

属性:value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。

  • @Controller @Service @Repository

他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。

他们只不过是提供了更加明确的语义化。

@Controller: 一般用于表现层的注解。

@Service: 一般用于业务层的注解。

@Repository: 一般用于持久层的注解。

细节:如果注解中有且只有一个属性要赋值时,且名称是 value, value 在赋值是可以不写。

注解IOC注入数据

用于注入数据的 相当于:<property name="" value="">

  • @Autowired

作用:自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。

例如下图:spring的IOC容器为Map结构(key-value),key值为变量名称,value值为Object具体的类,使用@Autowired注解按照类型注入时,首先会在IOC容器中寻找IAccountDao数据类型的value值,也就是找到下图右边红框的2部分,如果找到多个,再按照变量名称去寻找key,也就是下图蓝色箭头标注的部分,找到就可以给相应的变量进行赋值操作。

  • @Qualifier

作用:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。

属性:value:指定 bean 的 id。

  • @Resource

作用:直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。

属性:name:指定 bean 的 id。

  • @Value

作用:注入基本数据类型和 String 类型数据的

属性:value:用于指定值

配置类注解

  • @Configuration

作用:用于指定当前类是一个spring 配置类, 当创建容器时会从该类上加载注解。 获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)。

属性:value:用于指定配置类的字节码

  • @ComponentScan

作用:用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package="com.tyut"/>是一样的。

属性:basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。

  • @Bean

作用:该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。

属性:name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)

  • @PropertySource

作用:用于加载 .properties 文件中的配置 。 例如我们配置数据源时, 可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。

属性:value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

  • @Import

作用:用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。

属性:value[]:用于指定其他配置类的字节码。

AOP 面向切面编程

什么是AOP

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。

就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

AOP相关术语

Joinpoint(连接点 ):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。业务层中所有的接口都是连接点

Pointcut(切入点 ):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。被增强的方法称为切入点,这里的test方法就是连接点,而不是切入点,因为test方法没有增强

Advice(通知 /增强 ):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

基于XML的AOP配置

基于XML的AOP配置

原本的业务层方法,现需引入增强类

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService{

    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);

    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

增强Logger类printLog()方法

package com.tyut.utils;

/**
 * 用于记录日志的工具类,它里面提供了公共的代码
 */
public class Logger {

    /**
     * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
     */
    public void printLog() {

        System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
    }
}

在xml文件中配置,配置前置通知AOP

<!-- 配置Logger类 -->
    <bean id="logger" class="com.tyut.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* com.tyut.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>

Spring中基于XML的AOP配置步骤

1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
		id属性:是给切面提供一个唯一标识
    ref属性:是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
   	我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
    	aop:before:表示配置前置通知
     	method属性:用于指定Logger类中哪个方法是前置通知
      pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
		 	* 切入点表达式的写法:
     		关键字:execution(表达式)
      	表达式:
        	访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
        标准的表达式写法:
    		public void  																					com.tyut.service.impl.AccountServiceImpl.saveAccount()
        实际开发中切入点表达式的通常写法:
            切到业务层实现类下的所有方法
            * com.tyut.service.impl.*.*(..)

环绕通知

增强方法Logger,存在前置通知、后置通知、异常通知、最终通知、环绕通知

package com.tyut.utils;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 用于记录日志的工具类,它里面提供了公共的代码
 */
public class Logger {

    /**
     * 前置通知
     */
    public  void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }
    /**
     * 异常通知
     */
    public  void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
    public  void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }

    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     * spring中的环绕通知:
     *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

XML文件中的配置

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

    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="com.tyut.service.impl.AccountServiceImpl"></bean>


    <!-- 配置Logger类 -->
    <bean id="logger" class="com.tyut.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
              此标签写在aop:aspect标签内部只能当前切面使用。
              它还可以写在aop:aspect外面,此时就变成了所有切面可用
          -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知:在切入点方法执行之前执行
            <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->

            <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->

            <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->

            <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->

            <!-- 配置环绕通知 详细的注释请看Logger类中-->
            <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>

基于注解的AOP配置

   <!-- 配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.tyut"></context:component-scan>

    <!-- 配置spring开启注解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

增强类配置

package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 用于记录日志的工具类,它里面提供了公共的代码
 */
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 前置通知
     */
//    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
//    @AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }
    /**
     * 异常通知
     */
//    @AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
//    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }

    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

posted @ 2022-09-06 17:25  lalalaxiaoyuren  阅读(30)  评论(0编辑  收藏  举报