前言
Spring的核心=IOC+AOP
- IOC 为Inversion of Control的缩写,意为:控制反转=接口+工厂模式实现调用方和被调用方之间的解耦合
- AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,底层是动态代理
一、接口+工厂模式解耦合
如何设计低耦合、易复用的软件,面向接口+工厂模式是不错的选择;
1.面向接口编程思想
为什么在MVC三层架构中service层一定要定义接口呢?web层直接调用service实现类不好吗?
1.1.什么是耦合?
在1个系统系统架构中,各个组件之间的耦合是不可避免的。
耦合就是依赖,B依赖A的方法才能执行。
1.2.面向接口编程的流程?
在程序开发之前预先定义接口,接口中定义方法;
强制被调用方按照接口中定义的方法开发实现类;
调用方在调用被调用方时只关注接口(中间层)中定义的方法即可,被调用方修改了接口的实现类逻辑,也不会影响调用方调用;
例如:在web层(调用方)和service层(被调用方)之间定义接口层;
1.3.面向接口编程的目的
面向接口编程实现了低耦合,但是没有完全解开耦合;
我们需要借助面向接口编程+工厂模式,继续解开耦合 ;
2.接口+工厂模式
使用接口+工厂模式可以解开程序编译阶段的耦合,实现程序高内聚和低耦合。
2.1.Java程序执行流程
Java程序分为编译和运行阶段;
- 编译(开发)阶段耦合:是可以解开的
- 运行阶段耦合: 是无法避免的不可以解开的
2.2.解编译阶段的耦合
程序之间的耦合是不可避免的,借助面向接口编程+工厂模式:
- 可以实现程序编译阶段的耦合,
- 无法实现程序运行阶段的耦合;
2.3.编译阶段耦合解开代码体现
使用接口+工厂模式解耦合(编译阶段耦合解开)的代码体现是
- 调用方在调用被调用方时,无需调用方new而是通过给工厂类传递被调用方的对象参数,工厂类接收到对象参数,由工厂类创建被调用方的实现类对象,实现程序的高内聚和低耦合;
- 即使注释/删除被调用方的代码
- 调用方调用被调用方法的代码,程序在编译阶段也不会报错;
2.4.实现流程分析
调用方和被调用方之间增加工厂类
实现工厂类
package com.itheima.factory; import java.util.ResourceBundle; //创建service层(serviceImp)对象的工厂 public class BeanFactory { private static Object bean = null; public static Object getBean(String beanId) { try { //1.读取配置文件 ResourceBundle resourceBundle = ResourceBundle.getBundle("beans"); //2.从配置文件中根据beanId获取到类的全限定名称 String stringClassName = resourceBundle.getString(beanId); //3.根据上一步获取到的类权限定名称创建对象 Class aClass = java.lang.Class.forName(stringClassName); bean = aClass.newInstance(); //4.将上一步创建的对象返回 } catch (Exception e) { // 出现异常打印异常 e.printStackTrace(); //谁调用就把异常扔给谁 throw new RuntimeException(); } return bean; } }
调用方法通过工厂类创建被调用方对象
//1.面向接口创建studentService对象:需要手动new调用方的对象 //StudentService studentService = new StudentServiceImpl();
//2.面向接口+工厂模式创建studentService对象:无需调用方手动new调用方的对象,由BeanFactory来创建 StudentService studentService = (StudentService) BeanFactory.getBean("StudentService");
2.5.工厂类优化
工厂类加载时创建配置文件中配置的所有bean实例,避免调用方每次调用工厂类的getBean()方法都创建1次对象。
package com.itheima.factory; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.ResourceBundle; public class BeanFactory2 { //存储所有bean的HashMap private static Map<String, Object> beanMap = new HashMap<>(); //类加载时创建配置文件中配置的所有bean实例,避免调用方每次调用工厂类的getBean()方法都创建1次对象 static { try { //1.读取配置文件 ResourceBundle resourceBundle = ResourceBundle.getBundle("beans"); //2.遍历配置文件中所有key,实例化bena集中存放到beanMap中 Enumeration<String> keys = resourceBundle.getKeys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); String stringClassName = resourceBundle.getString(key); Class aClass = Class.forName(stringClassName); Object instance = aClass.newInstance(); beanMap.put(key, instance); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(); } } //获取bean public static Object getBean(String beanId) { Object bean = beanMap.get(beanId); return bean; } }
二、代理模式
23种设计模式中有一种代理模式。
使用代码模拟静态代理和动态代理;
- 目标对象(明星): 原始对象
- 代理对象(明星的经纪人):帮助原始对象的对象,一般用于在不改变原始对象的前提下,对原始对象的进行功能的增强。
1.静态代理
如果想要在不改变原始对象的前提下,使用代理对象增强原始对象的功能,那么原始对象和代理对象都需要实现同一个接口;
原始+代理对象的接口
package com.itheima.test.proxy; //演员和经纪人都需要实现的接口 public interface Performer { //唱歌的方法 void sing(); //跳舞的方法 void dance(); }
原始对象的类
package com.itheima.test.proxy; //男演员类实现了演员接口 public class Actor implements Performer { private String name; private Integer age; public Actor() { } public Actor(String name, Integer age) { this.name = name; this.age = age; } @Override public void sing() { System.out.printf("%s唱了一首歌\n", this.name); } @Override public void dance() { System.out.printf("%s跳了一支舞", this.name); } @Override public String toString() { return "actor{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
代理对象的类
package com.itheima.test.proxy; //经纪人类:再拥有演员功能的前提下,增加新的功能 public class Agent implements Performer { //经纪人服务的演员 private Performer performer; public Agent() { } public Agent(Performer performer) { this.performer = performer; } @Override public void sing() { System.out.println("经纪人帮助演员谈谈演出费用问题"); this.performer.sing(); System.out.println("经济人帮助演员收费"); System.out.println("经济人帮助演员照顾演员家里人"); } @Override public void dance() { System.out.println("经纪人帮助演员谈谈演出费用问题"); this.performer.dance(); System.out.println("经济人帮助演员收费"); System.out.println("经纪人帮助演员照顾演员家里人"); } }
2.动态代理
静态代理有以下弊端
- 实现了代理对象给原始对象添加新功能的需求;
- 但每新增1个原始对象都需要新增1代理对象,单独为这个原始对象服务 ;
动态代理
- 动态代理拥有静态代理的代理功能,即在不改变原始对象的前提下,使用代理对象增强原始对象的功能;
- 动态代理还可以在不定义代理类的前提下,直接创建1个代理对象,使用1个代理对象服务多个原始对象;
package com.itheima.test.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Customer { public static void main(String[] args) { //客户要求演员-宝强 唱首歌 //方式1:直接实例化1个演员类 Performer actor1 = new Actor("宝强", 41);//原始对象 actor1.sing(); System.out.println("----------------------------------------"); //方式2:客户要求演员-宝强 唱首歌, 需要找到宝强的经纪人,通过代理找到演员 Performer actor2 = new Actor("宝强", 41); Agent agent = new Agent(actor2); //代理对象 agent.sing(); System.out.println("----------------------------------------"); //方式3动态代理:虽然方式2实现了给原始对象添加功能的需求; // 但每1个演员都需要,创建1为单独的经纪人,能不能让多个演员公用1个代理人? //再简化一步:不定义代理类,直接创建代理对象 //代理对象=目标对象的功能+增强逻辑 //1.创建目标对象 Actor actor3 = new Actor("奶量", 41); //2.编写增强逻辑 //3.使用JDK动态创建代理对象 Performer instance = (Performer) Proxy.newProxyInstance( //指定1个类加载器:跟目标对象使用同一个类加载器 actor3.getClass().getClassLoader(), //接口:限制代理对象可以使用目标对象哪些方法? actor3.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用目标对象的方法: //参数1:目标对象, //参数2:目标对象方法执行需要 System.out.println("经纪人帮助演员谈谈演出费用问题"); method.invoke(actor3, args); System.out.println("经济人帮助演员收费"); System.out.println("经济人帮助演员照顾演员家里人"); return null; } } ); //4.调用代理对象的方法 instance.sing(); instance.dance(); } }