Spring:IOC/DI和AOP的解析原理和代码

在spring中经常被提及的概念,一个是控制反转IOC,一个是依赖注入DI。还有一个面向切面编程AOP。

浅谈控制反转(IOC):

      在任何一个有请求作用的系统当中,至少需要有两个类互相配合工作,在一个入口类下使用new关键字创建另一个类的对象实例,这就好比在面向对象编程的思想下,“我“充当一个入口类,在这个入口类中,我每次吃饭的时候都要买一双一次性筷子(每一次使用都要new一次),在这样的关系下,是”我“(即调用者)每次都要”主动“去买一次性筷子(另一个类),我对筷子说你老老实实的过来我的手上,是我控制了筷子,那好,在这种控制正转的关系下,放在现实生活当中,肯定是不现实的,他总会去创造出更加方便自己生活的想法,更确切的做法是,买一双普通的筷子(非一次性),把他放在一个容器当中(在Spring中叫做IOC容器),你需要使用的时候就对容器说:IOC我想要用筷子(向容器发出请求),接着筷子就会”注入“到的手上,而在这个过程当中,你不再是控制方,反而演变成一名请求者(虽然本身还是调用者),依赖于容器给予你资源,控制权坐落到了容器身上,于是这就是人们俗称的控制反转

依赖注入(DI):在控制反转中获取资源的过程叫做依赖注入【由容器动态的将某个依赖关系注入到组件之中】,那么这里代码实现也是专注于依赖注入。

依赖注入有3种方式:构造函数方法注入,setter方法注入,接口注入(侵入性,淘汰)。

IOC: 体现了工厂模式,通过BeanFactory注入实例,将对象交给容器管理,在配置文件定义相应的bean,以及设置相关的属性,让spring容器来生成实例对象以 及管理对象。


说白了其实就是由我们平常的new转成了使用反射来获取类的实例,相信任何人只要会用java的反射机制,比如:

    public ObjectgetInstance(String className) throws Exceptio {
      Object obj = Class.forName(className).newInstance(); //获取传入类的实例 
      Method[] methods = obj.getClass().getMethods(); //获取传入类的所有方法
      for (Method method : methods) {
        if (method.getName().intern() == "setString") {
          method.invoke(obj, "hello world!");
        }
      }
    }

2.关于Spring  AOP :

 AOP:面向切面编程,在不修改源代码的情况下(通过预编译方式和动态代理实现)给程序动态统一添加功能。比如说加日志,权限。

  jdk动态代理:用拦截器(拦截器必须实现InvocationHanlder接口)加上反射机制生成一个实现代理接口的匿名类,在调用方法前调用 InvokeHandler来处理。使用Proxy.newProxyInstance产生代理对象。

  cglib动态代理:必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法。是一种继承。针对接口编程的环境下推荐使用JDK的代理。

  静态代理模式:是由程序员创建或特定工具自动生成源代码,在对其进行编译。在程序运行之前,代理类的.class文件就已经存在。

注意: 如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用cglib代理。

比如:我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,就会在要一些方法前去加上一条日志记录。

springboot项目demo:

 applicationContext.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
  <!-- 配置扫描的包 -->
    <context:component-scan base-package="com.road.unifiedpay.test2"/>
   <!-- 切面的声明 -->
    <bean id="transaction" class="com.road.unifiedpay.test2.Transaction"/>
      <!--aop配置  --> 
    <aop:config>
         <!-- 切点, 配置aop的切入点id; 是切入点的标识 ;expression 为切入点的表达式 -->
         <aop:pointcut expression="execution(* com.road.unifiedpay.test2..*(..))" id="perform"/>
         <!-- 切面,配置切面(切面里面配置通知)—— ref 指向声明切面的类 -->
         <aop:aspect ref="transaction">
         <!--     前置通知pointcut-ref 引用一个切入点 -->
         <aop:before method="beginTransaction" pointcut-ref="perform"/>
         <!--     后置通知 -->
         <!-- <aop:after-returning method="commit" pointcut-ref="perform" returning="val"/> -->
         </aop:aspect>
    </aop:config>
  
</beans>
package com.road.unifiedpay.test2;
public class Person {
     private Long pid;
        private String pname;
 
        public Long getPid() {
            return pid;
        }
 
        public void setPid(Long pid) {
            this.pid = pid;
        }
 
        public String getPname() {
            return pname;
        }
 
        public void setPname(String pname) {
            this.pname = pname;
        }
}
package com.road.unifiedpay.test2;
import java.util.List;
 
 
/**
 * 目标对象和代理对象都实现的接口
 */
public interface PersonDao {
         void deletePerson();
        List<Person> getPerson() throws Exception;
        void savePerson();
        void updatePerson();
}
package com.road.unifiedpay.test2;
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.stereotype.Service;
 
/**
 * 目标对象:实现目标接口
 */
@Service("personDao")
public class PersonDaoImpl implements PersonDao{
    
     @Override
    public void deletePerson() {
        System.out.println("delete perosn");
    }
 
    @Override
    public List<Person> getPerson() throws Exception {
        List<Person> personList = new ArrayList<Person>();
        Person person1 = new Person();
        person1.setPid(1L);
        person1.setPname("person1");
        System.out.println("get person");
        personList.add(person1);
        return personList;
    }
 
    @Override
    public void savePerson() {
        System.out.println("save perosn");
    }
 
    @Override
    public void updatePerson() {
        System.out.println("update perosn");
    }

}
package com.road.unifiedpay.test2;
import java.util.ArrayList;
import java.util.List;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
 
 
/**
 * 切面(spring aop 就不需要拦截器啦)
 * (模拟hibernate里面保存数据要打开事物,然后各种增删改之后,再提交事物。)
 */
public class Transaction {
    
    //前置通知
    public void beginTransaction() {
        System.out.println("begin Transaction");//打开事物
    }
 
    /**
     * @param joinPoint 通过joinPoint可以得到目标类和目标方法的一些信息
     * @param val       目标方法的返回值
     *                  和<aop:after-returning returning="val"/>中returning的值保质一致
     */
    //后置通知
    public void commit(JoinPoint joinPoint, Object val) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:"+methodName);
        //提交事物
        System.out.println("commit");
        List<Person> personList = (ArrayList<Person>) val;
        for (Person person : personList) {
            System.out.println(person.getPname());
        }
    }
 
    public void finalMethod() {
        System.out.println("最终通知");
    }
 
    public void aroundMethod(ProceedingJoinPoint joinPoint) {//环绕通知
        try {
            System.out.println("around method");
            joinPoint.proceed();//调用目标类的目标方法
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
    /**
     * 异常通知
     */
    public void throwingMethod(Throwable except) {
        System.out.println(except.getMessage());
    }

}

测试:

package com.road.unifiedpay.test2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
 
public class TestAop {
    
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        PersonDao personDao=(PersonDao) context.getBean("personDao");
        try {
            personDao.savePerson();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果:

 说明:在applicationContext.xml中申明了切面,切点,通知类型,切点配置了调用personDaoImpl实现类的所有方法只要执行就会先打印前置通知“begin Transcation” 。

 总结一句话就是:AOP 在不修改源代码的情况下给程序动态统一添加功能。  这样就能够在一个项目及时要在中途需要这么一个功能,那也就只需修改配置文件和加一个类,而没有该已经写好的类的代码。aop明显增加了代码的复用性,也省去了重新测试。

上面的三条红色的竖向框就是经常说的切面,在这个切面里面有很多的方法,可以把a()看做上面的说道的前置通知,b()看做后置通知,c()看做最终通知等等。总而言之,这些方法都不需要我们去写的,而是aop自动帮我们做好的。我们只要触动了我们的比如“保存方法”就会执行切面里的一系列方法。这样就省去了很多开发时间,也精简了代码。

原文地址:https://www.cnblogs.com/lgg20/p/11188743.html