Java面試題:Spring框架除了IOC和AOP,還有哪些好玩的設計模式?

猫鱼吐泡泡發表於2024-05-14

Spring是一個基於Java的企業級應用程式開發框架,它使用了多種設計模式來實現其各種特性和功能。本文將介紹一些在Spring中使用的常見設計模式以及相應的程式碼示例和說明。

單例模式

單例模式是Spring中最常用的設計模式之一。在ApplicationContext中,Bean預設為單例模式。當我們建立一個Bean時,預設情況下它就是單例的。這意味著當Bean被請求時,Spring會返回相同的例項。下面是一個示例程式碼:

public class MyBean {
    // ...
}

@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

}

在上面的程式碼中,myBean()方法返回了MyBean類的例項,這個例項將作為單例物件存在於ApplicationContext中。

在Spring AOP中,切面預設為單例模式。這意味著切面物件只會建立一次,並與所有目標物件共享。下面的程式碼演示瞭如何在Spring AOP中配置一個單例切面:

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        // ...
    }

}

這裡,LoggingAspect類用@Aspect註解進行了標註,它包含了@Before通知,該通知將在com.example.service包中的所有方法執行前執行。由於LoggingAspect是一個@Component,所以它將被Spring自動掃描並建立一個單例例項。

在Spring MVC中,控制器(Controller)也通常是單例的。下面是一個簡單的控制器類示例:

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }

}

工廠模式

在Spring框架中工廠模式是一種常用的模式之一。下面我將介紹Spring中使用工廠模式的幾個具體示例:

BeanFactory

Spring 的核心容器是BeanFactory和其子介面ApplicationContext。其中,BeanFactory使用了工廠模式來建立和管理bean例項。它包含了建立、配置和管理 bean 的所有功能,如下所示:

public interface BeanFactory {
    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    boolean containsBean(String name);
}

上述程式碼中,BeanFactory介面定義了一個getBean()方法,透過傳入bean的名稱或型別,返回相應的bean例項。這裡的getBean()方法就是工廠方法。

FactoryBean

FactoryBean 是 Spring 中另一個使用工廠模式的類。它用於建立複雜的 bean,這些 bean 可以有自己的生命週期、作用域和依賴項等。

public interface FactoryBean<T> {

    T getObject() throws Exception;

    Class<?> getObjectType();

    boolean isSingleton();
}

述程式碼中,FactoryBean 定義了一個getObject()方法,用於建立並返回一個特定型別的bean。該方法會在應用程式需要訪問bean時被呼叫。

MessageSource

Spring 的國際化支援是基於MessageSource介面實現的。MessageSource為應用程式提供了訪問訊息資源的方法,如下所示:

public interface MessageSource {
    String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
}

上述程式碼中,getMessage()方法使用工廠模式建立和管理訊息資源。它接收訊息程式碼、引數、預設訊息和語言環境等引數,並返回相應的訊息字串。

代理模式

在Spring AOP中,代理模式被廣泛應用。Spring使用JDK動態代理和CGLIB代理來建立切面。下面是一個簡單的使用註解方式配置切面的示例:

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(public * com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        // ...
    }

}

在上面的程式碼中,LoggingAspect類使用了@Aspect和@Component註解進行標註,表明它是一個切面,並會被Spring自動掃描並建立代理物件。在@Before通知中,執行方法呼叫前進行日誌記錄。

在Spring事務管理中,代理模式也被廣泛使用。Spring使用動態代理技術來實現宣告式事務管理。下面是一個使用@Transactional註解來宣告事務的示例:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional
    public User createUser(User user) {
        return userRepository.save(user);
    }

}

在上面的程式碼中,createUser()方法使用@Transactional註解標記,Spring將在該方法呼叫之前建立一個代理物件。當然,這只是一個簡單的示例,實際上,在複雜的應用程式中,Spring可以再透過多種方式來宣告式事務。

對於Spring MVC中的控制器類,我們也可以使用代理模式來增強其功能,例如在控制器方法之前和之後新增日誌記錄。下面是一個基於註解方式實現AOP攔截器的示例:

@Aspect
@Component
public class LoggingInterceptor {

    @Before("execution(* com.example.controller.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        // ...
    }

    @AfterReturning(value = "execution(* com.example.controller.*.*(..))", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        // ...
    }

}

在上面的程式碼中,LoggingInterceptor類使用了@Aspect和@Component註解進行標註,表明它是一個切面,並且會被Spring自動掃描並建立代理物件。在@Before通知中,執行方法呼叫前進行日誌記錄,在@AfterReturning通知中,執行方法呼叫後進行日誌記錄。

觀察者模式

Spring中的事件機制也是基於觀察者模式實現的。在Spring中,所有的Bean都可以作為事件源釋出事件,其他的Bean則可以透過註冊監聽器來響應這些事件。

ApplicationEventPublisher

ApplicationEventPublisher是Spring 框架中使用觀察者模式的一個類。它負責釋出事件並通知已註冊的監聽器。以下是ApplicationEventPublisher的程式碼示例:

public interface ApplicationEventPublisher {
    void publishEvent(ApplicationEvent event);
}

上述程式碼中,publishEvent()方法用於釋出一個事件,並通知已註冊的所有監聽器。具體的監聽器實現可以透過實現ApplicationListener介面來完成。

ApplicationContext

ApplicationContext是Spring的核心介面之一。它擴充套件了BeanFactory介面,並在其基礎上新增了更多的功能,例如事件釋出和提供環境資訊等。以下是 ApplicationContext使用觀察者模式的程式碼示例:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

    void publishEvent(ApplicationEvent event);

    String[] getBeanNamesForType(ResolvableType type);

    <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;
}

上述程式碼中,publishEvent()方法也用於釋出事件,並通知已註冊的所有監聽器。與 ApplicationEventPublisher不同的是,ApplicationContext繼承了多個介面,這使得它可以處理各種型別的事件。

BeanPostProcessor

BeanPostProcessor是Spring框架中一個可插入的回撥介面,用於在bean例項化和配置的過程中提供擴充套件點。以下是BeanPostProcessor使用觀察者模式的程式碼示例:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

postProcessBeforeInitialization()和postProcessAfterInitialization()方法分別在bean例項化和初始化之前/之後被呼叫。可以將這些方法視為鉤子函式,可以在其中新增自定義邏輯以修改或擴充套件bean的預設行為。

責任鏈模式

責任鏈模式是一種行為型設計模式,它允許你將請求沿著處理鏈傳遞,直到其中一個處理程式處理該請求。

HandlerInterceptor

HandlerInterceptor是Spring MVC中使用責任鏈模式的一個類。它提供了多個方法,例如 preHandle()、postHandle()和afterCompletion()等,可以在請求處理過程中攔截並修改請求和響應。以下是HandlerInterceptor的程式碼示例:

public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                    @Nullable ModelAndView modelAndView) throws Exception;

    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                         @Nullable Exception ex) throws Exception;
}

上述程式碼中,HandlerInterceptor提供了三個方法,分別在請求處理前、處理後、以及完成後呼叫。透過實現這些方法,在請求處理過程中可以執行自定義邏輯,例如驗證使用者身份、記錄日誌等。

AbstractRequestLoggingFilter

AbstractRequestLoggingFilter是Spring中使用責任鏈模式的另一個類。它提供了預先和後續處理請求和響應的方法,可以進行訪問日誌記錄。

以下是 AbstractRequestLoggingFilter的程式碼示例:

public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter {

    protected void beforeRequest(HttpServletRequest request, String message) {}

    protected void afterRequest(HttpServletRequest request, String message) {}
}

上述程式碼中,AbstractRequestLoggingFilter的beforeRequest()和afterRequest()方法分別在請求處理前和處理後呼叫。透過實現這些方法,可以記錄訪問日誌,包括請求的地址、引數等資訊。

HandlerExceptionResolver

HandlerExceptionResolver是Spring MVC中使用責任鏈模式的另一個類。它提供了多個方法,例如resolveException()和shouldHandle()等,可以處理異常並決定是否繼續執行下一個處理器。以下是HandlerExceptionResolver的程式碼示例:

public interface HandlerExceptionResolver {

    @Nullable
    ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler,
            Exception ex);

    boolean shouldHandle(HttpServletRequest request, @Nullable Exception ex);
}

上述程式碼中,HandlerExceptionResolver的resolveException()方法用於處理異常並返回ModelAndView物件,該物件可以包含自定義的錯誤頁面或其他錯誤資訊。而 shouldHandle()方法則用於判斷是否應該由當前處理器處理異常,如果返回false,則會繼續執行下一個處理器。

模板方法模式

模板方法模式是一種行為型設計模式,它定義了一個演算法的骨架,並允許子類實現演算法中的某些步驟。在Spring框架中,JdbcTemplate和HibernateTemplate就是使用了模板方法模式的例子。

JdbcTemplate

JdbcTemplate是Spring中使用模板方法模式的一個類。它提供了多個方法,例如 update()、query()等,可以執行SQL語句並返回結果。以下是JdbcTemplate的程式碼示例:

public class JdbcTemplate {

    public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
        // ...
    }

    // ...

    public int update(String sql, Object... args) throws DataAccessException {
        // ...
    }

    public <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
        // ...
    }

    // ...
}

上述程式碼中,JdbcTemplate 提供了execute()、update()和query()等方法,它們都使用了模板方法模式。其中,execute()方法是一個模板方法,它接受一個 ConnectionCallback物件並執行其中的doInConnection()方法,該方法由子類實現。而 update()和 query()方法也是模板方法,它們都呼叫了execute()方法,並傳入不同的引數。

HibernateTemplate

HibernateTemplate是Spring中使用模板方法模式的另一個類。它提供了多個方法,例如 save()、delete()等,可以操作Hibernate實體並返回結果。以下是HibernateTemplate 的程式碼示例:

public class HibernateTemplate extends HibernateAccessor {

    public Object execute(HibernateCallback<?> action) throws DataAccessException {
        // ...
    }

    // ...

    public void save(Object entity) throws DataAccessException {
        // ...
    }

    public void delete(Object entity) throws DataAccessException {
        // ...
    }

    // ...
}

上述程式碼中,HibernateTemplate提供了 execute()、save()和 delete()等方法,它們也都使用了模板方法模式。其中,execute()方法是一個模板方法,它接受一個 HibernateCallback物件並執行其中的doInHibernate()方法,該方法由子類實現。而 save()和delete()方法也是模板方法,它們都呼叫了execute()方法,並傳入不同的引數。

策略模式

在Spring框架中,策略模式被廣泛應用於各種場景,例如事務管理、快取管理等。以下是 Spring中使用策略模式的幾個具體示例:

事務管理

Spring提供了多種事務管理方式,其中之一就是基於策略模式實現的。該模式下,開發人員需要將不同的事務屬性(如傳播行為、隔離級別等)封裝到TransactionDefinition 介面的實現類中,並將其作為引數傳遞給PlatformTransactionManager的方法。以下是一個示例程式碼:

public class TransactionalTest {

    private PlatformTransactionManager transactionManager;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void doTransactional() {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);

        TransactionStatus status = transactionManager.getTransaction(definition);

        try {
            // 執行事務操作
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
        }
    }
}

上述程式碼中,TransactionalTest類使用了策略模式來管理事務。它將 DefaultTransactionDefinition物件作為引數傳遞給PlatformTransactionManager的方法,並在try-catch塊中執行事務操作。

快取管理

Spring 提供了多種快取管理方式,其中之一就是基於策略模式實現的。該模式下,開發人員需要將不同的快取屬性(如快取型別、快取超時時間等)封裝到CacheManager和Cache 介面的實現類中,並將其作為引數傳遞給CacheResolver和Cache的方法。以下是一個示例程式碼:

public class CacheTest {

    private CacheResolver cacheResolver;

    public void setCacheResolver(CacheResolver cacheResolver) {
        this.cacheResolver = cacheResolver;
    }

    public void doCached() {
        Cache cache = cacheResolver.resolveCache("myCache");

        Object value = cache.get("myKey");
        if (value == null) {
            // 從資料庫或其他儲存介質中獲取資料
            value = "myValue";

            cache.put("myKey", value);
        }
    }
}

上述程式碼中,CacheTest類使用了策略模式來管理快取。它將CacheResolver物件作為引數傳遞給resolveCache()方法,並根據快取的鍵值對判斷是否需要從快取中獲取資料。

往期面試題:

Java面試題:如果你這樣做,你會後悔的,兩次啟動同一個執行緒~~~

Java面試題:@PostConstruct、init-method和afterPropertiesSet執行順序?

Java面試題:SimpleDateFormat是執行緒安全的嗎?使用時應該注意什麼?

Java面試題:細數ThreadLocal大坑,記憶體洩露本可避免

Java面試題:請談談對ThreadLocal的理解?

Java面試題:為什麼HashMap不建議使用物件作為Key?

Java面試題:你知道Spring的IOC嗎?那麼,它為什麼這麼重要呢?

Java面試題:執行緒池內“鬧情緒”的執行緒,怎麼辦?

Java面試題:Spring Bean執行緒安全?別擔心,只要你不寫併發程式碼就好了!

相關文章