zoukankan      html  css  js  c++  java
  • Java笔记23

    • Spring 支持快速开发Java EE的框架

    • 主要模块:

      • 支持IoC和AOP的容器
      • 支持JDBC和ORM的数据访问模块
      • 支持声明式事务的模块
      • 支持基于Servlet的MVC开发
      • 支持基于Reactive的WEB开发
      • 集成JMS, JavaMail, JMX, 缓存等其他模块
    • 容器:

      • 为某种特定组件的运行提供一种必要支持的一个软件环境
      • 提供需要底层服务
    • Tomcat容器:

      • 为Servlet提供运行环境
      • 提供类似Http解析的底层环境
    • Docker容器: 提供Linux环境, 以便运行一个Linux进程

    • IoC容器:

      • 管理所有轻量级的JavaBean组件
      • 组件生命周期管理
      • 配置和组装服务
      • AOP支持
      • AOP基础上的声明式事务服务

    IoC原理

    • Inversion of Control: 控制反转

    • 在线书城例子:

      1. BookServicesUserServices, 都需要读取配置, 实例化Config, 根据配置, 实例化DataSource;
      2. BookServicesUserServices可以共享一个DataSource; CarServletHistoryServlet也可以共享一个BookServicesUserServices. 谁来创建, 谁来使用, 不好处理.
      3. 很多组件需要销毁, 以便释放资源.例如DataSource, 如果确保使用方已经全部销毁, 不好处理.
      4. 组件越来越多, 共享的组件会越来越复杂.
      5. 测试某个组件是复杂的, 必须在真是的数据库环境下运行.
    • 传统开发: 生命周期与相互之间的依赖关系如果组件自己维护, 增加系统的复杂度, 导致组件之间极为耦合, 给测试和维护带来复杂

    • 核心问题:

      1. 谁负责创建组件?
      2. 谁负责根据依赖关系组装组件?
      3. 销毁时, 如何按照依赖关系正确销毁?
    • 使用IoC解决问题: 组件的创建, 装配控制权从组件本身移动到容器中

    • 使用注入机制: 进行组件装配

    public class BookServices {
      private DataSource dataSource;
      public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
      }
    }
    
    • 好处:

      1. BookServices不再关系如何创建DataSource, 因此不必编写数据库配置之类的
      2. DataSource实例被注入到BookServices, 也可以被注入到UserServices, 共享一个组件变得非常简单
      3. 测试BookServices非常简单, 因为是注入的DataSource, 可以使用内存数据库, 而不是真是的MySQL配置
    • IoC依赖注入: 将组件的创建配置和使用相分离, 并且由IoC容器负责管理组件的生命周期.

    • 使用XML文件声明: 容器如何创建组件, 以及组件见的依赖关系.

    <beans>
      <bean id="dataSource" class="HiDataSource" />
      <bean id="bookServices" class="bookServices">
        <property name="dataSource" ref="dataSource" />
      </bean>
      <bean id="userServlet" class="UserServices">
        <property name="dataSource" ref="dataSource" />
      </bean>
    </beans>
    
    • 声明创建三个JavaBean组件, 并把id为dataSource组件, 通过属性dataSource(即调用setDataSource()方法)注入到另外两个组件
    • 所有的组件都称为JavaBean, 配置一个组件就是配置一个Bean

    依赖注入方式

    • 可以通过set()方法实现
    • 也可以通过构造方法实现
    public class BookServices {
      private DataSource dataSource;
      public BookServices(DataSource dataSource) {
        this.dataSource = dataSource;
      }
    }
    

    无入侵容器

    • 应用程序无需实现Spring的特定接口, 组件不知道自己在Spring的容器中运行.
    • 好处:
      • 应用程序既可以在Spring的IoC容器中运行, 也可以自己编写代码自己组装.
      • 测试的时候, 并不依赖Spring容器, 可单独进行测试, 大大提高开发效率.

    装配Bean

    • 每个<bean ...>都有一个id标识, 相当于Bean的唯一ID;

    • userServiceBean中, 通过<property name="..." ref="...">注入了另一个Bean;

    • Bean的顺序并不重要, Spring根据依赖关系会自动正确初始化;

    • *Spring容器通过读取XML文件后, 使用反射完成

    • 使用spring框架操作

    • ClassPathXmlApplicationContext自动从classpath中查找指定的XML的配置文件

    public class Main {
    
      public static void main(String[] args) {
        // 创建一个Spring的IoC容器, 然后配置加载文件
        // 让Spring容器为我们创建并装配好配置文件中指定的所有Bean
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 获取Bean:
        UserService userService = context.getBean(UserService.class);
        // 正常调用
        User user = userService.Login("bob@example", "password");
        System.out.println(user.getName());
      }
    }
    
    • Spring还可以通过另一种IoC容器叫做BeanFactory

    使用Annotation配置

    @Component // 声明一个bean
    public class UserService {
      @Autowired // 自动注入
      private MailService mailService;
    
      public void setMailService(@Autowired MailService mailService) { // 自动注入
        this.mailService = mailService;
      }
    }
    
    • 一般写在字段上, 通常使用package权限字段, 以便测试
    /**
     * AppConfig
     */
    @Configuration // 配置类
    @ComponentScan // 自动搜索当前类所在的包以及子包
    public class AppConfig {
      public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        User user = userService.Login("bob@example.com", "password");
        System.out.println(user.getName());
      }
    }
    
    • 使用注解方式大大简化Spring的配置
      • 每个Bean被标注为@Component并正确使用@Autowired注入;
      • 配置类被标注为@Configuration@ComponentScan;
      • 所有Bean均在指定包以及子包内.
      • 配置AppConfig位于自定义的顶层包

    定制Bean

    Scope

    • 对于Spring容器来说, 一个Bean标记为Component后, 会自动创建一个单例(Singleton)

    • 容器初始化时创建Bean, 容器关闭前销毁Bean

    • 容器运行期间调用getBean(Class)获取到的Bean总是一个实例

    • 使用@Scope注解, 声明一个Prototype的Bean时, 每次调用getBean(Class)每次都会返回一个新的实例

    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //  @Scope("prototype")
    public class MailSession {
      
    }
    

    注入List

    • 自动把所有的类型为Validator的Bean装配为一个List注入进来
    @Component
    public class Validators {
      @Autowired
      List<Validator> validators;
    
      public void validate(String email, String password, String name) {
        for (Validator validator: validators) {
          validator.validate(email, password, name);
        }
      }
    }
    
    • Spring是扫描classpath获得到所有的Bean, 而List是有序的, 可以通过@Order注解写明
    @Component
    @Order(1)
    public class EmailValidator implements Validator{
    
      @Override
      public void validate(String email, String password, String name) {
        if (!email.matches("^[a-z0-9]+\@[a-z0-9]+\.[a-z]{2,10}$")) {
          throw new IllegalArgumentException("invalid email: " + email);
        }
      }
      
    }
    

    可选注入

    • @Autowired(required = false)如果找到了, 就注入, 没找到, 就忽略.

    创建第三方Bean

    • Bean不再我们自己的package管理之内, 在@Configuration类中编写Java方法创建并返回
    @Configuration
    @ComponentScan
    public class AppConfig {
      @Bean // 创建一个Bean, 这里的Bean是单例模式
      ZoneId createZoneId() {
        return ZoneId.of("Z");
      }
    }
    

    初始化和销毁

    • 首先引入注解标准
    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
    </dependency>
    
      @PostConstruct
      public void init() {
        System.out.println("Init mail service with zoneId = " + this.zoneId);
      }
    
      @PreDestroy
      public void shutdown() {
        System.out.println("Shut mail service");
      }
    
    • Spring容器对Bean初始化流程:

      1. 调用构造方法创建MailService流程
      2. 根据@Autowired进行注入
      3. 调用标记有PostConstructinit()方法进行初始化
    • Spring只根据Annotation查找无参数方法, 对方法名不作要求

    使用别名

    • 对于同一种Bean, 容器只能创建一实例, 某些情况下, 我们需要对同一种类型的Bean创建多个实例.
      @Bean("z")
      @Primary // 定义为主要Bean
      ZoneId createZoneOfZ() {
        return ZoneId.of("Z");
      }
    
      @Bean
      @Qualifier("UTC8")
      ZoneId createZoneOFUTC8() {
        return ZoneId.of("UTC+08:00");
      }
    
    
      @Autowired
      @Qualifier("z")
      private ZoneId zoneId;
    
    

    使用FactoryBean

    • 可以定义个工厂, 然后由工厂创建真正的Bean
    @Component
    public class ZoneIdFactoryBean implements FactoryBean<ZoneId>{
    
      String zone = "Z";
    
      @Override
      public ZoneId getObject() throws Exception {
        return ZoneId.of(zone);
      }
    
      @Override
      public Class<?> getObjectType() {
        return ZoneId.class;
      }
    }
    
    • Bean实现了FactoryBean接口后, Spring会先实例化这个工厂, 然后调用getObject()创建真正的Bean.
    • getObjectType()可以指定创建的Bean的类型, 因为指定类型不一定和实际类型一致, 可以是接口或者抽象类.

    使用Resource

    • 使用Spring容器, 我们可以把配置文件, 资源文件等注入进来, 方便使用
    @Component
    public class AppService {
    
      // Value("file:/path/to/logo.txt")
      @Value("classpath:/logo.txt")
      private Resource resource;
    
      private String logo;
    
      @PostConstruct
      public void init() throws IOException {
        try (BufferedReader reader = new BufferedReader(
          new InputStreamReader(
            resource.getInputStream(),
            StandardCharsets.UTF_8
          )
        )) {
          this.logo = reader.lines().collect(Collectors.joining("
    "));
          System.out.println(this.logo);
        }
      }
    }
    

    注入配置

    • 通告注解写到方法参数中
    @Value("${app.zone:Z}")
    String zoneId;
    
    @Bean
    ZoneId createZoneId(@Value("${app.zone:Z}") String zoneId) {
        return ZoneId.of(zoneId);
    }
    
    • 通告简单的JavaBean持有
    @Component
    public class SmtpConfig {
        @Value("${smtp.host}")
        private String host;
    
        @Value("${smtp.port:25}")
        private int port;
    
        public String getHost() {
            return host;
        }
    
        public int getPort() {
            return port;
        }
    }
    
    // 进行读取
    @Component
    public class MailService {
        @Value("#{smtpConfig.host}")
        private String smtpHost;
    
        @Value("#{smtpConfig.port}")
        private int smtpPort;
    }
    

    使用条件装配

    • Spring容器根据@Profile来决定是否创建:

    • 三种环境

      • native: 开发
      • test: 测试
      • producation: 生产
    • 程序运行时, 加上JVM蚕食, 可以指定启动方法

    • 并可以表示使用test环境, master分支代码

    -Dspring.profiles.active=test,master
    

    使用Conditional

    
    public class OnSmtpEnvCondition implements Condition {
    
      @Override
      public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
      }
      
    }
    
    @Component
    @Conditional(OnSmtpEnvCondition.class)
    public class MailService {
    }
    
  • 相关阅读:
    F# 语法概览
    Excel 帮助无法正常工作的解决方法
    autofac 组件的实例范围
    visual studio code 中隐藏从 ts 文件生成的 js 文件和 map 文件
    git vim 编辑器基本操作
    nhibernate 中 lazy="no-proxy" 时的问题
    什么是数据科学
    Elasticsearch 疑难解惑
    Hadoop MapReduce执行过程实例分析
    深入浅出JVM
  • 原文地址:https://www.cnblogs.com/zhangrunhao/p/14207326.html
Copyright © 2011-2022 走看看