美文网首页JavaWeb
Spring之IoC与AOP

Spring之IoC与AOP

作者: 不知名码农 | 来源:发表于2019-03-04 19:15 被阅读10次

    前言

    spring框架的释义:

    Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。

    为什么Spring用来完成EJB完成的事情,那么这个EJB是什么玩意?概念:

    EJB:Enterprise JavaBean,对于商务软件来说,其核心部分就是她的业务逻辑,业务逻辑抽象了整个商务过程的流程,并使用计算机语言将他们实现。
    ……
    J2EE对于这个问题的处理方法是将业务逻辑从客户端软件中抽取出来,封装在一个组件中。这个组件运行在一个独立的服务器上,客户端软件通过网络调用组件提供的服务来实现业务逻辑,而客户端软件的仅仅负责发送调用请求和显示处理结果。在J2EE中,这个运行在一个独立的服务器上,并封装了业务逻辑的组件就是EJB组件。

    从上面的概念中,可以理解为EJB概念说的就是C/S软件,简单来说EJB 就是将那些"类"放到一个服务器上,用C/S 形式的软件客户端对服务器上的"类"进行调用。
    感兴趣的可以看下这篇文章https://blog.csdn.net/jojo52013145/article/details/5783677

    概念

    Spirng中的两个核心概念:依赖注入DI(dependency injection)和面向切面编程AOP(aspect-oriented programming)。

    为了降低Java开发的复杂性,Spring采取了以下四种关键策略:

    • 基于POJO(Plain Old Java Object)的轻量级和最小侵入性编程;
    • 通过依赖注入和面向接口实现松耦合;
    • 基于切面和管理进行声明式编程;
    • 通过切面和模板减少样板式代码。

    依赖注入

    传统的做法,每个对象负责管理它所依赖的对象的引用,这将会导致高度耦合和难以测试的代码。比如说,对象A要使用对象B,则在对象A中来控制对象B,如:

    public class A {
        private B b;
    
        public A () {
            this.b = new B();
        }
    
        public String sayHello() {
            return b.sayHello(userName);
        }
    }
    
    public class B {
        public String sayHello(String userName) {
            return "Hello "+ userName;
        }
    }
    

    耦合具有两面性,一方面,紧密耦合的代码难以测试、难以复用、难以理解;另一方面,一定程度的耦合又是必须的——完全没有耦合的代码什么也做不了。总之,耦合是必须的,但是应当被合理地管理。

    创建应用组件之间协作的行为通常称为装配。Spring有多种装配bean的方式,采用XMl、使用Java进行配置。

    通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。
    依赖注入的方式:

    1. 构造器注入;
    2. setter注入;
    3. 基于注解注入;

    一、构造器注入

    如:

    // 构造器注入
    public class A {
        private B b;
    
        public A(B b) {
            this.b = b;
        }
    
        public String sayHello() {
            return b.sayHello(userName);
        }
    }
    

    xml方式

    <!-- 注册A -->
    <bean id="a" class="com.lucifer.spring.di.A">
        <constructor-arg ref="b"></constructor-arg>
    </bean>
    
    <!-- 注册B -->
    <bean id="b" class="com.lucifer.spring.di.B"></bean>
    

    二、setter注入

    <!-- 注册A -->
    <bean id="a" class="com.lucifer.spring.di.A">
        <!-- 注意:这两种写法都可以,Spring将name值得每个单词的首字母转换为大写,再在前面拼上set构成set方法名,然后去对应的类中查找这个方法,通过反射调用实现注入,仅按照此种规则匹配,即对应的成员变量命名不做约束。也就是说 UserName 和 userName 都能匹配上 -->
        <!-- 写法一 -->
        <!-- <property name="UserName" ref="b"></property> -->
        <!-- 写法二 -->
        <property name="userName" ref="b"></property>
    </bean>
    
    <!-- 注册B -->
    <bean id="b" class="com.lucifer.spring.di.B"></bean>
    
    public class UserService implements IUserService {
    
        private String userName123;
        
        public String sayHello(String userName) {
            return "Hello "+ userName;
        }
    
        public void setUserName(String userName) {
            this.userName123 = userName;
        }
    
    }
    

    三、基于注解的注入

    bean的属性autowire,autowire主要有三个属性值:constructor,byName,byType。

    • constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
    • byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词的首字母必须小写,这个和手动set注入有点不同。
    • byType:查找所有的set方法,将符合参数类型的bean注入。

    注册bean的注解有以下几种:

    1. @Component:用于注册所有的bean;
    2. @Repository:用于注册dao层的bean;
    3. @Controller:用于注册控制层的bean;
    4. @Service:用于注册服务层的bean;

    常见的问题,@Resource和@Autowired之间的区别是什么?
    描述依赖关系的主要有两种:

    • @Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有再以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(Spring注解)指定某个具体名称的bean;
    • @Autowired:spring注解,默认是以byType的方式去匹配类型相同的bean,如果匹配到一个,那么就直接注入该bean,无论要注入的bean的那么是什么;如果匹配到多个,就会调用DefaultListableBeanFactory的determineAutowireCandidate方法来决定具体注入哪个bean。

    determineAutowireCandidate方法的逻辑是:

    1. 先找Bean上有@Primary注解的,有则直接返回bean的name;
    2. 再找Bean上有@Order,@PriorityOrder注解的,有则返回bean的name;
    3. 最后再以名称匹配(ByName)的方式去查找相匹配的bean。
    4. 没有找到的话就抛出异常。

    还有一点要注意:如果使用了 @Qualifier 注解,那么当自动装配匹配到多个 bean 的时候就不会进入 determineAutowireCandidate 方法,而是直接查找与 @Qualifer 指定的 bean name 相同的 bean 去注入,找到了就直接注入,没有找到则抛出异常。

    注意
    如果通过set方法注入属性,那么Spring会通过默认的无参构造方法来实例化对象,所以如果在类中重写带有参数的构造方法,一定要把无参构造方法也写上,否则spring没有办法实例化对象,导致报错。

    问题

    1.依赖注入(DI)和控制反转(IoC)

    • 谁控制谁,控制什么: 传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

    • 为何是反转,哪些方面反转了: IoC:Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想,传统应用程序是由我们自己在对象中主动去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象,为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

    理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”

    • 谁依赖谁: 应用程序依赖于IoC容器;
    • 为什么需要依赖: 应用程序需要IoC容器来提供对象需要的外部资源;
    • 谁注入谁: 很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
    • 注入了什么: 就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

    其实IoC和DI是同一个概念的不同角度描述,由于控制反转概念比较模糊(可能只是理解为容易控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
    我认为可以这么理解:使用依赖注入来实现了控制反转。

    面向切面编程

    DI能够让相互协作的软件组件保持松散耦合,而面向切面编程(aspect-oriented programming)允许把遍布在应用各处的功能分离出来形成可重用的组件。定义切点的表达式的语法采用的是AspectJ的切点表达式语言。

    一、AOP的相关概念

    1. Aspect(切面):通知和切点共同定义了切面的全部内容;
    2. Joint Point(连接点):程序执行过程中明确的点,是在应用执行过程中能够插入切面的一个点,切面代码利用这些点插入到应用的正常流程之中并添加新的行为;
    3. Advice(通知):AOP在特定的切入点上执行的增强处理,五个通知类型:
      1. 前置通知(Before):在目标方法被调用之前调用通知功能,@Before只需要指定切入点表达式即可;
      2. 后置通知(After):在目标方法完成之后调用通知,此时不关心方法的输出是什么。不论拦截的方法是否有异常。在目标方法完成之后做增强,无论目标方法什么时候成功完成。@After可以指定一个切入点表达式;
      3. 环绕通知(Around):需要放行操作。通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。环绕通知是最重要的通知类型,像事务、日志等都是环绕通知;
      4. 返回通知(After-returning):在目标方法成功执行之后调用通知,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值;
      5. 异常通知(After-throwing):主要用来处理程序中未处理的异常,在目标抛出异常后调用通知,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象;
    4. Pointcut(切入点):带有通知的连接点,在程序中主要体现为书写切入点表达式。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”;
    5. AOP代理:AOP框架创建的对象,代理就是目标对象的增强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类;
    6. 织入(Weaving):实现AOP代理所声明的功能,即把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期中,可以在编译期、类加载期、运行期进行织入;
    7. 关注点:切面的具体功能方法被称为关注点。
    注解 通知
    @After 通知方法会在目标方法返回或抛出异常后调用
    @AfterReturning 通知方法会在目标方法返回后调用
    @AfterThrowing 通知方法会在目标方法抛出异常后调用
    @Around 通知方法会将目标方法封装起来
    @Before 通知方法会在目标方法调用之前执行

    Spring提供了4种类型的AOP支持:

    • 基于代理的经典Spring AOP;
    • 纯POJO切面;
    • @AspectJ注解驱动的切面;
    • 注入式AspectJ切面(适用于Spring各版本);

    前面三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截;也正是因为Spring基于动态代理,所以Spring只支持方法连接点。

    AOP主要的作用是:日志记录,性能统计,安全控制,事务处理,异常处理,权限登录等等。AOP代理由Spring的IoC容器负责生成、管理,其依赖关系也由IoC容器负责管理。

    二、Spring借助AspectJ的切点表达式语言来定义Spring切面

    Spring AOP所支持的AspectJ切点指示器

    AspectJ指示器 描述
    arg() 限制连接点匹配参数为指定类型的执行方法
    @args() 限制连接点匹配参数由指定注解标注的执行方法
    execution() 用于匹配是连接点的执行方法
    this() 限制连接点匹配AOP代理的bean引用为指定类型的类
    target 限制连接点匹配目标对象为指定类型的类
    @target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
    within() 限制连接点匹配指定的类型
    @within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
    @annotation 限定匹配带有指定注解的连接点
    execution(* concert.Performance.perform(..))
    /* 
    *:返回任意类型;
    concert.Performance:方法所属的类;
    perform:方法;
    ..:使用任意参数;
    execution:在方法执行时触发;
    */
    

    相关资料


    欢迎扫描下方二维码,关注weyoung公众号,一起交流学习~~

    image

    更多联系方式

    平台 链接
    预览项目: https://nelucifer.gitee.io/
    个人微信公众号: weyoung
    segmentfault: https://segmentfault.com/u/nelucifer
    CSDN: https://me.csdn.net/wlx001
    简书: https://www.jianshu.com/u/99211cc23788
    掘金: https://juejin.im/user/59b08c575188250f4850e80e

    相关文章

      网友评论

        本文标题:Spring之IoC与AOP

        本文链接:https://www.haomeiwen.com/subject/umcwuqtx.html