21天搞定Spring---Spring架构设计原则(day1)

在接下来的21天,我将按照Spring架构设计原则,到Spring IOC,AOP的实现,通过源码分析的手段较为深入的讲解Spring的内核心法。

架构设计原则

首先,从大的方向了解Spring的结构设计是有必要的,因此我将详细介绍Spring的软件架构设计原则(也是通用的软件架构设计原则)以及Spring中常用的设计模式。

  1. 开闭原则:开闭原则是指一个软件实体(如类、模块和函数)应该对扩展开放,对修改关闭。它强调的是用抽象构建框架,用实现扩展细节,从而提高软件系统的可复用性和可维护性。即在项目需求发生变更时,能够通过新的Java Class实现新的需求,而不是修改原有的Java Class
  2. 依赖倒置原则:指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。

    以抽象为基准比以细节为基准搭建起来的架构要稳定得多,因此在拿到需求之后,要面向接口编程,先顶层再细节地设计代码结构。

  3. 单一职责原则:指不要存在多于一个导致类变更的原因。也就是一个类、接口或者方法只负责一项职责。
  4. 接口隔离原则:指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。但是要注意:
    1. 一个类对另一个类的依赖应该建立在最小的接口之上。
    2. 建立单一接口,不要建立庞大臃肿的接口。
    3. 尽量细化接口,接口中的方法尽量少(但绝对不是越少越好,避免过度设计)。
  5. 迪米特原则:指一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合度。
  6. 里氏替换原则:指如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么T2是T1的子类型。即一个软件实体,如果适用于父类,那么一定也适用于子类,所有引用父类的地方都必须能透明的使用其子类的对象。
  7. 合成复用原则:指尽量使用对象组合(has-a)/聚合(contain-s)而不是继承(is-a)关系达到软件复用的目的。可以使系统更加灵活,降低类与类之间的耦合度。一个类的变化对其他类造成的影响相对较少。

注意:以上软件架构设计原则只能作为日常项目开发的方案设计指导,千万不能死脑筋,形成强迫症。在碰到业务复杂的场景时,要随机应变,灵活应用,避免过度设计。
另外,以上原则没有展开论述,相信看这篇文章的都是学习过<设计模式>的,因此不再重复赘述。对这一块有兴趣的或者不清楚的,可以自行查阅相关资料学习


设计模式

学过Java的人应该都了解过设计模式,在实际开发过程中,合理的利用设计模式,能够有效的提高代码的可读性,可维护性即可扩展性,实现”拥抱变化“。而Spring就是一个把各类设计模式应用的淋漓尽致的一个框架。下面将逐一分析Spring中常用的一些经典设计模式。

工厂模式:

顾名思义,就是可以生产一系列Java对象的接口。最经典的就是Spring中的BeanFactory。

工厂模式的优点:通过工厂模式,我们只需要向上层保留响应的工厂,而不需要暴露具体的类的实现细节,从而在需求变化的时候,可以不改上层代码,只是简单的替换生成的实例对象即可。

工厂模式细分下去可以分为以下三类,由于工厂模式相对比较简单,并且工厂模式往往结合其他设计模式(如策略模式,模板方法模式等)一起使用,因此这里不做过多赘述。

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工厂模式
代理模式:

动态代理采用字节数组,重新生成对象来替代原始对象,已达到动态代理的目的。Spring的核心AOP就是通过动态代理的方式实现的。

  1. JDK动态代理生成对象的步骤如下:
  1. 获取被代理对象的引用,并且获取它的所有接口,通过反射获取。
  2. JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
  3. 动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用。
  4. 重新生成的Java代码.class文件
  5. 重新加载到JVM中运行。
    注意:JDK动态代理的目标类,必须实现了某一个接口,否则无法生成代理对象。
// 定义接口1
public interface OrderService {
    void buy();
}
// 定义接口2
public interface OrderService2 {
    void refund();
}
// 定义目的类:实现了接口1和接口2
public class OrderServiceImpl implements OrderService, OrderService2 {
    private String name;
    public OrderServiceImpl(String name){
        this.name = name;
    }
    @Override
    public void buy() {
        System.out.println(this.name + " want to buy a macbook pro...");
    }
    @Override
    public void refund() {
        System.out.println(this.name + " want to refund");
    }
}
// 代理处理类:需要实现InvocationHandler接口,其中有一个invoke方法
// 调用代理类的方法,其实底层就是调用的InvocationHandler的invoke方法
public class OrderServiceProxy implements InvocationHandler {

// 保存目标对象:即被代理对象
    private Object target;

    public  <T> T getProxy(T target){
        this.target = target;
        Class<?> cls = target.getClass();
        Class<?>[] interfaces = cls.getInterfaces();
        if (interfaces== null || interfaces.length <= 0){
            throw new RuntimeException("Not implement a interface");
        }
// 通过JDK的反射机制,生成代理对象,对代理对象的方法调用,会交由InvocationHandler的invoke方法处理
        return (T) Proxy.newProxyInstance(cls.getClassLoader(), interfaces, this);
    }

// 重写invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   		 // 前置通知
        System.out.println("=======before========");
        // 调用被代理类,即target的对应method方法
        Object response = method.invoke(this.target, args);
        // 后置通知
        System.out.println("=======After==========");

        return response;
    }
}

/**
=======before========
xiaoming want to buy a macbook pro...
=======After==========
*/
	@Test
    public void testProxy(){
        OrderService orderService = new OrderServiceImpl("xiaoming");

        OrderServiceProxy proxy = new OrderServiceProxy();
        OrderService instance = proxy.getProxy(orderService);

        instance.buy();
    }
  1. Cglib动态代理: Cglib动态代理的一大优点是,无须被代理类实现接口,通过解析并重新组装字节码的形式实现动态代理。
// 定义一个没有实现任何接口的类:被代理类
public class NonInterfaceObject {
    public void refund(){
        System.out.println("I am not implement any interface...");
    }

}
// 通过Cglib生成动态代理对象,需要实现MethodInterceptor接口。相当于JDK动态代理中的InvocationHandler。
public class CglibProxy implements MethodInterceptor {

    public <T> T getInstance(Class<? extends T> cls){
		// Cglib的核心类,用于创建代理对象        
        Enhancer enhancer = new Enhancer();
        // 设置当前类为代理类的父类
        // 生成的代理类会继承自该被代理类
        enhancer.setSuperclass(cls);
        // 指定代理类的相关操作,交由该对象(实现了MethodInterceptor接口的intercept方法)执行
        enhancer.setCallback(this);

        return (T) enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("===============Before=============");
//        Object response = method.invoke(o, objects);   // 该语句会导致无限递归,因为invoke方法会再次调用intercept方法
        Object response = methodProxy.invokeSuper(o, objects);   System.out.println("===============After=============");
        return response;
    }
}

    @Test
    public void testCglibProxy() {

        // 利用Cglib的代理类,可以将内存中的.class文件写入磁盘文件
      	System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D://cglib");

        CglibProxy cglibProxy = new CglibProxy();
        NonInterfaceObject instance = cglibProxy.getInstance(NonInterfaceObject.class);
        instance.refund();
    }

通过反编译的方式,我们可以分别查看JDK动态代理和Cglib动态代理生成的代理对象的实现。

  1. Cglib和JDK动态代理的对比
  1. JDK动态代理实现了被代理对象的接口,Cglib代理则是继承了被代理对象。
  2. JDK动态代理和Cglib代理都在运行期间生成字节码,JDK动态代理直接写Class字节码,Cglib代理使用ASM框架写Class字节码,Cglib代理实现更加复杂,生成代理类比JDK动态代理低,注意,这里说的仅仅是生成代理类。
  3. JDK动态代理调用代理方法是通过反射机制调用的,Cglib代理是通过FastClass机制直接调用方法的,因此Cglib代理的执行效率更高,这里强调的是执行效率。
  1. Spring中的代理模式:实现AOP。

Spring利用动态代理实现AOP时,有两个非常重要的类: JDKDynamicAopProxyCglibAopProxy。具体实现后面再详细赘述。

  1. 当Bean有实现接口时,Spring就会用JDK动态代理,即JDKDynamicAopProxy
  2. 当Bean没有实现接口时,Spring会选择Cglib代理,即CglibAopProxy
  3. Spring可以通过配置强制使用Cglib代理,只需要在Spring的配置文件加入如下代码:
<aop:aspectj-autoproxy proxy-target-class="true">
  1. 代理模式的优缺点:

优点:

  1. 代理模式能够将代理对象与真实被调用目标对象分离。
  2. 在一定程度上降低了系统的耦合性,扩展性好。
  3. 可以起到保护目标对象的作用。
  4. 可以增强目标对象的功能。
    缺点:
  5. 代理模式会造成系统设计中类的数量增加
  6. 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢。
  7. 增加了系统的复杂度。
  1. 另外留下一些小彩蛋:有兴趣的可以自行查阅资料或尝试。
  1. Spring 5.x 中 AOP 默认依旧使用 JDK 动态代
  2. SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。
  3. 在 SpringBoot 2.x 中,如果需要默认使用 JDK 动态代理可以通过配置项spring.aop.proxy-target-class=false来进行修改,proxyTargetClass配置已无效。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值