《Spring in action 4》(一)初识Spring

初识Spring

莫道君行早,更有早行人

本篇主要是简单的尝试一下Spring的两大功能,来感觉一下Spring的强大,后面将进行更加详细的介绍。java

spring的两大功能

​ 咱们都知道,Spring两大核心功能就是控制反转/依赖注入、面向切面编程。下面介绍一下两大功能。git

IoC/DI

Don't call me , I will call you!

控制反转(Inversion of Control)/依赖注入(Dependency Injection),简称IoC/DI.正则表达式

控制反转不是一种技术,而是一种设计思想:将原来程序须要什么对象本身建立 转变为 须要什么对象向IoC容器获取,建立对象的工做由原来程序自身控制,反转到了由IoC容器进行建立。把相关的控制权进行了反转,反转给了Spring IoC容器。spring

DI:Dependency Injection。即依赖注入。对象(组件)之间的依赖关系由IoC容器来进行决定。express

好比:编程

在UserController中须要调用UserService(暂不考虑接口设计)。则在UserController中须要其自身经过new来建立UserService对象。网络

以下:学习

UserService:测试

public class UserService{

    private PrintStream printStream;
    
    public UserService(PrintStream printStream){
        this.printStream = printStream;
    }
    public void sayHello(){
        printStream.println("sayHello!")
    }
}

UserController:this

public class UserController{
    private UserService userService;
    
    public UserController(){
        this.userService = new UserService(System.out);
    }
    
    public void sayHi(){
        userService.sayHello();
    }    
}

​ 在Spring中,程序的对象控制权由其自身反转到了Spring容器,也就是不须要应用程序来new对象。既然不须要应用程序自身来建立Bean了,那么程序在运行的过程当中,Bean从何而来呢?此时就是DI的show time了。

​ Spring中的DI正是来实现IoC的一种方式:Spring容器负责维护对象(Bean)之间的依赖关系,并经过DI来向对象中注入其所依赖的对象。

Xml方式

下面使用Spring的方式来设计:

public class UserService{
    
    private PrintStream printStream;
    
    public UserService(PrintStream printStream){
        this.printStream = printStream;
    }
    
    public void sayHello(){
        printStream.println("sayHello!")
    }
}

public class UserController{
    
    private UserService userService;
    
    public UserController(UserService userService){
        this.userService = userService;
    }
    public void sayHi(){
        userService.sayHello();
    }
}

spring.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 
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.ooyhao.spring.bean.UserService">
        <constructor-arg value="#{T(System).out}"/>
    </bean>

    <bean id="UserController" class="com.ooyhao.spring.bean.UserController">
        <constructor-arg ref="userService"/>
    </bean>
</beans>

测试类:

public void testXml(){
      ClassPathXmlApplicationContext ctx = 
                          new ClassPathXmlApplicationContext("spring.xml");
      UserController userController = ctx.getBean(UserController.class);
      userController.sayHi();
}

​ 若是须要面向接口设计的话,接口因为没法实例化,因此在编码的时候必须指定具体的实现类,如此一来,致使没法自由动态的切换实现类,耦合度过高。而Spring xml方式的话,实现类松耦合,简化了开发,后期若是须要修改的话,直接修改xml文件,不用修改代码。

Java配置类方式

SpringConfig配置类:

public class SpringConfig{
  
      @Bean
      public UserService userService(){
          UserService userService = new UserService(System.out);
          return userService;
      }
  
      @Bean
      public UserController userController(){
          UserController userController = new UserController(userService());
          return userController;
      }
  
}

测试类:

public void testJavaConfig(){
      AnnotationConfigApplicationContext ctx 
                  = new AnnotationConfigApplicationContext(SpringConfig.class);
      UserController userController = ctx.getBean(UserController.class);
      userController.sayHi();
}

::: tip

​ 确实Spring xml文件实现了松耦合,可是实际项目中能够发现,每每xml不多修改。因此,SpringBoot又主张Java配置类的方式,可是Java配置类的绝大部分都是由Spring xml转化过来的。因此,不论是Java配置类方式仍是Spring xml文件方式都有必要了解,固然我以为,因为为了后期更好的学习和使用SpringBoot,能够以Java配置类方式为主。

:::

AOP

参考自:阿古拉斯啦啦 http://www.javashuo.com/article/p-hfyrskjs-mu.html

​ Aop:是Aspect oriented Programming的缩写,即面向切面编程。经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP(面向切面编程)是OOP(面向对象编程)的一种补充,而不是一种替代品。利用AOP能够对业务逻辑的各个部分进行隔离,从而下降各个模块之间的耦合度,简化维护。常见使用AOP的场景:事务控制,日志管理,权限控制等等。

Aop简单介绍

下面先用几张图熟悉一下AOP是怎么回事。(图片来源于网络)

在这里插入图片描述

  1. 从图中能够看出:不论是 【获取活动相关数据】仍是 【根据条件活动奖励发放】都须要先【检测活动有效性】和【检测活动是否须要登陆】这两步操做。

在这里插入图片描述

  1. 能够将【检测活动有效性】和【检测活动是否须要登陆】两部操做封装到一个方法,而后在两个不一样的业务中进行调用,可是这样虽然重用了代码,可是仍是将两步不是必须耦合在一块儿的代码耦合在了一块儿。

在这里插入图片描述

  1. 而第三幅图则使用了AOP的思想,将【检测活动有效性】和【检测活动是否须要登陆】两个操做封装到一个单独的类(切面)。只须要在须要执行的地方,进行切入便可达到前面同样的效果。这样最大程度的下降了模块之间的耦合度。

Aop中的术语

  • Aspect(切面): Aspect 声明相似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(链接点):表示在程序中明肯定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还能够嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是经过逻辑关系组合起来,或是经过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(加强):Advice 定义了在 Pointcut 里面定义的程序点具体要作的操做,它经过 before、after 和 around 来区别是在每一个 joint point 以前、以后仍是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其余对象链接起来, 并建立 Adviced object 的过程

举一个容易理解的例子

​ 看完了上面的理论部分知识, 我相信仍是会有很多朋友感受到 AOP 的概念仍是很模糊, 对 AOP 中的各类概念理解的还不是很透彻. 其实这很正常, 由于 AOP 中的概念是在是太多了, 我当时也是花了老大劲才梳理清楚的.
下面我以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系.

​ 让咱们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 做案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王刚好在这时候无心中发现了凶手行凶的过程, 可是因为天色已晚, 加上凶手蒙着面, 老王并无看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵固然不敢违背县令的命令, 只好把进出城的全部符合条件的人都抓了起来.

​ 来让咱们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先咱们知道, 在 Spring AOP 中 Joint point 指代的是全部方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 经过 point cut, 咱们就能够肯定哪些 Joint point 能够被织入 Advice. 对应到咱们在上面举的例子, 咱们能够作一个简单的类比, Joint point 就至关于 爪哇的小县城里的百姓,pointcut 就至关于 老王所作的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动做: 抓过来审问.

为何能够这样类比呢?

  • Join point : 爪哇的小县城里的百姓: 由于根据定义, Join point 是全部可能被织入 Advice 的候选的点, 在 Spring AOP中, 则能够认为全部方法执行点都是 Joinpoint. 而在咱们上面的例子中, 命案发生在小县城中, 按理说在此县城中的全部人都有多是嫌疑人.
  • Pointcut :男性, 身高约七尺五寸: 咱们知道, 全部的方法(joinpoint) 均可以织入 Advice, 可是咱们并不但愿在全部方法上都织入 Advice, 而 Pointcut 的做用就是提供一组规则来匹配joinpoint, 给知足规则的 joinpoint 添加 Advice. 同理, 对于县令来讲, 他再昏庸, 也知道不能把县城中的全部百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 知足此修饰规则的百姓都是嫌疑人, 都须要抓起来审问.
  • Advice :抓过来审问, Advice 是一个动做, 即一段 Java 代码, 这段 Java 代码是做用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到咱们的例子中, 抓过来审问 这个动做就是对做用于那些知足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.
  • Aspect::Aspect 是 pointcut 与 Advice 的组合, 所以在这里咱们就能够类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动做能够被认为是一个 Aspect.

在这里插入图片描述

其余的一些内容

AOP中的Joinpoint能够有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中咱们能够在上面的这些Joinpoint上织入咱们自定义的Advice,可是在Spring中却没有实现上面全部的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint

Advice 的类型

  • before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 可是它并不可以阻止 join point 的执行, 除非发生了异常(即咱们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
  • after return advice, 在一个 join point 正常返回后执行的 advice
  • after throwing advice, 当一个 join point 抛出异常后执行的 advice
  • after(final) advice, 不管一个 join point 是正常退出仍是发生了异常, 都会被执行的 advice.
  • around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最经常使用的 advice.
  • introductionintroduction能够为原有的对象增长新的属性和方法。

在Spring中,经过动态代理和动态字节码技术实现了AOP,这些内容,咱们将在之后进行讲解。

下面分别使用XML和Java配置类实现AOP

XmlAOP方式

目标类:

public class Person {

    public String sayHello(){
        System.out.println("Person say Hello!");
        return "sayHelloMethod";
    }

    public String sayBye(){
        System.out.println("Person say ByeBye!");
        return "sayByeMethod";
    }
}

切面类:

public class XmlLoggerAspect {


    public void before(){
        System.out.println("--->before");
    }


    public void after(){
        System.out.println("--->after");
    }

    public void afterReturning(Object returnVal){
        System.out.println("--->afterReturning : " + returnVal);
    }

    public void afterThrowing(Exception exception){

        System.out.println("--->afterTrowing:"+exception.getMessage());
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("--->around before");

        Object proceed = joinPoint.proceed();
        System.out.println("around result : "+proceed);
        System.out.println("--->around after");
        return proceed;
    }
}

xml配置文件:

<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"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--业务类Target-->
    <bean id="person" class="com.ooyhao.spring.aop.Person"/>

    <!--切面类-->
    <bean id="loggerAspect" class="com.ooyhao.spring.aspect.XmlLoggerAspect"/>

    <aop:config>
        <aop:aspect ref="loggerAspect">
            <!--public String com.ooyhao.spring.aop.Person.sayHello()
                * String com.ooyhao.spring.aop.Person.sayHello()
                * com.ooyhao.spring.aop.Person.sayHello()
                * *.sayHello()
                * *.say*()
                * *.say*(..)
            -->
            <aop:pointcut id="pointCut" expression="execution(* *.say*(..))"/>
            <aop:before method="before" pointcut-ref="pointCut"/>
            <aop:after method="after" pointcut-ref="pointCut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnVal"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="exception"/>
            <aop:around method="around" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试:

@Test
public void testXmlAop(){

  ClassPathXmlApplicationContext context
    = new ClassPathXmlApplicationContext("spring-aop.xml");
  Person bean = context.getBean(Person.class);
  bean.sayHello();
  System.out.println("=================");
  bean.sayBye();
  /**
  *
  *              --->before
  *             --->around before
  *             Person say Hello!
  *             around result : sayHelloMethod
  *             --->around after
  *             --->afterReturning : sayHelloMethod
  *             --->after
  *             =================
  *             --->before
  *             --->around before
  *             Person say ByeBye!
  *             around result : sayByeMethod
  *             --->around after
  *             --->afterReturning : sayByeMethod
  *             --->after
  */

  //sayHello出现 int i = 1/0;时
  /**
    *
    *          --->before
    *          --->around before
    *          Person say Hello!
    *          --->afterTrowing:/ by zero
    *          --->after
    *
    *          java.lang.ArithmeticException: / by zero
    *          at com.ooyhao.spring.aop.Person.sayHello(Person.java:7)
    *
    * */
}

由上测试结果能够看出:

  • 正常执行的执行过程

    before -- > around before --> target method --> around after --> afterReturning --> after

  • 出现异常的执行过程

    before --> around before --> target method --> afterTrowing --> after

Java配置类方式

java配置方式的切面:

@Aspect
@Component
@EnableAspectJAutoProxy
public class ConfigLoggerAspect {

    @Pointcut("execution(**.say*()))")
    public void pointCut(){}

    @Before("pointCut()")
    public void before(){
        System.out.println("--->before");
    }

    @After("pointCut()")
    public void after(){
        System.out.println("--->after");
    }

    @AfterReturning(value = "pointCut()", returning = "returnVal")
    public void afterReturning(Object returnVal){
        System.out.println("--->afterReturning : " + returnVal);
    }

    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void afterThrowing(Exception exception){
        System.out.println("--->afterTrowing:"+exception.getMessage());
    }

    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("--->around before");
        Object proceed = joinPoint.proceed();
        System.out.println("around result : "+proceed);
        System.out.println("--->around after");
        return proceed;
    }
}
  • @Aspect: 定义为一个切面
  • @Component:定义为一个Spring组件
  • @EnableAspectJAutoProxy:开启Aop自动代理模式

Java配置类:

@ComponentScan(basePackages = "com.ooyhao.spring")
public class AopConfig {

    @Bean
    public Person person(){
        return new Person();
    }
}
  • @ComponentScan(basePackages = "com.ooyhao.spring") : 将前面的切面进行扫描成组件。

测试类:

@Test
public void testJavaConfigAop(){

  AnnotationConfigApplicationContext context
    = new AnnotationConfigApplicationContext(AopConfig.class);
  Person bean = context.getBean(Person.class);
  bean.sayHello();
  bean.sayBye();

}

:::tip

提示:

​ 前面均使用的是AspectJ表达式,这样能够定位到有必定规律的目标方法,下降程序耦合,可是操做不是特别灵活,我的比较使用注解方式,能够指定到某一个目标方法。

@pointcut("@annotation(com.sample.security.AdminOnly)") // 匹配注解有AdminOnly注解的方法

:::

源码地址: https://gitee.com/ooyhao/JavaRepo_Public/tree/master/Spring-in-Action

最后

若是以为不错的话,那就关注一下小编哦!一块儿交流,一块儿学习

相关文章
相关标签/搜索