Spring 5 设计模式 - 开始

Spring 5 设计模式 - 开始

  • 依赖注入
  • AOP
  • template
  • Spring容器
    • bean的生命周期

依赖注入

对象之间的依赖增加复杂性,导致对象之间紧耦合。
比如下面的TransferService组件依赖其他两个组件:TransferRepository和AccountRepository:
Spring 5 设计模式 - 开始_第1张图片

如果直接使用实例:

public class TransferService {
    private AccountRepository accountRepository;
    
    public TransferService () {
        this.accountRepository = new AccountRepository();
    }
    
    public void transferMoney(Account a, Account b) {
        accountRepository.transfer(a, b);
    }
}

TransferService对象需要一个AccountRepository对象,实现账户A到账户B的转账功能。于是,直接增加AccountRepository的实例。这样很难维护,也很难做TransferService的单元测试。

也可以使用工厂模式实现该功能:

public class TransferService {
    private AccountRepository accountRepository;
    
    public TransferService() {
        this.accountRepository = AccountRepositoryFactory.getInstance("jdbc");
    }
    
    public void transferMoney(Account a, Account b) {
        accountRepository.transfer(a, b);
    }
}

上面的代码,我们使用工厂模式增加AccountRepository类的对象。更好的做法是program-to-interface,针对这个具体问题,我们可以增加一个接口,降低耦合:

public interface AccountRepository {
    void transfer();
    //other methods
}

我们可以增加JdbcAccountRepositry类,实现AccountRepository接口:

public class JdbcAccountRepositry implements AccountRepositry{
    //实现AccountRepositry定义的方法
    // 实现其他方法
}

这样,由工厂类增加的对象很容易维护。使用工厂类,也可以增加可配置的对象。但是,为了降低耦合,获取协作组件的时候在业务组件里增加了工厂类。让我们看看依赖注入怎么解决这个问题。

对于依赖注入模式,我们只是定义依赖性,而不用解决对象之间的依赖。看下图,我们会在需要对象的时候注入他们:

Spring 5 设计模式 - 开始_第2张图片

TransferService依赖AccountRepository和TransferRepository。TransferService可以使用多种TransferRepository完成转账功能,比如可以使用JdbcTransferRepository或者是JpaTransferRepository,依赖哪个,由部署环境决定。

public class TransferServiceImpl implements TransferService {
    private TransferRepository transferRepository;
    private AccountRepository accountRepository;
    
    public TransferServiceImpl(TransferRepository transferRepository, AccountRepository accountRepository) {
        this.transferRepository = transferRepository;//TransferRepository is injected
        this.accountRepository = accountRepository;//AccountRepository is injected
    }
    public void transferMoney(Long a, Long b, Amount amount) {
        Account accountA = accountRepository.findByAccountId(a);
        Account accountB = accountRepository.findByAccountId(b);
        transferRepository.transfer(accountA, accountB, amount);
    }
}

TransferServiceImpl没有增加自己的repositories实现,而是在构造器里通过构造参数传递的。这叫构造器注入。这样,TransferServiceImpl没有和任何repositories的实现相耦合,可以根据需要切换不同的实现。
Spring提供了把程序的各个部件组装成程序的支持:

  • 部件不用担心找不到对方
  • 任何部件都可以很容易地被替换

配置代码可以是这的:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService(){
        return new TransferServiceImpl(accountRepository(), transferRepository());
    }
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository();
    }
    @Bean
    public TransferRepository transferRepository() {
        return new JdbcTransferRepository();
    }
}

Spring程序,使用程序上下文的一个实现加载bean定义,放进Spring容器。比如下面的代码,就使用AnnotationConfigApplicationContext加载配置类AppConfig,得到AccountService的一个对象:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TransferMain {
    public static void main(String[] args) {
        //加载上下文
        ConfigurableApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(AppConfig.class);
        //得到 TransferService bean
        TransferService transferService =
                applicationContext.getBean(TransferService.class);
        //转账
        transferService.transferAmmount(100l, 200l, new Amount(2000.0));
        applicationContext.close();
    }
}

AOP

依赖注入让组件之间松耦合,面向切面编程(Spring AOP)可以捕获程序中重复出现的常用功能。能以最优雅的方式,把下列功能分开。可以通过声明的方式,透明地应用这些服务:

  • 日志和跟踪
  • 事务管理
  • 安全
  • 缓存
  • 错误处理
  • 性能监视
  • 自定义业务规则

列表中的组件不是你核心程序的一部分,但是他们是额外的责任,通常称为cross-cutting concerns,在核心责任之上跨多个组件。如果你把这些组件放到你的核心功能,而没有模块化,会导致两个主要问题:

  • Code tangling:比如安全、事务、日志等代码和业务逻辑耦合在一起
  • Code scattering:相同的concern散布在模块之间
    Spring 5 设计模式 - 开始_第3张图片

Spring AOP可以把cross-cutting concern模块化。Spring AOP是通过代理模式实现的:

  • 实现程序逻辑:你实现业务逻辑的时候,不需要担心附加功能,比如日志、安全等。
  • 写aspects,实现cross-cutting concerns
  • 在程序里Weave aspects:把cross-cutting行为放到正确的位置,可以通过声明的方式注入

Spring 5 设计模式 - 开始_第4张图片

比如,使用LoggingAspect实现日志功能:

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {
    @Before("execution(* *.transferAmount(..))")
    public void logBeforeTransfer(){
        System.out.println("####LoggingAspect.logBeforeTransfer() method called before transfer amount####");
    }
    @After("execution(* *.transferAmount(..))")
    public void logAfterTransfer(){
        System.out.println("####LoggingAspect.logAfterTransfer() method called after transfer amount####");
    }
}

修改AppConfig:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    @Bean
    public TransferService transferService(){
        return new TransferServiceImpl(accountRepository(), transferRepository());
    }
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository();
    }
    @Bean
    public TransferRepository transferRepository() {
        return new JdbcTransferRepository();
    }
    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}

template

在企业程序里,经常会看到很多相似的代码。比如使用JDBC,经常会写相同的代码,处理下列问题:

  • 从连接池获取连接
  • 增加PreparedStatement对象
  • 绑定SQL参数
  • 执行PreparedStatement对象
  • 从ResultSet对象检索数据,填充数据容器
  • 释放数据库资源

比如getAccountById方法的代码可能是这样的:

    public Account getAccountById(long id) {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            stmt = conn.prepareStatement(
                    "select id, name, amount from " +
                            "account where id=?");
            stmt.setLong(1, id);
            rs = stmt.executeQuery();
            Account account = null;
            if (rs.next()) {
                account = new Account();
                account.setId(rs.getLong("id"));
                account.setName(rs.getString("name"));
                account.setAmount(rs.getString("amount"));
            }
            return account;
        } catch (SQLException e) {
        } finally {
            if(rs != null) {
                try {
                    rs.close();
                } catch(SQLException e) {}
            }
            if(stmt != null) {
                try {
                    stmt.close();
                } catch(SQLException e) {}
            }
            if(conn != null) {
                try {
                    conn.close();
                } catch(SQLException e) {}
            }
        }
        return null;
    }

Spring JDBC使用模板设计模式,让数据访问代码更简洁,也能防止连接泄漏:

    public Account getAccountById(long id) {
        return jdbcTemplate.queryForObject(
                "select id, name, amoount" +
                        "from account where id=?",
                new RowMapper<Account>() {
                    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                        account = new Account();
                        account.setId(rs.getLong("id"));
                        account.setName(rs.getString("name"));
                        account.setAmount(rs.getString("amount"));
                        return account;
                    }
                },
                id);
    }

Spring容器

有两种不同的Spring容器:

  • Bean factory:对象池,通过配置创建和管理对象
  • Application contexts:bean工厂的简单包装,还提供了额外的程序上下文

bean的生命周期

Spring 5 设计模式 - 开始_第5张图片

  • 加载所有的bean定义,增加有序图(ordered graph)
  • 实例化,运行BeanFactoryPostProcessors(可以在这里修改bean定义)
  • 实例化每个bean
  • 把值和bean引用注入bean的属性
  • 如果bean实现了BeanNameAware接口,就调用setBeanName()方法设置bean的ID
  • 如果bean实现了BeanFactoryAware,就调用setBeanFactory()方法设置bean工厂的引用
  • 如果bean实现了ApplicationContextAware,就调用setApplicationContext()方法设置程序上下文的引用
  • 如果bean实现了BeanPostProcessor,调用postProcessBeforeInitialization()修改bean的实例
  • 如果bean实现了InitializingBean,调用afterPropertiesSet()方法做初始化或者加载资源。也可以用其他方法实现这一步,比如@PostConstruct注解
  • 如果bean实现了BeanPostProcessor,调用postProcessAfterInitialization(),在初始化之后修改bean。此时,bean已经可以使用,你的程序可以通过使用getBean()访问这个bean。调用程序上下文的close()之前,bean一直活着
  • 如果bean实现了DisposibleBean,Spring调用它的destroy()方法执行销毁过程,释放资源。也可以用其他方法实现这一步,@PreDestroy

你可能感兴趣的:(设计模式,java,Spring5)