整理Spring中常用的设计模式

2023年 7月 14日 50.0k 0

相信这个点也是面试常考的,面试无非两种,

一种是手撕( 一次面试设计模式手撕经历 )

一种就是结合框架问

前置知识

设计模式:

菜鸟教程教的还是非常详细的 墙裂推荐!!

www.runoob.com/design-patt…

整理的代码:

github.com/scwlkq/java…

Spring中的设计模式

代理模式

比如 鸡哥是个明星,他有个经纪人,合作的事情都是由经纪人代理执行

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

代码:

我们定义一个用户服务类

public interface UserService {  
    String login(String username, String password);  
}

给出它的实现:

public class UserServiceImpl implements UserService {  
    @Override  
    public String login(String username, String password) {  
        if("admin".equals(username) && "123456".equals(password)){  
            return "ok";  
        }  
    return "no";  
    }  
}

这时候的代理类:

public class UserProxy implements UserService {  
    private final UserService userService;  
  
    public UserProxy(UserService userService) {  
        this.userService = userService;  
    }  
  
    @Override  
    public String login(String username, String password) {  
        //调用方法之前,我们可以添加自己的操作  
        System.out.println("before method login()");  
        String result = userService.login(username, password);  
        //调用方法之后,我们同样可以添加自己的操作  
        System.out.println("after method login()");  
        return result;  
    }  
}

使用:

public class Main {  
    public static void main(String[] args) {  
        UserService userService = new UserServiceImpl();  
        UserProxy userProxy = new UserProxy(userService);  
        System.out.println(userProxy.login("admin", "123456"));  
    }  
}

针对每个服务 我们需要实现对应的方法的代理,很不方便

动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。

jdk代理

也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。

代码示例:

首先定义一个接口和实现类:

public interface UserService {  
    String login(String username, String password);  
}
public class UserServiceImpl implements UserService {  
    @Override  
    public String login(String username, String password) {  
        if("admin".equals(username) && "123456".equals(password)){  
            return "ok";  
        }  
        return "no";  
    }  
}

然后 我们实现InvocationHandler这个处理接口:

这个可以理解为 你要代理类在这个方法前后干些啥

public class DebugInvocationHandler implements InvocationHandler {  
  
    private final Object target;  
      
    public DebugInvocationHandler(Object object){  
        this.target = object;  
    }  
  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        System.out.println("before:"+method.getName());  
        Object result = method.invoke(target, args);  
        System.out.println("after:"+method.getName());  
  
        return result;  
    }  
}

再定义一个Jdk代理类的工厂:

import java.lang.reflect.Proxy;  
  
public class JdkProxyFactory {  
    public static Object getProxy(Object target){  
        System.out.println(target.getClass().getClassLoader());  
  
        return Proxy.newProxyInstance(  
            target.getClass().getClassLoader(),  
            target.getClass().getInterfaces(),  
            new DebugInvocationHandler(target)  
            );  
    }  
}

使用:

public class Main {  
    public static void main(String[] args) {  
        UserService userService = (UserService) JdkProxyFactory.getProxy(new UserServiceImpl());  
        System.out.println(userService.login("admin", "123456"));  
    }  
}

image.png

CGLib代理

CGLIBopen in new window(Code Generation Library)是一个基于ASMopen in new window的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。(你可以看到下面的Enhancer类就是继承的被代理对象)

简而言之,你需要引入CGLIB的maven依赖

 
    cglib 
    cglib
    3.3.0 

定义一个方法:

public class UserService {  
    public String login(String username, String password) {  
        if("admin".equals(username) && "123456".equals(password)){  
            return "ok";  
        }  
        return "no";  
    }  
}

关键是要实现这个MethodInterceptor:

public class DebugMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, objects);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }
}

然后同样构建factory:

public class CglibProxyFactory {
    public static Object getProxy(Class clazz){
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

使用:

public class Main {
    public static void main(String[] args) {
        UserService userService = (UserService) CglibProxyFactory.getProxy(UserService.class);
        System.out.println(userService.login("admin", "123456"));
    }
}
JDK 动态代理和 CGLIB 动态代理对比
  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。
  • JDK 动态代理效率更优秀。
  • 静态代理和动态代理的对比
  • 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
  • JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。注意 动态代理都是在运行时动态生成字节码文件
  • 单例模式

  • 在Spring的配置文件中定义该类的Bean:
  • 
    
  • 在Java代码中获取该Bean:
  • ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    MySingleton mySingleton = (MySingleton) context.getBean("mySingleton");
    mySingleton.doSomething();
    

    在上面的代码中,我们在Spring的配置文件中定义了一个名为mySingleton的Bean,它的类是com.example.MySingleton,作用域为singleton(即单例)。然后在Java代码中,我们通过ApplicationContext获取该Bean,并调用它的doSomething()方法。

    使用Spring框架可以方便地管理单例对象,同时也可以很容易地实现依赖注入和控制反转等功能。

    AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。
    是一个双检锁的单例

    @Override
    	@Nullable
    	public Object getSingleton(String beanName) {
    		return getSingleton(beanName, true);
    	}
    
    	/**
    	 * Return the (raw) singleton object registered under the given name.
    	 * 

    Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }

    工厂模式

    image.png

    image.png
    在Spring框架中,AbstractBeanFactory是BeanFactory的一个抽象实现,它提供了一些通用的功能和实现细节,以便其他具体的BeanFactory实现可以继承或扩展它。AbstractBeanFactory实现了BeanFactory接口中的大部分方法,并提供了一些额外的方法和功能,例如BeanDefinition的注册和解析、BeanPostProcessor的处理、Bean生命周期管理等。

    BeanFactory是Spring框架中的一个核心接口,它定义了一些方法,用于获取和管理Bean对象。BeanFactory接口的具体实现包括:XmlBeanFactory、DefaultListableBeanFactory、ApplicationContext等。这些实现类都继承了AbstractBeanFactory,并在此基础上进行了扩展和优化,以满足不同的使用场景和需求。因此,AbstractBeanFactory和BeanFactory之间是一种继承关系。

    在Spring框架中,ApplicationContext和BeanFactory是一种继承关系。ApplicationContext接口扩展了BeanFactory接口,并提供了更多的功能和特性,例如:

  • 支持消息国际化和资源访问
  • 支持事件发布和监听
  • 支持AOP(面向切面编程)和事务管理
  • 支持Web应用程序开发
  • ApplicationContext是Spring框架中最常用的接口之一,它是一个集成了多种功能的容器,可以管理Bean对象的生命周期,并提供了丰富的功能和服务,例如自动装配、依赖注入、Bean后置处理器等。与此相比,BeanFactory接口只提供了最基本的Bean管理功能,没有提供其他高级功能。

    因此,ApplicationContext可以看作是BeanFactory的一个超集,它在BeanFactory的基础上进行了扩展和优化,提供了更多的功能和服务,使得开发者可以更加方便地使用Spring框架。

    适配器模式

    实现方式:

    SpringMVC中的适配器HandlerAdatper。

    实现原理:

    HandlerAdatper根据Handler规则执行不同的Handler。

    实现过程:

    DispatcherServlet根据HandlerMapping返回的handler,向HandlerAdatper发起请求,处理Handler。

    HandlerAdapter根据规则找到对应的Handler并让其执行,执行完毕后Handler会向HandlerAdapter返回一个ModelAndView,最后由HandlerAdapter向DispatchServelet返回一个ModelAndView。

    实现意义:

    HandlerAdatper使得Handler的扩展变得容易,只需要增加一个新的Handler和一个对应的HandlerAdapter即可。

    因此Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了。

    策略模式

    非常优雅~

  • ResourceLoader和Resource接口
  • ResourceLoader接口定义了获取资源的方法,而Resource接口则表示一个资源对象。在Spring中,ResourceLoader和Resource接口使用策略模式来实现不同类型的资源的加载和处理。

    ResourceLoader接口定义了getResource方法,该方法返回一个Resource对象,表示一个资源。Spring中提供了多种Resource实现类,例如ClassPathResource、FileSystemResource、UrlResource等,它们分别用于加载类路径下的资源、文件系统中的资源和URL中的资源。

    ResourceLoader和Resource接口之间的关系可以看作是一种策略模式。ResourceLoader接口相当于Context类,而不同类型的Resource实现类相当于具体的策略类。使用ResourceLoader接口可以方便地加载和处理不同类型的资源,而无需关心具体的实现细节。

  • HandlerInterceptor接口
  • HandlerInterceptor是Spring MVC框架中的一个拦截器接口,用于在请求处理过程中进行拦截和处理。在Spring MVC中,HandlerInterceptor使用策略模式来实现不同类型的拦截器的注册和处理。

    HandlerInterceptor接口定义了三个方法:preHandle、postHandle和afterCompletion,分别对应请求处理前、请求处理后和请求处理完成后的处理逻辑。在Spring MVC中,可以通过实现HandlerInterceptor接口来编写自定义的拦截器,并通过配置文件将其注册到Spring MVC框架中。

    HandlerInterceptor和拦截器实现之间的关系可以看作是一种策略模式。HandlerInterceptor接口相当于Context类,而不同类型的拦截器实现类相当于具体的策略类。使用HandlerInterceptor接口可以方便地注册和处理不同类型的拦截器,而无需关心具体的实现细节。

    观察者模式

    Spring的事件驱动模型用了「观察者模式」,具体实现就是ApplicationContextEvent、ApplicationListener

    具体的可拓展机制可以查看另一篇文章:juejin.cn/post/722260…

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论