【复习 spring IOC与AOP思想】


控制反转(Inversion of Control:IoC)

  IOC 把对象的创建交给框架,它包括依赖注入(DI)和依赖查找,它的作用是降低程序间的耦合(依赖关系)。

而所谓依赖注入(DI),就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”,我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层,这样可以减少维护代码的困难度。

  简单来说,IOC特性 就是 把new对象的权利交给了Spring容器来处理,当前就是简述其设计实现思想,实际开发大多通过注解注入即可,就拿注解注入来举例:

  主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:

      @Component:可以用于注册所有bean
      @Repository:主要用于注册dao层的bean
      @Controller:主要用于注册控制层的bean
      @Service:主要用于注册服务层的bean

  还有两个注解是用于描述依赖关系:

  @Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找。

        如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。

  @Autowired:spring注解,默认是以byType的方式去匹配类型相同的bean,如果只匹配到一个,那么就直接注入该bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会根据byName去找对应bean名称的。

切面编程(AOP)

  AOP的基本概念如下:

  (1)Aspect(切面):通常是一个类,里面可以定义切入点(Pointcut )和通知(Advice)

  (2)JointPoint(连接点):表示在程序中明确定义的点,一般是方法的调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point

  (3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around,Advice 定义了在 切入点 (PointCut) 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

  (4)Pointcut(切入点):就是一组带有通知(advice)的连接点(JointPoint),在程序中主要体现为书写切入点表达式,并定义相应的通知(advice) 将要发生的地方。

  (5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

  (6)Weaving(织入):将 切面(Aspect)和其他对象连接起来, 并创建 Adviced object 的过程

  这些概念比较抽象,可以通过一个demo来理解:

  首先,测试方法为jdk代理(Spring中的AOP代理:代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。)

  jdk的Proxy:动态代理,执行的时候处理,要求必须有接口、实现类,代理创建的是实现类的子类。  

  然后xml实现和注解实现两种方式 我们选择注解实现:

  代码如下:

  接口类:

package com.wang.service;

/**
 * on 2022/2/6 19:43
 */
public interface HelloWorldService {
    public void sayHello(int id);
}

  接口实现类:

package com.wang.service.Impl;

import com.wang.pojo.User;
import com.wang.service.HelloWorldService;
import org.springframework.stereotype.Component;

/**
 *  2022/2/6 19:44
 */

@Component("HelloWorldServiceImpl")
public class HelloWorldServiceImpl implements HelloWorldService {

    @Override
    public void sayHello(int id) {
        System.out.println("Spring AOP——这里是bean内方法");
    }
}

  切面类:

package com.wang.Aspect;

import com.wang.pojo.User;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 *  on 2022/2/6 19:49
 */

@Component    //声明这是一个组件
@Aspect       ///声明这是一个切面Bean
public class HelloWorldAspect {
    //定义切点
    @Pointcut("execution(* com.wang..*.*(..)) ")
    public void sayings(){
    }

    /**
     * 前置通知(注解中的sayings()方法,其实就是上面定义pointcut切点注解所修饰的方法名,那只是个代理对象,不需要写具体方法,
     * 相当于xml声明切面的id名,如下,相当于id="embark",用于供其他通知类型引用)
     * 
     
     
     
     
     
     
     
     
     
     */
/*
    @Before("sayings()")
    public void sayHello(){
        System.out.println("注解类型前置通知");
    }

    //后置通知
    @After("sayings()")
    public void sayGoodbey(){
        System.out.println("注解类型后置通知");
    }
*/
    @Before(value="bean(HelloWorldServiceImpl) && args(id,..)", argNames="id")
    public void beforeWithParam(int id) {
        System.out.println("带参数的前置通知,也可以理解为我们定义一个Advice来拦截这个带参数id的这个方法,bean类为HelloWorldServiceImpl");
        System.out.println(this.getClass().getName()+" ID is : " + id);
    }

    //环绕通知。注意要有ProceedingJoinPoint参数传入。
    @Around(value = "sayings()")
    public void sayAround(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("注解类型环绕通知..环绕前");
        pjp.proceed();//执行方法,即执行被通知函数
        System.out.println("注解类型环绕通知..环绕后");
    }

}

  启动类:

package com.wang.AopTest;

import com.wang.service.HelloWorldService;
import com.wang.service.Impl.HelloWorldServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 *  on 2022/2/6 19:51
 */
public class HelloWorldTest {
    public static void main(String[] args) {

        //这个是application容器,所以就会去所有的已经加载的xml文件里面去找,包括jar包里面的xml文件
        ApplicationContext context = new FileSystemXmlApplicationContext("D:\\IDEAprojects\\Mybatis-study\\mybatis-04\\src\\main\\resources\\applicationContext.xml");

        //通过ApplicationContext.getBean(beanName)动态加载数据(类)【获取Spring容器中已初始化的bean】。
        HelloWorldServiceImpl helloWorld=(HelloWorldServiceImpl) context.getBean("HelloWorldServiceImpl");

        int id = 4;
        //执行动态加载到的类的方法
        helloWorld.sayHello(id);

    }
}

配置文件:applicationContext.xml


       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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-4.3.xsd">



    
    package="com.wang"/>
    
    
    
    class="true"/>

  demo2:改动了一些方法

  还可以更详细一点,定义一些具体的方法来感受所谓的增强,改动后代码如下:(全部放在一起了)

// interface
package com.wang.service;

public interface HelloWorldService {
    public void sayHello(int id);

    void getTool(String tool);
    void doSomething(String action);
    void initWork(String tool, String action);
}



// impl
package com.wang.service.Impl;

import com.wang.pojo.User;
import com.wang.service.HelloWorldService;
import org.springframework.stereotype.Component;


@Component("HelloWorldServiceImpl")
public class HelloWorldServiceImpl implements HelloWorldService {

    @Override
    public void sayHello(int id) {
        System.out.println("Spring AOP——这里是bean内方法");
    }

    @Override
    public void getTool(String tool) {
        System.out.println("=====调用getTool方法=====");
        System.out.println("获得道具:" + tool);
        System.out.println("=====调用结束=====");
    }

    @Override
    public void doSomething(String action) {
        System.out.println("=====调用doSomething方法=====");
        System.out.println("执行操作:" + action);
        System.out.println("=====调用结束=====");
    }


    @Override
    public void initWork(String tool, String action) {
        System.out.println("生成投篮的测试用例");
        getTool(tool);
        doSomething(action);
    }
}



// 切面类
package com.wang.Aspect;

import com.wang.pojo.User;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component    //声明这是一个组件
@Aspect       ///声明这是一个切面Bean
public class HelloWorldAspect {
    //定义切点
    @Pointcut("execution(* com.wang..*.*(..)) ")
    public void sayings(){
    }

  
    @Before("sayings()")
    public void sayHello(){
        System.out.println("注解类型前置通知");
    }

    //后置通知
    @After("sayings()")
    public void sayGoodbey(){
        System.out.println("注解类型后置通知");
    }
*/
    //后置通知
/*    @After("sayings()")
    public void sayGoodbey(){
        System.out.println("注解类型后置通知");
    }*/

/*    @Before(value="bean(HelloWorldServiceImpl) && args(id,..)", argNames="id")
    public void beforeWithParam(int id) {
        System.out.println("带参数的前置通知,也可以理解为我们定义一个Advice来拦截这个带参数id的这个方法,bean类为HelloWorldServiceImpl");
        System.out.println(this.getClass().getName()+" ID is : " + id);
    }*/

    //环绕通知。注意要有ProceedingJoinPoint参数传入。
    @Around(value="bean(HelloWorldServiceImpl) && args(tool, action)", argNames= "pjp,tool, action")
    public void sayAround(ProceedingJoinPoint pjp, String tool, String action) throws Throwable{
        System.out.println("注解类型环绕通知..环绕前");
        System.out.println(this.getClass().getName()+"需要先" +  "去获得投篮所需道具:"+tool);
        pjp.proceed();//执行方法,即执行被通知函数
        System.out.println("nice shooting!");
        System.out.println("注解类型环绕通知..环绕后");
    }

}





// 启动类
package com.wang.AopTest;

import com.wang.service.HelloWorldService;
import com.wang.service.Impl.HelloWorldServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;


public class HelloWorldTest {
    public static void main(String[] args) {

        //这个是application容器,所以就会去所有的已经加载的xml文件里面去找,包括jar包里面的xml文件
        ApplicationContext context = new FileSystemXmlApplicationContext("D:\\IDEAprojects\\Mybatis-study\\mybatis-04\\src\\main\\resources\\applicationContext.xml");

        //通过ApplicationContext.getBean(beanName)动态加载数据(类)【获取Spring容器中已初始化的bean】。
        HelloWorldServiceImpl helloWorld=(HelloWorldServiceImpl) context.getBean("HelloWorldServiceImpl");

        //执行动态加载到的类的方法
        helloWorld.initWork("basketball", "shoot");

    }
}

 控制台输出:

注解类型环绕通知..环绕前
com.wang.Aspect.HelloWorldAspect需要先去获得投篮所需道具:basketball
生成投篮的测试用例
=====调用getTool方法=====
获得道具:basketball
=====调用结束=====
=====调用doSomething方法=====
执行操作:shoot
=====调用结束=====
nice shooting!
注解类型环绕通知..环绕后

Process finished with exit code 0

 PS:切面类要添加注解@Aspect,不然不会触发aop的那几个注解,比如@Before 等。

  PPS:总结来说spring 允许用户实现自定义切面,用AOP来完善OOP(面向对象编程)的使用,我们可以把Spring AOP看作是对Spring的一种增强

参考连接:https://blog.csdn.net/q982151756/article/details/80513340 (例子很形象)

     https://www.cnblogs.com/liuruowang/p/5711563.html