Spring之IOC和AOP及DI

一、IOC(Inversion of Control)控制反转

1. 概念

IoC Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。

Spring通过IoC容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。

IoC容器是Spring框架中最重要的核心组件之一,它贯穿了Spring从诞生到成长的整个过程。

2. 实现方式

此处先要想清楚一个事情,控制反转就是把对象的实例化初始化交给Spring去管理,而不是我们自己用到的时候自己去实例化,那么它时如何管理呢?换句话说,Spring如何利用IOC思想创建对象给对象赋值呢?

此处只是说明大致过程,详细了解 Spring IOC 容器源码分析

  1. 通过解析xml或者注解的方式拿到类的相关信息

  2. 通过BeanFactory工厂,在工厂内部利用反射完成类的实例化和初始化

  3. 并将结果放入一个ConcurrentHashMap<String, BeanDefinition>中,其中 Key默认是类名首字母小写 , BeanDefinition存的是类的定义(描述信息)

3. 注意事项

  • Spring利用IOC容器进行Bean的容器管理,IOC 容器底层就是对象工厂

  • Spring 使用工厂模式可以通过 BeanFactoryApplicationContext 创建 bean 对象

    • BeanFactory::IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用,加载配置文件时候不会创建对象,在获取对象(使用) 才去创建对象

    • ApplicationContext:BeanFactor接口的子接口,提供更多更强大的功能,一般由开发人员进行使用,加载配置文件时候就会对象创建

  • ApplicationContext 的三个实现类:

    • ClassPathXmlApplication:把上下文文件当成类路径资源。

    • FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。

    • XmlWebApplicationContext:从 Web 系统中的 XML 文件载入上下文定义信息。

二、DI(Dependency Injection):依赖注入

通过IOC将bean交给Spring进行管理了,我们如何使用Spring管理的bean呢?通过依赖注入

依赖注入指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

注入的方式参考 Spring之基于注解的Bean管理

三、AOP(Aspect Oriented Programming):面向切面编程

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1. 代理

  • 动态代理分为JDK动态代理和cglib动态代理

  • 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理

  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口

  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类

  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。

  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。

  • AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

2. 如何使用

@Aspect //表示这个类是一个切面类
@Component //保证这个切面类能够放入IOC容器
public class LogAspect {
    
    //前置通知:使用@Before注解标识,在被代理的目标方法前执行
    @Before("execution(public int com.litecode.aop.annotation.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }
 
    //后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行
    @After("execution(* com.litecode.aop.annotation.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名:"+methodName);
    }

    //返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行
    //属性returning,用来将通知方法的某个形参,接收目标方法的返回值(可省略)
    @AfterReturning(value = "execution(* com.litecode.aop.annotation.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }

    //使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行
    //属性throwing,用来将通知方法的某个形参,接收目标方法的异常(可省略)
    @AfterThrowing(value = "execution(* com.litecode.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }
    
    //环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
    @Around("execution(* com.atguigu.aop.litecode.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标对象(连接点)方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
    
}

3. 切入点表达式

3.1切入点表达式语法

3.2重用切入点表达式
  • ①声明

@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}
  • ②在同一个切面中使用

@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
  • ③在不同切面中使用

@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}