Spring框架中的GoF设计模式

Spring Framework是一个用于构建企业级应用程序的流行Java框架。它提供了广泛的特性和功能,使开发高性能,可扩展和可维护的应用程序。Spring的一个关键优势是它对设计模式的支持。在本文中,我们将探索Spring Framework中一些最常用的设计模式,并了解它们在实践中的使用情况。

单例模式
在Spring中,默认情况下创建单例对象。这意味着每个Spring上下文只创建一个特定bean的实例。这是通过使用单例bean作用域来实现的。

当您在Spring中定义一个具有单例作用域的bean时,Spring将只创建该bean的一个实例并缓存它。对bean的任何后续请求都将返回缓存的实例。

要在Spring中定义一个singleton bean,可以使用@Component注解或它的一个原型注解(@Service,@Repository,等等)在一个类。例如,以下是如何使用@Component注释定义单例bean:

@Component
public class MySingletonBean {
    // implementation here
}

当Spring容器初始化时,它将创建MySingletonBean的实例并缓存它。对MySingletonBean的任何后续请求都将返回缓存的实例。值得注意的是,虽然singleton bean在每个Spring上下文中只创建一次,但如果bean没有被设计为线程安全的,它们仍然可能具有可以由多个线程修改的状态。因此,确保单例bean是线程安全的非常重要,无论是通过正确的同步还是完全避免可变状态。

工厂模式
工厂模式是Spring中另一个常用的设计模式。它提供了一个在超类中创建对象的接口,但允许子类改变将要创建的对象的类型。在Spring中,工厂模式用于基于应用程序的当前状态动态创建bean的实例。Spring提供了Factory模式的两个主要实现:BeanFactory和ApplicationContext。

BeanFactory是访问Spring容器的核心接口,它负责创建和管理bean对象。BeanFactory使用许多不同的策略来创建和管理bean,包括Singleton和Prototype设计模式。

让我们考虑一个示例,其中我们有一个UserService接口和一个实现该接口的UserServiceImpl类。我们希望创建一个UserService对象并在应用程序中使用它。我们可以在Spring配置文件中将UserServiceImpl定义为bean,如下所示:

<bean id="userService" class="com.example.UserServiceImpl"/>



@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserServiceImpl();
    }
}

现在,当我们想在应用程序中使用UserService对象时,我们可以使用BeanFactory从Spring容器中获取它。下面是我们如何做到这一点的一个例子:

public class UserController {
    private BeanFactory beanFactory;

    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public void doSomething() {
        UserService userService = (UserService) beanFactory.getBean("userService");
        // use userService object here
    }
}

在上面的例子中,我们有一个UserController类,它有一个通过其setter方法注入的BeanFactory实例。UserController类的doSomething()方法使用BeanFactory从Spring容器中获取UserService对象,然后在应用程序中使用它。

请注意,当我们调用BeanFactory的getBean()方法来获取UserService对象时,Spring使用Factory模式来创建UserService对象。Spring创建UserServiceImpl类的一个新实例,并使用它可能具有的任何依赖项对其进行初始化。

ApplicationContext接口通过提供额外的功能扩展了BeanFactory的功能,比如支持国际化、资源加载和事件传播。ApplicationContext接口通常用于Web应用程序中,它提供了其他功能,例如对Web范围的支持以及从ServletContext获取对象的能力。

下面是一个例子,说明我们如何使用ApplicationContext在Spring应用程序中创建和管理bean对象:

public class UserServlet extends HttpServlet {
    private ApplicationContext applicationContext;

    public void init() throws ServletException {
        applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        UserService userService = (UserService) applicationContext.getBean("userService");
        // use userService object here
    }
}

在上面的例子中,我们有一个UserServlet类,它在init()方法中初始化ApplicationContext。UserServlet类的doGet()方法使用ApplicationContext从Spring容器获取UserService对象,然后在应用程序中使用它。

模板方法模式
在Spring框架中,模板方法模式被广泛用于各种模板类的实现。这些模板类提供算法或过程的框架实现,并允许用户根据需要覆盖算法或过程的特定部分。

Spring中最著名的模板方法模式示例之一是JdbcTemplate类,它提供了一组用于处理关系数据库的方便方法。

JdbcTemplate类提供了一个名为execute()的模板方法,该方法接受一个回调对象,该对象定义要执行的SQL语句以及要传递给该语句的参数。JdbcTemplate类负责打开和关闭数据库连接、执行语句以及处理可能发生的任何异常的所有低级细节。下面是一个示例,说明如何使用JdbcTemplate类执行简单的SQL查询:

public class UserDaoImpl implements UserDao {
    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public User getUserById(int userId) {
        return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", new Object[]{userId}, new UserRowMapper());
    }
}

在上面的示例中,我们有一个UserDaoImpl类,它使用JdbcTemplate执行SQL查询,通过ID检索用户。JdbcTemplate的execute()方法由queryForObject()方法在内部调用,该方法将一个回调对象(UserRowMapper类的实例)传递给execute()方法。UserRowMapper类负责将SQL查询的结果映射到User对象。

请注意,JdbcTemplate类的execute()方法使用Template Method模式来提供数据库访问过程的框架实现,同时允许用户通过提供回调对象来自定义方法的行为。

Spring中Template Method模式的另一个示例是AbstractController类,它提供了用于处理HTTP请求的控制器类的骨架实现。

AbstractController类定义了一个名为handleRequestInternal()的模板方法,Spring框架调用该方法来处理HTTP请求。handleRequestInternal()方法负责处理请求,并返回一个ModelAndView对象,该对象包含要呈现的视图以及在呈现视图时要使用的模型数据。下面是一个如何使用AbstractController类来处理HTTP请求的示例:

public class UserController extends AbstractController {
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        int userId = Integer.parseInt(request.getParameter("userId"));
        User user = userService.getUserById(userId);

        ModelAndView modelAndView = new ModelAndView("userView");
        modelAndView.addObject("user", user);

        return modelAndView;
    }
}

在上面的例子中,我们有一个UserController类,它扩展了AbstractController类来处理HTTP请求。AbstractController类的handleRequestInternal()方法被重写,以提供一个自定义的实现,该实现使用UserService对象按用户ID检索用户,然后返回一个ModelAndView对象,该对象包含要在视图中呈现的用户数据。

代理模式
在Spring框架中,代理模式被广泛用于实现各种AOP(面向方面编程)功能。AOP是一种编程范式,它允许将横切关注点与应用程序的核心业务逻辑分离。换句话说,它允许开发人员封装应用程序行为的某些方面,并以模块化和可重用的方式将它们应用于多个类或模块。

Spring中Proxy模式最常见的示例之一是使用动态代理为服务层中的方法提供声明性事务管理。下面是一个使用动态代理的Spring事务管理的例子:

@Transactional
public void updateEmployee(Employee employee) {
    employeeDao.update(employee);
}

在上面的示例中,@Transactional annotation告诉Spring将事务管理应用于updateEmployee()方法。当调用此方法时,Spring创建一个动态代理对象,该对象拦截方法调用并执行必要的事务管理操作,例如开始事务,提交事务或在发生异常时回滚事务。

Spring中代理模式的另一个例子是使用JDK动态代理和CGLIB代理来实现依赖注入(DI)机制。当使用接口为DI配置bean时,Spring创建一个JDK动态代理对象,该对象实现接口并将方法调用委托给实际的bean对象。下面是一个例子:

public interface EmployeeService {
    void updateEmployee(Employee employee);
}

@Service
public class EmployeeServiceImpl implements EmployeeService {
    @Override
    public void updateEmployee(Employee employee) {
        employeeDao.update(employee);
    }
}

@Controller
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;

    @PostMapping("/employee")
    public void updateEmployee(@RequestBody Employee employee) {
        employeeService.updateEmployee(employee);
    }
}

在上面的示例中,EmployeeController类依赖于EmployeeService接口,该接口由EmployeeServiceImpl类实现。在创建控制器时,Spring创建一个动态代理对象,该对象实现EmployeeService接口并将方法调用委托给实际的EmployeeServiceImpl对象。这允许Spring在运行时注入服务的实际实现,而不需要控制器知道实现。

当使用类为DI配置bean时,Spring创建一个CGLIB代理对象,该对象扩展类并覆盖执行DI操作所需的方法。下面是一个例子:

@Service
public class EmployeeService {
    @Autowired
    private EmployeeDao employeeDao;

    public void updateEmployee(Employee employee) {
        employeeDao.update(employee);
    }
}

在上面的示例中,EmployeeService类依赖于EmployeeDao类,后者使用@Autowired注释注入。当服务被创建时,Spring创建一个CGLIB代理对象,它扩展EmployeeService类并覆盖updateEmployee()方法来执行DI操作。

装饰模式
装饰器模式是一种设计模式,它允许将行为静态或动态地添加到单个对象,而不会影响同一个类中其他对象的行为。它用于在运行时扩展或修改对象的行为,而不影响它们的原始结构。

在Spring中,Decorator模式在多个地方使用,以提供额外的功能,而不更改对象的原始行为。Spring中Decorator模式最常见的例子之一是使用HandlerInterceptor接口为Spring MVC应用程序中的HTTP请求处理提供额外的行为。下面是一个例子:

public class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Logging logic here
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // Logging logic here
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // Logging logic here
    }
}

在上面的示例中,LoggingInterceptor类实现了HandlerInterceptor接口,该接口提供了三种方法,可用于在控制器处理HTTP请求之前和之后拦截HTTP请求。这允许开发人员在不改变控制器的原始行为的情况下向请求处理管道添加额外的行为。

Spring中Decorator模式的另一个例子是使用DelegatingFilterProxy类向Web应用程序过滤器添加额外的行为。下面是一个例子:

public class LoggingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // Initialization logic here
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // Logging logic here
        chain.doFilter(request, response);
    }
    
    @Override
    public void destroy() {
        // Cleanup logic here
    }
}

在上面的示例中,LoggingFilter类实现了Filter接口,该接口用于拦截Web应用程序中的HTTP请求和响应。过滤器通过记录请求和响应数据向请求处理管道添加附加行为。

要在Spring应用程序中使用filter,我们可以配置DelegatingFilterProxy类将请求委托给LoggingFilter类,如下所示:

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

在此配置中,DelegatingFilterProxy类充当LoggingFilter类的Decorator,拦截所有请求并将其委托给过滤器进行处理。

观察者模式
观察者模式是一种设计模式,它允许一个对象观察另一个对象中的更改并对这些更改做出反应。它用于保持相关对象之间的一致性,并减少它们之间的耦合。

在Spring中,观察者模式被用于多个地方,以允许对象观察其他对象的变化并做出相应的反应。Spring中Observer模式最常见的示例之一是使用ApplicationEventPublisher接口将事件发布到注册的侦听器。下面是一个例子:

public class Order {
    private List<OrderItem> items;
    private double totalPrice;
    
    // getters and setters
    
    public void addItem(OrderItem item) {
        items.add(item);
        totalPrice += item.getPrice();
        ApplicationEventPublisher publisher = // get ApplicationEventPublisher instance
        publisher.publishEvent(new OrderItemAddedEvent(this, item));
    }
}

public class OrderItemAddedEvent extends ApplicationEvent {
    private Order order;
    private OrderItem item;
    
    public OrderItemAddedEvent(Order order, OrderItem item) {
        super(order);
        this.order = order;
        this.item = item;
    }
    
    // getters and setters
}

public class OrderProcessor implements ApplicationListener<OrderItemAddedEvent> {
    @Override
    public void onApplicationEvent(OrderItemAddedEvent event) {
        // Process the order item added event here
    }
}

在上面的示例中,Order类包含OrderItem对象的列表和totalPrice字段。当一个新的OrderItem被添加到订单中时,addItem方法被调用,总价被更新。然后,该方法将OrderItemAddedEvent发布到ApplicationEventPublisher实例,该实例将该事件通知给所有已注册的侦听器。

OrderProcessor类实现ApplicationListener接口,并注册为OrderItemAddedEvent事件的侦听器。发布事件时,将调用onApplicationEvent方法,该方法将根据需要处理事件。

Spring中Observer模式的另一个例子是使用@EventListener annotation来处理事件。下面是一个例子:

@Component
public class OrderProcessor {
    @EventListener
    public void handleOrderItemAddedEvent(OrderItemAddedEvent event) {
        // Process the order item added event here
    }
}

在本例中,OrderProcessor类使用@Component注释进行注释,这使其成为Springbean。该类包含一个用@EventListener注释的方法,该注释指定该方法应处理OrderItemAddedEvent事件。

当一个事件被发布时,Spring会自动检测带注释的方法并调用它来处理事件。

命令模式
命令模式是一种行为设计模式,它将特定操作的请求与执行该操作的对象分离。该模式允许将请求存储为对象,这些对象可以作为参数传递给其他对象,也可以存储以供以后使用。

在Spring中,Command模式在多个地方使用,以允许对象执行命令,并在必要时撤消或重做命令。Spring中Command模式最常见的示例之一是使用JdbcTemplate类来执行数据库查询。下面是一个例子:

@Autowired
private JdbcTemplate jdbcTemplate;

public void updateOrder(Order order) {
    jdbcTemplate.update("UPDATE orders SET status = ? WHERE id = ?", order.getStatus(), order.getId());
}

在本例中,JdbcTemplate类用于执行数据库查询,以更新数据库中的Order对象。JdbcTemplate类的update方法接受一个SQL查询字符串和一个要在查询中使用的参数数组。

update方法是Command对象的一个示例,它将数据库查询及其参数封装为一个对象。JdbcTemplate类是执行命令对象的Invoker对象的示例。

Spring中Command模式的另一个例子是使用@RequestMapping annotation将HTTP请求映射到Spring控制器中的方法。下面是一个例子:

@Controller
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String getOrder(@PathVariable("id") Long id, Model model) {
        Order order = orderService.getOrderById(id);
        model.addAttribute("order", order);
        return "orderDetails";
    }
}

在本例中,OrderController类使用@Controller和@RequestMapping注释进行注释,这指定该类是Spring控制器,并且所有对“/orders”的HTTP请求都应由该控制器中的方法处理。

getOrder方法使用@RequestMapping注释进行注释,该注释指定对“/orders/{id}”的HTTP GET请求应由此方法处理。该方法接受一个Long参数,该参数表示要从数据库中检索的订单的ID。

getOrder方法是Command对象的一个示例,它封装了从数据库检索订单的逻辑。当收到“/orders/{id}"的GET请求时,Spring MVC框架将调用该方法。

责任链模式
责任链是一种行为设计模式,它允许一组对象以顺序的方式处理请求或事件。在这种模式中,链中的每个对象都有机会处理请求或将其传递给链中的下一个对象。

在Spring中,责任链模式在多个地方使用,以允许多个对象以灵活和可扩展的方式处理请求。Spring中责任链模式最常见的示例之一是使用拦截器来处理HTTP请求。下面是一个例子:

@Configuration
public class AppConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**");
        registry.addInterceptor(new AuthorizationInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**");
    }

    // Other configuration methods...
}

在本例中,AppConfig类扩展了WebMvcConfigurerAdapter类,并覆盖了addInterceptors方法以注册两个拦截器:LoggingInterceptor和AuthorizationInterceptor。

addInterceptor方法是Chain对象的一个示例,它将Interceptor对象添加到链中。Interceptor对象是Handler对象的一个示例,它处理HTTP请求,并可选地将其传递给链中的下一个对象。

Interceptor对象可以配置一组路径模式,指定它们应该处理哪些请求。在这个例子中,两个拦截器都被配置为处理所有对“/API/”的请求,除了那些匹配“/api/public/”的请求。

Spring中责任链模式的另一个例子是使用AOP(面向方面编程)向Springbean添加行为。下面是一个例子:

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before calling method: " + joinPoint.getSignature().getName());
    }

    // Other advice methods...
}

在本例中,LoggingAspect类使用@Aspect和@Component注释进行注释,以指示它是一个向Springbean提供横切行为的方面组件。

方面提供了在Springbean中的方法之前、之后或周围执行的建议方法。通知方法是处理请求以执行Springbean中的方法的Handler对象的一个示例。

@Before注释中的执行切入点表达式指定在调用com.example.service包中的任何方法之前执行advice方法。

Flyweight模式
Flyweight是一种结构化的设计模式,它允许在多个上下文中共享具有公共状态的对象,从而减少应用程序的总体内存占用。在Spring中,Flyweight模式在多个地方使用,以优化内存使用并提高性能。

Spring中Flyweight模式最常见的例子之一是对bean使用Singleton作用域。下面是一个例子:

@Service
@Scope("singleton")
public class MyService {
    // ...
}

在本例中,MyService类使用@Service注释进行了注释,该注释告诉Spring该类是一个Springbean。此外,@Scope注释用于指定此bean应创建为Singleton,这意味着仅创建一个bean实例并在应用程序的所有需要它的部分之间共享。

通过使用Singleton作用域,Spring能够避免创建MyService类的多个实例,这可以在大型应用程序中保存大量内存。

Spring中Flyweight模式的另一个例子是使用Object Pool设计模式来管理创建成本很高的对象。下面是一个例子:

@Component
public class MyObjectPool {
    
    private List<MyObject> objects;
    
    public MyObjectPool(int size) {
        objects = new ArrayList<MyObject>(size);
        for (int i = 0; i < size; i++) {
            objects.add(new MyObject());
        }
    }
    
    public MyObject borrowObject() {
        if (objects.isEmpty()) {
            throw new RuntimeException("No more objects in pool");
        }
        return objects.remove(0);
    }
    
    public void returnObject(MyObject object) {
        objects.add(object);
    }
}

在此示例中,MyObjectPool类表示MyObject类的实例的对象池。池是用固定数量的实例创建的,创建每个实例的成本都很高。通过使用对象池,Spring可以避免每次需要时创建和销毁MyObject实例的开销。

borrowObject和returnObject方法是Flyweight模式的示例。当从池中借用对象时,它将返回给调用方,并带有任何必要的状态更改。当不再需要该对象时,它将返回到池中,以便另一个调用方可以重用它。

解释器模式
解释器模式是一种设计模式,它定义了一种语言的语法,并提供了一个解释器来执行语法。在解释器模式中,使用一组规则或表达式定义语法,解释器通过解释这些表达式来评估语法。

在Spring框架中,解释器模式用于实现Spring表达式语言(SpEL)。SpEL是一种功能强大的表达式语言,可用于在Spring应用程序中配置和操作bean。SpEL表达式在运行时计算,可用于将值注入bean属性、调用方法以及对数据执行操作。下面是如何使用SpEL将值注入bean属性的示例:

@Component
public class MyService {

    @Value("#{ systemProperties['myProperty'] }")
    private String myProperty;

    // ...

}

在本例中,使用@Value注释将myProperty系统属性的值注入到myProperty类的MyService字段中。SpEL表达式#{ systemProperties['myProperty'] }在运行时进行计算,并检索myProperty系统属性的值。

SpEL还可以用于执行更复杂的操作,例如调用方法和对数据执行操作。下面是一个例子:

@Component
public class MyService {

    @Value("#{ T(java.lang.Math).random() * 100.0 }")
    private double randomNumber;

    // ...

}

在本例中,使用@Value注释将随机数注入到randomNumber类的MyService字段中。SpEL表达式#{ T(java.lang.Math).random() * 100.0 }调用java.lang.Math.random()方法生成一个0到1之间的随机数,然后将其乘以100,得到一个0到100之间的随机数。

SpEL还可用于访问和操作复杂的数据结构,如映射和列表。下面是一个例子:

@Component
public class MyService {

    @Value("#{ myMap['myKey'] }")
    private String myValue;

    // ...

}

在本例中,使用@Value注释将myKey映射中的myMap键的值注入到myValue类的MyService字段中。SpEL表达式#{ myMap[‘myKey’] }检索myKey映射中myMap键的值。

总结
综上所述,Spring框架为广泛的设计模式提供了强大的支持,包括Singleton,Factory,Template Method,Proxy,Decorator和Observer等。通过有效地理解和应用这些模式,开发人员可以构建高质量、可伸缩和可维护的应用程序。