Java 和 SpringBoot 中的设计模式 详解

一、建造者模式

发生场景

假如有一结果api结果返回值的类Person,其在代码中频繁被使用。如果要使用它,一般的方法是:

java 复制代码
public class Main {
    public static void main(String[] args) {
        //方法1,使用全量的构造函数
        Person person1 = new Person("Tom", 28, "Male");

        //方法2,使用空的构造函数加setter函数赋值
        Person person2 = new Person();
        person2.setName("Tom");
        person2.setAge(28);
        person2.setGender("Male");
    }
}

//--》产品
class Person {
    private String name;
    private int age;
    private String gender;

    public Person() {
    }

    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
}

这两种使用方法的弊端有:

(1)方法一 :当只需要部分参数的时候需要再定义个构造函数(比如失败的情况只需要code和message,结果肯定是空,因此不需要data),且一旦参数较多,则构造函数冗长

(2)方法二setter冗长

使用建造者模式优化上述场景

java 复制代码
//使用方法--》指导者
Person person = Person.builder()
                    .name("Tom")   //相当于setName("Tom"),只是方法名取name
                    .age(28)
                    .gender("Male")
                    .build();

//--》产品
public class Person {
    private String name;
    private int age;
    private String gender;

    //⑤
    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    //①
    public static PersonBuilder builder() {
        return new PersonBuilder();
    }
}

// Java 实现--》建造者
public class PersonBuilder {
    private String name;
    private int age;
    private String gender;

    //②空构造器
    public PersonBuilder() {
    }
 
    //③然后使用setter方法进行设置
    public PersonBuilder name(String name) {
        this.name = name;
        return this;  //方法返回当前对象的引用。这样可以支持方法链式调用。
        //通过返回当前对象的引用,可以在一个语句中连续调用对象的方法,而无需每次调用都创建一个新的对象。
    }
 
    public PersonBuilder age(int age) {
        this.age = age;
        return this;
    }
 
    public PersonBuilder gender(String gender) {
        this.gender = gender;
        return this;
    }
 
    //④
    public Person build() {
        return new Person(name, age, gender);
    }
}

应用案例

Lombok插件的@Builder注解

java 复制代码
//使用
Person person = Person.builder()
                    .name("Tom")
                    .age(28)
                    .gender("Male")
                    .build();

//lombok优化后
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private int age;
    private String gender;
}

二、代理模式

避免客户端直接访问真实对象,必须通过代理对象访问真实对象。

好处:①在代理对象中限制访问权限(权限校验);②在代理对象中扩展功能(日志记录)

静态代理

  1. 代理对象真实对象实现了同一个接口,这样代理对象就具有了和真实对象一样的同名方法
  2. 代理对象持有真实对象的实例(代理对象构造器输入参数为公共接口,这样可以代理多个实现类)
  3. 调用时使用代理对象的方法,在代理对象的方法里再调用真实对象同名方法和增加一些扩展操作

缺点:

  • 代理对象的一个接口只服务于一种类型的对象,如果要代理的类很多,势必要为每一种类都进行代理,在程序规模稍大时静态代理代理类就会过多会造成代码混乱
  • 2、如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。

动态代理

1.JDK 动态代理(反射)------目标对象实现了接口

  1. 定义一个接口及其实现类;
  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
java 复制代码
//公共接口
public interface SmsService {
    String send(String message);
}

//真实对象
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

//代理对象(自定义 InvocationHandler)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

//代理对象的工厂类
public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(              //创建代理对象
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}

//实际使用
SmsService smsServiceProxy = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsServiceProxy.send("java");

2.CGLIB 动态代理机制(反射+继承)------目标对象没有实现接口

  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;
java 复制代码
//真实对象(类和方法都不能是final的,因为CGLIB代理用到了继承)
public class SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

//代理对象(自定义 MethodInterceptor)
public class MethodInterceptor implements MethodInterceptor {
 
    //被代理对象
    private Object target;

    public MethodInterceptor(Object target) {
        this.target = target;
    }
 
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLib商店打广告!");
        //调用原来方法
        Object invoke = method.invokeSuper(target, objects);
        System.out.println("CGLib商店做售后!");
        return invoke;
    }
}

//代理对象的工厂类
public class CGlibFactoryProxy {
    public Object createProxy(Object target){
        this.target = target;
        //创建代理对象
        Enhancer enhancer = new Enhancer();
        //设置父类,以便 CGLIB 去生成该类的子类
        enhancer.setSuperclass(this.target.getClass());
        //设置方法回调MethodInterceptor实现,你可以认为是设置增强方法
        enhancer.setCallback(new MethodInterceptor());
        //返回代理对象
        return enhancer.create();
    }
}

//实际使用
CGlibFactoryProxy factoryProxy = new CGlibFactoryProxy();
SmsService smsServiceProxy = (SmsService) factoryProxy.createProxy(new SmsService());
smsServiceProxy.send("java");

3.应用案例

Spring 框架的 AOP 使用了动态代理模式来实现切面编程。通过代理,Spring 能够在目标对象的方法执行前、执行后或抛出异常时插入切面逻辑,而不需要修改原始代码。

  • (切点)在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。
  • (处理时机)在什么时候切入,是业务代码执行前还是执行后。
  • (处理内容)切入后做什么事,比如做权限校验、日志记录等。

Joint point:一个方法的执行或者一个异常的处理。

Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

基于AspectJ实现AOP操作

Spring框架一般都是基于AspectJ实现AOP操作。AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作。

1.基于xml配置文件实现

2.基于注解方式实现(使用,重要)

java 复制代码
//切面类
@Aspect
@Component
public class UserProxy {
	
    //1.相同切入点抽取
    @Pointcut(value = "execution(* com.jin.aopanno.User.add(..))")
    public void pointdemo(){}
    
    @Before(value = "pointdemo()")
    public void before(){
        System.out.println("before ...");
    }

    //2.不同切入点抽取
    //前置通知(value = 切入点表达式)
    @Before(value = "execution(* com.jin.aopanno.User.add(..))")
    public void before(){
        System.out.println("before ...");
    }

    //3.自定义注解
    @Before("@annotation(com.easychat.annotation.GlobalInterceptor)")
    public void before(){
        System.out.println("before ...");
    }

    private void checkLogin(Boolean checkAdmin) {
    }
}


//自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalInterceptor {
    //校验登录
    boolean checkLogin() default true;
}
相关推荐
ZSYP-S2 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
越甲八千7 分钟前
重温设计模式--享元模式
设计模式·享元模式
yuanbenshidiaos9 分钟前
C++----------函数的调用机制
java·c++·算法
是小崔啊27 分钟前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
黄公子学安全36 分钟前
Java的基础概念(一)
java·开发语言·python
liwulin050637 分钟前
【JAVA】Tesseract-OCR截图屏幕指定区域识别0.4.2
java·开发语言·ocr
jackiendsc42 分钟前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
Yuan_o_42 分钟前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
Oneforlove_twoforjob1 小时前
【Java基础面试题027】Java的StringBuilder是怎么实现的?
java·开发语言
数据小小爬虫1 小时前
利用Java爬虫获取苏宁易购商品详情
java·开发语言·爬虫