Spring IoC/AOP原理

IoC

  • Inversion of Control,控制反转。

  • Spring核心容器的主要组件是Bean工厂(BeanFactory),Bean工厂使用控制反转(IoC)模式来降低程序代码之间的耦合度,并提供了面向切面编程(AOP)的实现。

  • 控制反转,就是将设计好的对象交给容器控制。创建对象的控制权,被反转到了Spring框架上。

    通常,我们实例化一个对象时,都是使用类的构造方法来new一个对象,这个过程是由我们自己来控制的,而控制反转就把new对象的工交给了Spring容器。

  • IoC的主要实现方式有两种:依赖查找、依赖注入。

  • 依赖注入(Dependency Injection):一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

    组件不做定位查询,只提供标准的Java方法让容器去决定依赖关系。容器全权负责组件的装配,把符合依赖关系的对象通过Java Bean属性或构造方法传递给需要的对象。
    由IoC容器动态地将某个对象所需要的外部资源(包括对象、资源、常量数据)注入到组件(Controller, Service等)之中。

  • 依赖查找:主要是容器为组件提供一个回调接口和上下文环境。这样一来,组件就必须自己使用容器提供的API来查找资源和协作对象,控制反转仅体现在那些回调方法上,容器调用这些回调方法,从而应用代码获取到资源。

  • Spring依赖注入的方式主要有四个,基于注解注入方式、set注入方式、构造器注入方式、静态工厂注入方式。推荐使用基于注解注入方式,配置较少,比较方便。

实现原理
  • 使用的技术:1、xml配置文件;2、dom4j解析XML文件;3、工厂设计模式;4、反射

    xml文件的作用:一是配置信息,二是存储信息。来让容器知道需要创建的对象与对象的关系。
    服务器得到这些配置信息要通过解析工具,dom4j就是这样的一个解析框架,它可以修改其中的文件。dom4j是树形结构,通过节点来解析。

    在这里插入图片描述
    当web容器启动的时候,spring的全局bean的管理器会去xml配置文件中扫描的包下面获取到所有的类,并根据使用的注解,进行对应的封装,封装到全局的bean容器中进行管理,一旦容器初始化完毕,beanID以及bean实例化的类对象信息就全部存在了。
    现在当在某个service里面调用另一个bean的某个方法的时候,只需要依赖注入进来另一个bean的Id即可,调用的时候,spring会去初始化完成的bean容器中获取,如果存在就把依赖的bean的类的实例化对象返回,然后就可以调用依赖的bean的相关方法或属性等;

在这里插入图片描述

  • 工厂模式:工厂模式可将Java对象的调用者从被调用者的实现逻辑中分离出来。属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
    简单的说就是不用自己new对象了,对象的实例化都交给工厂来完成,需要对象的时候直接问工厂拿一个就行。spring IOC与工厂模式并不是完全相同的,最大的不同在于普通的工厂模式内部还是使用new来创建对象,但是spring IOC是用反射来创建对象(更大的灵活性)。
  • Spring Bean的创建是典型的工厂模式。(spring将bean创建好放到容器中,等到需要的时候将实例化对象返回)。一系列的Bean工厂,IOC容器为开发者管理对象间的依赖关系提供了很多便利和基础服务。

【转载】Spring IOC与工厂模式

  • 写一个bean工厂模拟spring ioc:
    //文件名:bean.properties;spring中用的是XML,这里为了简化就用properties,原理都是一样的:
    circle=com.demo.Circle
    rectangle=com.demo.Rectangle
    square=com.demo.Square
    
    
    //文件名:BeanFactory.java
    package com.demo;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class BeanFactory {
        //配置对象(类比spring IOC容器中的Bean定义注册表)
        private static final Properties props;
        //保存创建好的对象的容器,与类名组成key-value对(类比spring IOC容器中的Bean缓存池)
        private static Map<String, Object> beans;
        static {
            props = new Properties();
            //通过类加载器读入配置文件
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            try {
                //加载配置文件到配置对象
                props.load(in);
                //初始化容器
                beans = new HashMap<>();
                Enumeration<Object> keys = props.keys();
                //循环遍历配置对象中的所有的类名(key)
                while (keys.hasMoreElements()){
                    String key = keys.nextElement().toString();
                    //通过类名拿到全类名(value)
                    String beanPath = props.getProperty(key);
                    //利用全类名反射创建对象
                    Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
                    //将对象放入容器中
                    beans.put(key, value);
                }
            } catch (IOException e) {
                throw new ExceptionInInitializerError("初始化properties失败!程序终止!");
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        public static Object getBean(String beanName){
            //从容器中获取对象
            return beans.get(beanName);
        }
    }
    
    
  • Spring IOC容器接口继承图,BeanFactory是简单容器,他实现了容器的基本功能,典型方法如 getBean、containsBean等。ApplicationContext是应用上下文,他在简单容器的基础上,增加上下文的特性。我们开发时一般都是使用ApplicationContext接口,因为他的功能比BeanFactory更强大。ApplicationContext有好多的实现类,最常用的是——ClassPathXmlApplicationContext。
    在这里插入图片描述

AOP

  • Aspect-Oriented Programming
  • 面向切面编程(AOP)将程序运行过程分解成各个切面。
    AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面。
  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 (横向切入系统,提取各个模块可能都要重复操作的部分(如:权限检查,日志记录等等))
  • 日志记录,性能统计,安全控制,事务处理,异常处理,权限控制,参数校验等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
  • 让关注点代码(重复代码)与业务代码分离(对很多功能都有的重复代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”。)
实现原理
  • 通过动态代理。由 JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)实现。动态代理就是在不修改原有类对象方法的源代码基础上,通过代理对象实现原有类对象方法的增强,也就是拓展原有类对象的功能。(比如业务A和业务B现在需要一个相同的操作,传统方法我们可能需要在A、B中都加入相关操作代码,而应用AOP就可以只写一遍代码,A、B共用这段代码。并且,当A、B需要增加新的操作时,可以在不改动原代码的情况下,灵活添加新的业务逻辑实现。)

    JDK动态代理:只针对接口操作。
    CGLIB:可以针对没有接口的java类和有接口的java类。

【转载】深入理解Spring两大特性:IoC和AOP

  • 切面(Aspect):共有功能的实现。如日志切面、权限切面、验签切面等。在实际开发中通常是一个存放共有功能实现的标准Java类。当Java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面。

  • 通知(Advice):切面的具体实现。就是要给目标对象织入的事情。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分。

    前置通知:在某连接点之前执行的通知
    后置通知:在一个匹配的方法返回的时候执行。

  • 连接点(JoinPoint):程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出等。Spring只支持方法级的连接点。一个类的所有方法前、后、抛出异常时等都是连接点。

  • 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

    execution(modifier-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
    修饰符匹配 modifier-pattern? 例:public private
    返回值匹配 ret-type-pattern 可以用 * 表示任意返回值
    类路径匹配 declaring-type-pattern? 全路径的类名
    方法名匹配 name-pattern 可以指定方法名或者用 * 表示所有方法;set* 表示所有以set开头的方法
    参数匹配 (param-pattern) 可以指定具体的参数类型,多个参数用“,”分隔;可以用 * 表示匹配任意类 型的参数;可以用 (…) 表示零个或多个任意参数
    异常类型匹配throws-pattern? 例:throws Exception

    比如,在上面所说的连接点的基础上,来定义切入点。我们有一个类,类里有10个方法,那就产生了几十个连接点。但是我们并不想在所有方法上都织入通知,我们只想让其中的几个方法,在调用之前检验下入参是否合法,那么就用切点来定义这几个方法,让切点来筛选连接点,选中我们想要的方法。切入点就是来定义哪些类里面的哪些方法会得到通知。

  • 目标对象(Target):那些即将切入切面的对象,也就是那些被通知的对象。这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入。

  • 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入共有功能后产生的对象。

  • 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制动态代理机制来动态实现。

使用场景举例
  • 一个简单的切面类

    @Aspect      //该注解被AOP容器识别为切面
    @Component
    public class SignAop {
     	//切入点表达式:定义切面需要切入的位置,范围是controller包下所有的类的所有方法
        @Pointcut("execution(public * cn.wbnull.springbootdemo.controller.*.*(..))")  
        private void signAop() {
     
        }
     
        @Before("signAop()")         //前置通知
        public void doBefore(JoinPoint joinPoint) throws Exception {
            //code
        }
     
        @AfterReturning(value = "signAop()", returning = "params")   //后置通知
        public JSONObject doAfterReturning(JoinPoint joinPoint, JSONObject params) {
            //code
        }
    }
    
  • 异常处理。(SpringBoot开发详解-- 异常统一管理以及AOP的使用
    Spring全局异常处理
    使用 @ControllerAdvice ,将所有捕获的异常统一返回CommonResponse。

    到这里还没有用到AOP。接下来:使用接口若出现了异常,很难知道是谁调用接口,是前端还是后端出现的问题导致异常的出现,这时候AOP就发挥作用了,定义一个切面类:

    @Aspect
    @Component
    public class HttpAspect {
    
        private final static Logger LOGGER = LoggerFactory.getLogger(HttpAspect.class);
    
        @Autowired
        private ExceptionHandle exceptionHandle;
    
        @Pointcut("execution(public * com.zzp.controller.*.*(..))")
        public void log(){
    
        }
    
        @Before("log()")
        public void doBefore(JoinPoint joinPoint){
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            //url
            LOGGER.info("url={}",request.getRequestURL());
            //method
            LOGGER.info("method={}",request.getMethod());
            //ip
            LOGGER.info("id={}",request.getRemoteAddr());
            //class_method
            LOGGER.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName() + "," + joinPoint.getSignature().getName());
            //args[]
            LOGGER.info("args={}",joinPoint.getArgs());
        }
    
        @Around("log()")
        public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Result result = null;
            try {
    
            } catch (Exception e) {
                return exceptionHandle.exceptionGet(e);
            }
            if(result == null){
                return proceedingJoinPoint.proceed();
            }else {
                return result;
            }
        }
    
        @AfterReturning(pointcut = "log()",returning = "object")//打印输出结果
        public void doAfterReturing(Object object){
            LOGGER.info("response={}",object.toString());
        }
    }
    

    使用@Aspect来声明这是一个切面,使用@Pointcut来定义切面所需要切入的位置,这里是对每一个HTTP请求都需要切入。
    在进入方法之前使用@Before记录了调用的接口URL,调用的方法,调用方的IP地址以及输入的参数等。
    在整个接口代码运作期间,使用@Around来捕获异常信息,并用之前定义好的Result进行异常的返回,最后使用@AfterReturning来记录出參。

【参考文档】
SpringAOP原理分析
SpringIoc 实现原理
深入理解Spring两大特性:IoC和AOP
Spring IOC与工厂模式
SpringBoot开发详解-- 异常统一管理以及AOP的使用

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值