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;
}
相关推荐
也无晴也无风雨几秒前
代码中的设计模式-策略模式
设计模式·bash·策略模式
七星静香16 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员17 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU17 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie621 分钟前
在IDEA中使用Git
java·git
Elaine20239136 分钟前
06 网络编程基础
java·网络
G丶AEOM37 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201338 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀39 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海42 分钟前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试