Spring的IOC和AOP

Spring简介

​ Spring是一个开原框架,是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。Spring的核心是控制反转(IoC)面向切面(AOP)。Spring 是为了解决企业级应用开发的复杂性而创建的。在 Spring 之前,有一个重量级的工具叫做 EJB,使用 Spring 可以让 Java Bean 之间进行有效的解耦,而这个操作之前只有 EJB 才能完成,EJB 过于臃肿,使用很少。Spring 不仅仅局限于服务端的开发,在测试性和松耦合方面都有很好的表现。

一站式,是指Spring有JavaEE开发的每一层解决方案

  • web层:Struts2,Spring-MVC
  • service层:Spring
  • dao层:Hibernate,MyBatis ,Spring-Data

Spring发展史

1997年IBM提出了EJB的思想(EJB是的Enterprise Java Beans技术的简称, 又被称为企业Java Beans)
1998年,SUN制定开发标准规范EJB1.0
1999年,EJB1.1发布
2001年,EJB2.0发布
2003年,EJB2.1发布
2006年,EJB3.0发布

2004年,Spring1.0发布
2006年,Spring2.0发布
2009年,Spring3.0发布
2013年,Spring4.0发布
2017年,Spring5.0发布
2022年,Spring6.0发布

Spring核心体系

image-20230224201823016

Spring为企业级开发提供了丰富的功能,这些功能的底层都依赖于它的两个核心特性:

  • 控制反转(Inversion of Control,IOC
  • 面向切面编程(aspect-oriented programming,AOP

Spring依赖

<!-- spring核心jar包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

IOC

Ioc (Inversion of Control),中文叫做控制反转。这是一个概念,也是一种思想。控制反转,实际上就是指对一个对象的控制权的反转。

控制:控制对象的创建、初始化、销毁

反转:对象的控制权(对象的创建、初始化、销毁)从主动管理转交给Spring的容器管理

IOC思想:面向接口编程,反射+配置文件,工厂模式

xml格式配置

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 1.创建spring控制的资源
              1). id 是这个bean的标识, 可以自定义,但是最好见名知意
              2). class 指定实现类的全限定名
      -->
    <bean id="bean标识" class="实现类的全限定名" name="别名"/>
</beans>

获取bean

@Test
public void test01(){
    //加载配置文件
    ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件名.xml");
    //获取资源: 通过配置文件中的id
    Object obj = ctx.getBean("bean的id属性");
}

beans标签

  • 根标签
  • 子标签:bean

bean基本属性

  • id
    • bean的名称,通过id值获取bean
  • class
    • bean的类型 (全限定名,即包名+类名)
  • name
    • bean的名称,可以通过name值获取bean,用于多人配合时给bean起别名

bean标签scope属性

  • 作用定义bean的作用范围
  • 取值
    • singleton
      • 设定创建出的对象保存在spring容器中,是一个单例的对象
      • 饿汉单例
    • prototype
      • 设定创建出的对象保存在spring容器中,是一个非单例的对象
      • 懒汉多例

bean生命周期属性

  • init-method,destroy-method

    • 定义bean对象在初始化或销毁时完成的工作
  • 格式

    <bean init-method="init" destroy-method="destroy"></bean>
    
  • 注意事项

    • 当scope=“singleton”时,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次

    • 当scope=“prototype”时,spring容器要创建同一类型的多个对象,init方法在每个对象创建时均执行一次

    • 当scope=“singleton”时,关闭容器会导致bean实例的销毁,调用destroy方法一次

    • 当scope=“prototype”时,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行

加载第三方资源

如果知道类的全限定名可以通过直接配置的方式,但是有些类是没有类名的。例如:匿名内部类和动态代理类。可以通过工厂方式进行IOC配置。

工厂方式
1). 静态工厂 : 创建对象的方法是静态
2). 实例工厂 : 创建对象的方法是非静态的

​ 一般用来配置其他框架的bean

  • 静态工厂

    • bean标签属性factory-bean

    • 作用:定义bean对象创建方式,使用静态工厂的形式创建bean

    • 格式

      <bean id="bean标识" class="FactoryClassName" factory-method="factoryMethodName"></bean>
      
      • class属性必须配置成静态工厂的类名
      • method是工厂中用于获取对象的静态方法名
    • 要求:方法必须是静态的

    • 原理:反射获取静态方法,调用静态方法获取目标对象

  • 实例工厂

    • bean标签属性factory-bean,factory-method

    • 格式

      <bean id="实例工厂标识" class="实例工厂全限定名"/>
      <bean id="bean标识" factory-bean="实例工厂标识" factory-method="factoryMethodName"></bean>
      
    • 原理:获取目标对象的方法是非静态的,需要先通过反射获取实例工厂对象,然后通过反射获取非静态方法,继而调用非静态方法获取目标对象

EL表达式

el : expression language 表达式语言

  1. ${}
    ${} 用于加载外部文件指定的Key值 (在下一节课的properties文件中演示)
  2. #{}
    #{} 强调的是把内容赋值给属性
  • Spring提供了对EL表达式的支持,统一属性注入格

  • 类型:属性值

  • 归属:value属性值

  • 作用:为bean注入属性值

  • 格式:

    <property value="EL"></bean>
    
  • 注意:所有属性值不区分是否引用类型,统一使用value赋值

    • 提供配置文件路径

      xml配置方式:
      <context:property-placeholder location="classpath:data.properties"/>
      注解配置方式:
      @PropertySource(value = "classpath:filename.properties")
      
    • 常量 #{10} #{3.14} #{2e5} #{‘itcast’}

    • 引用bean #{beanId}

    • 引用bean属性 #{beanId.propertyName}

    • 引用bean方法 beanId.methodName().method2()

    • 引用静态方法 T(java.lang.Math).PI

    • 运算符支持 #{3 lt 4 == 4 ge 3}

    • 正则表达式支持 #{user.name matches‘[a-z]{6,}’}

    • 集合支持 #{likes[3]}

      • 示例

         <context:property-placeholder location="classpath:data.properties"/>
        <bean id="myDate" class="java.util.Date"/>
        <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
        <bean id="userService" class="com.itheima.service.impl.UserServiceImpl2">
            <!-- <property name="name" value="zs"/>
               <property name="age" value="18"/>
               <property name="date" ref="myDate"/>
               <property name="dao" ref="userDao"/>-->
            <property name="name" value="#{'zs'}"/>
            <property name="age" value="#{18}"/>
            <property name="date" value="#{myDate}"/>
            <property name="dao" value="#{userDao}"/>
        </bean>
        

DI依赖注入

set注入

  • bean子标签property

  • 格式

    <bean>
    	<property name="propertyName" value="propertyValue" />
        <property name="propertyName" ref="propertyValue" 
    </bean>
    
  • name

    • 对应bean中的属性名
    • 要求该属性必须提供可访问的set方法
  • value

    • 设定非引用类型(8大基本类型和String)
    • 不能与ref同时使用
  • ref

    • 设定引用类型
    • 不能与value同时使用
  • 注意:一个bean可以有多个property标签

  • 原理:反射获取对象的setter方法,然后调用setter方法给属性赋值

构造器注入

  • bean子标签constructor-arg

  • 作用:使用构造方法的形式为bean提供资源,兼容早期遗留系统的升级工作

  • name

    • 对应bean中的构造方法所携带的参数名
  • value

    • 设定非引用类型构造方法参数对应的值,不能与ref同时使用
  • ref

    • 设定引用类型构造方法参数对应bean的id ,不能与value同时使用
  • type

    • 设定构造方法参数的类型,用于按类型匹配参数或进行类型校验
  • index

    • 设定构造方法参数的位置,用于按位置匹配参数,参数index值从0开始计数
  • 注意:

    • 需要有参构造
    • 一个bean可以有多个constructor-arg标签
  • 原理:反射获取类的构造方法,通过构造器创建对象,完成注入

集合类型注入

  • 子标签名称:array,list,set,map,props

  • 父标签为property标签 或 constructor-arg标签

  • 作用:注入集合数据类型属性

  • array

    • 格式

      <property name="arr">
          <array>
              <value>12345</value>
              <value>66666</value>
          </array>
      </property>
      
  • list

    • 格式

      <property name="al">
          <list>
              <value>12345</value>
              <value>66666</value>
          </list>
      </property>
      
  • set

    • 格式

       <property name="hs">
           <set>
               <value>12345</value>
               <value>66666</value>
           </set>
      </property>
      
  • map

    • 格式

      <property name="hm">
          <map>
              <entry key="name" value="12345"/>
              <entry key="value" value="66666"/>
          </map>
      </property>
      
  • props

    • 格式

      <property name="properties">
          <props>
              <prop key="username">itheima666</prop>
              <prop key="passwo">666666</prop>
          </props>
      </property>
      

注解配置

优点:书写简单

缺点:配置需要基于源代码,而第三方框架提供的代码都是字节码。为达成目的,可能会将xml方式中很简单的书写方式变得复杂

全注解方式获取bean

AnnotationConfigApplicationContext

  • 加载纯注解格式上下文对象,需要使用AnnotationConfigApplicationContext

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    

半注解方式启用注解

注解配置和xml配置可以混合使用

  • 启动注解扫描,加载类中配置的注解项

    <context:component-scan base-package="packageName"/>
    
  • 说明:

    • 在进行包扫描时,会对配置的包及其子包中所有文件进行扫描

    • 扫描过程是以文件夹递归迭代的形式进行的

    • 扫描过程仅读取合法的java文件

    • 扫描时仅读取spring可识别的注解

    • 扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器

配置类

  • 代码

    @Configuration
    @ComponentScan("scanPackageName")
    public class SpringConfig{
    }
    
  • @Configuration、@ComponentScan

    • 类注解
    • @Configuration设置当前类为spring核心加载类、
    • @ComponentScan定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中

bean的定义

  • @Component @Controller @Service @Repository

    • 类注解
    • 作用:设置该类为spring管理的bean
    • 示例:
    @Component
    public class ClassName{}
    

bean的作用域

  • @Scope
    • 类注解
    • value(默认):定义bean的作用域,默认为singleton

bean的生命周期

  • @PostConstruct、@PreDestroy

    • 方法注解

    • 作用:设置该方法作为bean对应的生命周期方法

    • 示例

      @PostConstructpublic void init(){ 			   	System.out.println("init...");
      }
      @PreDestroy void destory(){ 	System.out.println("destory...");
      }
      

加载properties文件

  • @PropertySource

    • 类注解

    • 作用:加载properties文件中的属性值

    • 示例

      @PropertySource(value = "classpath:filename.properties")
      public class ClassName {
          @Value("${propertiesAttributeName}")
          private String attributeName;
      }
      

非引用类型注入

  • @Value

    • 属性注解、方法注解

    • 作用:设置对应属性的值或对方法进行传参

    • 示例

      @Value("${jdbc.username}")
      private String username;
      
    • 原理:反射获取注解的属性值,赋给当前的属性

    • 说明

      • value值仅支持非引用类型数据,赋值时对方法的所有参数全部赋值

      • value值支持读取properties文件中的属性值,通过类属性将properties中数据传入类中

      • value值支持SpEL

      • @value注解如果添加在属性上方,可以省略set方法(属性注入原理不是反射获取setter方法)

引用类型注入

  • @Autowired、@Qualifier

    • 属性注解、方法注解

    • 作用:设置对应属性的对象或对方法进行引用类型传参

    • 示例

      @Autowired(required = true)
      @Qualifier("userDao")
      private UserDao userDao;
      
    • 说明

      • @Autowired
        1.自动配置, 如果IOC容器中只有一个此类型的对象,会自动配置
        2.如果IOC容器中有多个此类型的对象,会自动配置跟变量名一致的标识的bean
        3.如果IOC容器中有多个此类型的对象,且跟变量都不一致,需要手动设置@Qualifier
        • 属性: 了解,一般不写
          required:
          true: 此对象必须注入成功,若不成功则报错. 默认值
          false: 可以注入不成功,此对象为null

加载第三方资源

工厂模式: 框架中的类(第三方资源)是字节码,不是源码,无法使用注解注入

所以用工厂模式进行配置这个bean

  • @Bean

    • 方法注解

    • 作用:设置该方法的返回值作为spring管理的bean

    • 相关属性

      • value(默认):定义bean的访问id
    • 说明:

      • 因为第三方bean无法在其源码上进行修改,使用@Bean解决第三方bean的引入问题
      • 该注解用于替代XML配置中的静态工厂与实例工厂创建bean,不区分方法是否为静态或非静态
      • @Bean所在的类必须被spring扫描加载,否则该注解无法生效

第三方bean配置与管理

  • @Import

    • 类注解

    • 作用:导入第三方bean作为spring控制的资源

    • 示例

      @Configuration
      @Import(OtherClassName.class)
      public class ClassName {
      }
      
    • 说明:

      • @Import注解在同一个类上,仅允许添加一次,如果需要导入多个,使用数组的形式进行设定

      • 在被导入的类中可以继续使用@Import导入其他资源

      • @Bean所在的类可以使用导入的形式进入spring容器,无需声明为bean

AOP

AOP(Aspect Oriented Programing)面向切面编程,一种编程范式,指导开发者如何组织程序结构。AOP 广泛应用于处理一些具有横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。

AOP要解决的问题是用一个“横切面”的方式,来统一处理很多对象都需要的,相同或相似的功能,减少程序里面的重复代码,让代码变得更干净,更专注于业务。

spring的AOP的实现方式

  1. 当Bean实现接口时,Spring就会用JDK的动态代理
  2. 当Bean没有实现接口时,Spring使用CGLib来实现
    • JDK8之后, JDK动态代理效率高于CG lib
  3. 备注: 开发者可以在spring中强制使用CGLib
    • 在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

动态代理

代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

Spring AOP动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。

  • JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
  • 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类(通过修改字节码来实现代理)。 注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 jdk和cglib动态代理来共同实现我们的aop面向切面的功能。

AOP相关概念

  1. Target(目标对象)
    • 要被增强的对象(被代理类的对象)
  2. Proxy(代理对象)
    • 对目标对象的增强对象 (生成的代理类对象)
  3. Joinpoint(连接点)
    • 目标对象中的可被增强的所有方法(被代理类中的所有方法)
      • JDKProxy中被代理类不可被增强方法 (父接口没有的方法)
      • CGlib中被代理类不可被增强方法(比如用final修饰的方法)
  4. Pointcut(切入点)
    • 要被增强的方法(被代理类中要增强的方法)
      • 切入点一定是连接点
      • 但连接点不一定是切入点
  5. Advice(通知/增强)
    • 通知是增强的那段代码形成的方法
      • 前置通知 在方法之前进行增强
      • 后置通知 在方法之后进行增强
      • 异常通知 在方法异常进行增强
      • 最终通知 最终执行的方法进行增强
      • 环绕通知 单独使用(以上所有通知)
  6. Aspect(切面)
    • 切面= 切入点+通知
    • 目标方法和增强方法合到在一起 叫做切面
  7. Weaving(织入)
    • 在运行过程中,spring底层将通知和切入点进行整合的过程,称为织入

AOP注解配置

依赖

<!-- spring核心jar包,已经依赖的AOP的jar -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<!-- TODO: 切入点表达式 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

示例代码

image-20230226161844315

原始设计(需要增强的类)
package com.itheima.service;
public interface AccountService {

    //有返回值
    String findAll();
    //有参数
    void insert(String str);

    void update();
    void delete();
}
package com.itheima.service.impl;

import com.itheima.service.AccountService;
@Service
public class AccountServiceImpl implements AccountService {
    @Override
    public String findAll() {
        System.out.println("findAll.............");
        return "result: 查询结果";
    }

    @Override
    public void insert(String str) {
//        int i = 1/0;
        System.out.println("insert............." + str);
    }

    @Override
    public void update() {
        System.out.println("update.............");
    }

    @Override
    public void delete() {
        System.out.println("delete.............");
    }
}
配置类
package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

//配置类
@Configuration
//包扫描
@ComponentScan("com.itheima")
// 开启aop注解支持
@EnableAspectJAutoProxy
public class SpringConfig {
}
代理类书写方式一
package com.itheima.aspect;

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

//IOC配置: 当前bean需要加载到ioc容器中
@Component
//aop配置 : 声明当前类是切面类 (切面=切入点+通知)
@Aspect
public class MyAdvice {

    /*
        指定切入点 : 指定AccountServiceImpl类中所有方法为切入点 (切入点表达式)
        1. @Pointcut配置切入点
        2. 需要写在一个三无方法上 (无参无返回值空方法体)
     */
    @Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
    public void pt(){
    }

    @Before("pt()")
    public void before(){
        System.out.println("前置通知:11111111111111111");
    }

    @AfterReturning("pt()")
    public void afterReturning(){
        System.out.println("后置通知:2222222222222222");
    }

    @AfterThrowing("pt()")
    public void afterThrowing(){
        System.out.println("异常通知:3333333333333333333");
    }

    @After("pt()")
    public void after(){
        System.out.println("最终通知:44444444444444444444444");
    }

}

代理类书写方式二

使用环绕通知@Around

环绕通知:在原始方法执行前后均有对应执行,还可以阻止原始方法的执行

package com.itheima.aspect;

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

/*
    TODO: 环绕通知
 */
@Component
@Aspect
public class MyAdvice2 {

    @Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
    public void pt(){
    }
    
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object result = null;
        try {
            System.out.println("前置通知:11111111111111111");
            result = pjp.proceed();
            System.out.println("后置通知:2222222222222222");
        } catch (Throwable throwable) {
//            throwable.printStackTrace();
          	System.out.println("异常通知:3333333333333333333");
        } finally {
            System.out.println("最终通知:44444444444444444444444");
        }
        return result;
    }
}

测试类
//spring整合junit
@RunWith(SpringJUnit4ClassRunner.class)
//加载注解配置类
@ContextConfiguration(classes = SpringConfig.class)
public class MyApp {
    @Autowired
    AccountService service;
    @Test
    public void test01(){
        System.out.println(service);
        System.out.println(service.getClass());
        //配置对insert方法具有增强效果
        service.insert("参数");
    }
}
运行结果
before: 前置通知
insert.............参数
afterReturning:后置通知
after: 最终通知

相关注解

  • @Aspect配置当前类为切面类(切入点+通知)

  • @PonitCut:定义公共的切入点

  • 切入点表达式

    1. 完整写法:execution(方法的修饰符 方法的返回值 类的全限定名.方法名(参数))
    2. 支持通配符的写法:
    	1) *   表示任意字符串
    	2) ..  表示任意重复次数
    3. 规则	
        1. 方法的修饰符可以省略:
        2. 返回值可以使用*号代替:标识任意返回值类型
        3. 包名可以使用*号代替,代表任意包(一层包使用一个*)
        4. 使用..配置包名,标识此包以及此包下的所有子包
        5. 类名可以使用*号代替,标识任意类
        6. 方法名可以使用*号代替,表示任意方法
        7. 可以使用..配置参数,任意参数
    
  • 通知类型

    @Before: 前置通知
    @AfterReturning:后置通知
    @AfterThrowing :异常通知
    @After :最终通知
    @Around:环绕通知
        
    1. 前置通知
    原始方法(切入点)执行前执行,如果通知中抛出异常,阻止原始方法运行
    应用:数据校验
    2. 后置通知:原始方法执行后执行,无论原始方法中是否出现异常,不再执行
    应用:返回值相关数据处理
    3. 抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
    应用:对原始方法中出现的异常信息进行处理
    4. 最终通知:无论如何最终都执行
    应用:现场清理
    5. 环绕通知:在原始方法执行前后均有对应执行,还可以阻止原始方法的执行
    应用:十分强大,可以做包括四种类型的所有事情
    
    1. 环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用
              public Object around(ProceedingJoinPoint pjp) throws Throwable {
                  Object ret = pjp.proceed();
                  return ret;
              }
    2. 环绕通知方法相关说明:
      1).方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为void,通知方	也可以设定返回值类型为void,最终返回null
      2). 方法需在第一个参数位置设定ProceedingJoinPoint对象(代表切入点),通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行
      3). 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛出Throwable对象,封装原始方法中可能出现的异常信息
    
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值