• 【Guice】Guice入门


    学习地址

    官方指导文档

    慕课网使用Google Guice实现依赖注入

    注入

    构造函数注入和成员变量注入

    注意:在构造函数中注入的参数并不一定都必须在AbstractModule中绑定,Guice会每次新建一个实例注入

    绑定(入口)-->Guice-->@Inject(出口)

    主要使用构造函数注入,也可以给成员变量注入但是一般不用。使用@Inject构造器注入如下所示:

    // 先绑定再注入
    // 1、绑定
    public class ServerModule extends AbstractModule {
    	@Override
    	protected void configure() {
    	
    		bind(OrderService.class).to(OrderServiceImpl.class);
    		bind(PaymentService.class).to(PaymentServiceImpl.class);
    		bind(PriceService.class).to(PriceServiceImpl.class);
    }
    // 2、注入
    public class OrderServiceImpl implements OrderService {
    
    	// Dependencies,使用构造器注入后不再改变,使用final修饰
    	private final PriceService priceService;
    	private final PaymentService paymentService;
    	private final SessionManager sessionManager;
        private final NoBindService nobindService;
    
    	// States 这种成员变量会被改变,不能添加final修饰
    	private Long ordersPaid = 0L;
    
        // 下面的NoBindService并未在Module中绑定,但是是可以正常注入的,绑定和注入并不是强相关
    	@Inject
    	public OrderServiceImpl(PriceService priceService,
    			PaymentService paymentService,
    			SessionManager sessionManager, NoBindService nobindService) {
    		super();
    		this.priceService = priceService;
    		this.paymentService = paymentService;
    		this.sessionManager = sessionManager;
            this.nobindService = nobindService;
    	}
    }
    

    构造函数注入:

    • 使用final来区分dependency和状态
    • 注入时不需要考虑如何实现/绑定

    Provider注入

    Guice 使用Provider 表示创建对象的工厂以满足依赖要求。Provider是只包含一个方法的接口:

    interface Provider<T> {
      /** Provides an instance of T.**/
      T get();
    }
    

    应用场景

    1、懒加载,如数据库连接比较耗时,可以在需要的时候通过Provider获取,而不是直接使用构造器注入指定

    • databaseConnectionProvider.get()

    2、多个实例,使用构造器注入只能选择固定的日志输出器

    • logEntryProvider.get()

    注入Provider方式

    我们可以向Guice请求Provider,不论如何绑定(注入和绑定是完全分开的过程)

    DataBaseConnection dbConn

    -->Provider<DataBaseConnection> dbConnProvider

    一般无需自己实现Provider,Guice已经提供,且会考虑生命对象周期,但是需要时可以自己实现Provider。

    通过@Provides 绑定Provider之后,就可以通过其唯一的get方法获取想要的值。

    // 先绑定,再注入
    // 1、绑定
    public class ServerModule extends AbstractModule {
    
    	@Provides Long generateSesssionId() {
    		return System.currentTimeMillis();
    	}
    }
    // 2、注入
    public class SessionManager {
    	private final Provider<Long> sessionIdProvider;
    
    	@Inject
    	public SessionManager(Provider<Long> sessionIdProvider) {
    		super();
    		this.sessionIdProvider = sessionIdProvider;
    	}
    
    	public Long getSessionId() {
    		return sessionIdProvider.get();
    	}
    
    }
    

    命名注入

    场景,如果绑定了多个同类型的实例,如String等且未进行标识,则注入时就会发生冲突,此时需要使用命名的方式加以区分。

    有以下两种注入形式:

    @Inject @Named("dbSpec") private String dbSpec;
    @Inject @LogFileName private String logFileName;
    

    使用@Named

    • 参数来自配置文件,命令行
    • 为了开发方便

    使用属性

    • 通常采用此方法

    下面是示例,获取名为@SessionId的Long类型

    //先定义注解、再绑定、最后注入
    // 1、定义注解
    @Retention(RetentionPolicy.RUNTIME)
    @BindingAnnotation
    public @interface SessionId {
    
    }
    // 2、绑定
    public class ServerModule extends AbstractModule {
    	@Provides @SessionId Long generateSesssionId() {
    		return System.currentTimeMillis();
    	}
    }
    // 3、注入
    public class SessionManager {
    	private final Provider<Long> sessionIdProvider;
    
    	@Inject
    	public SessionManager(
    			@SessionId Provider<Long> sessionIdProvider) {
    		super();
    		this.sessionIdProvider = sessionIdProvider;
    	}
    
    	public Long getSessionId() {
    		return sessionIdProvider.get();
    	}
    
    }
    
    

    绑定

    结构

    注入和绑定是分开的,

    分类

    一般通过继承AbstractModule,实现其configure方法进行绑定。

    • 类名绑定
    • 实例绑定 Instance Bindings
    • 连接绑定 Linked Bindings
    • Provider绑定 Provider Bindings
    • 命名绑定 Binding Annotations
    • 泛型绑定
    • 集合绑定

    类名绑定和实例绑定

    类名绑定是最常见的绑定方式,将类的实现绑定到接口类上;

    实例绑定除了可以绑定具体类到它上,还可以直接使用new的实例绑定。

    
    public class ServerModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
            // 类名绑定 将实现类OrderServiceImpl绑定到接口OrderService上
            // 表示需要OrderService时,注入OrderServiceImpl的一个实例
    		bind(OrderService.class).to(OrderServiceImpl.class);
            // 实例绑定 直接将具体的实例绑定
            // 表示需要PriceService时,注入PriceServiceImpl实例
            bind(PriceService.class).toInstance(new PriceServiceImpl());
    
    	}
    }
    

    连接绑定

    连接绑定可以不断将子类绑定到父类(接口)上。可以是链式结构。下面的示例中,当请求PriceService时,最终会返回new 出来的PriceServiceImpl实例。可以按下面的绑定持续绑定,只要不形成环,最终Guice会找到最后绑定的值。

    public class ServerModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
            // 类名绑定 将实现类PriceServiceImpl绑定到接口PriceService上
            // 表示需要PriceService时,注入PriceServiceImpl的一个实例
    		bind(PriceService.class).to(PriceServiceImpl.class);
            // 实例绑定 直接将具体的实例绑定
            // 表示需要PriceServiceImpl时,注入PriceServiceImpl实例
            bind(PriceServiceImpl.class).toInstance(new PriceServiceImpl());
    	}
    }
    

    Provider绑定

    当你创建对象时,使用@Provides方法。该方法必须定义在一个module中,同时要使用@Provides注解。返回值是绑定类型。,当injector需要该类型实例时,会调用该方法。

    方式一、在module中像上面的几种绑定一样绑定,然后直接使用Provider即可,如下示例

    // 1、先绑定
    public class ServerModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
            // 类名绑定 将实现类PriceServiceImpl绑定到接口PriceService上
            // 表示需要PriceService时,注入PriceServiceImpl的一个实例
    				bind(PriceService.class).to(PriceServiceImpl.class);
    	}
    }
    // 2、直接注入Provider
    public class OrderServiceImpl implements OrderService {
    
    	// Dependencies,使用构造器注入后不再改变,使用final修饰
    	private final Provider<PriceService> priceServiceProvider;
    	
    	@Inject
    	public OrderServiceImpl(Provider<PriceService> priceServiceProvider,) {
    		super();
    		this.priceServiceProvider = priceServiceProvider;
    	}
    }
    

    方式二、使用@Provides注解,这种方式比较常用 ,可以带参数或不带参数

    //1、先绑定(在AbstarctModule中)
    public class ServerModule extends AbstractModule {
        
        @Override
      protected void configure() {
        bind(PriceService.class).to(PriceServiceImpl.class);
      }
    
      @Provides
      TransactionLog provideTransactionLog() {
        DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
        transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
        transactionLog.setThreadPoolSize(30);
        return transactionLog;
      }
        // 可以带命名绑定
    	@Provides @SessionId Long generateSesssionId() {
    		return System.currentTimeMillis();
    	}
        // 可以带参数,Guice会自动注入参数,默认每次是个新的实例
    	@Provides List<String>  getCurrencyList(PriceService priceService) {
    		return priceService.getCurrencyList();
    	}
        
    }
    // 2、注入
    public class SessionManager {
    	private final Provider<Long> sessionIdProvider;
        // 不使用Provider包裹也可以直接注入List<String>
    	private final Provider<List<String>> currencyListProvider;
    	@Inject
    	public SessionManager(
    			@SessionId Provider<Long> sessionIdProvider, Provider<List<String>> currencyListProvider) {
    		super();
    		this.sessionIdProvider = sessionIdProvider;
            this.currencyList = currencyList;
    
    	}
    
    	public Long getSessionId() {
    		return sessionIdProvider.get();
    	}
        public List<String> getCurrencyList() {
    		return currencyListProvider.get();
    	}
    
    }
    

    当@Provides方法变得复杂,可以考虑将他们移动到自己的类中。provider类实现了Guice的Provider接口。

    Provider的实现类包含它的所有依赖,通过使用@Inject注解构造器接收依赖参数。通过实现Provider接口定义完全类型安全的返回类型。

    public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
      private final Connection connection;
    
      @Inject
      public DatabaseTransactionLogProvider(Connection connection) {
        this.connection = connection;
      }
    
      public TransactionLog get() {
        DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
        transactionLog.setConnection(connection);
        return transactionLog;
      }
    }
    

    最后,通过.toProvider进行绑定,这样就可以在TransactionLog实例中获取DatabaseTransactionLogProvider。

    public class BillingModule extends AbstractModule {
      @Override
      protected void configure() {
        bind(TransactionLog.class)
            .toProvider(DatabaseTransactionLogProvider.class);
      }
    }
    

    命名绑定

    当想要指定同一类型的不同绑定时需要使用可以指定名称。有两种方式:一是自定义注解命名,二是使用@Named注解

    //1、先绑定(在AbstarctModule中)
    public class ServerModule extends AbstractModule {
        
        @Override
      protected void configure() {
        bind(PriceService.class).to(PriceServiceImpl.class);
      }
    
      @Provides
      TransactionLog provideTransactionLog() {
        DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
        transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
        transactionLog.setThreadPoolSize(30);
        return transactionLog;
      }
        // 可以带命名绑定
    	@Provides @SessionId Long generateSesssionId() {
    		return System.currentTimeMillis();
    	}
        // 可以带参数
    	@Provides @Named("currencyList") List<String>  getCurrencyList(PriceService priceService) {
    		return priceService.getCurrencyList();
    	}
        
    }
    // 2、注入
    public class SessionManager {
    	private final Provider<Long> sessionIdProvider;
        // 不使用Provider包裹也可以直接注入List<String>
    	private final Provider<List<String>> currencyListProvider;
    	@Inject
    	public SessionManager(
    			@SessionId Provider<Long> sessionIdProvider,  @Named("currencyList") Provider<List<String>> currencyListProvider) {
    		super();
    		this.sessionIdProvider = sessionIdProvider;
            this.currencyList = currencyList;
    
    	}
    
    	public Long getSessionId() {
    		return sessionIdProvider.get();
    	}
        public List<String> getCurrencyList() {
    		return currencyListProvider.get();
    	}
    
    }
    

    如果不通过@Provides添加命名绑定@SessionId配置也可以通过在module的configure的bind方式绑定,但是这种方式一旦实例化就不会改变,注意与@Provides方式的区别。

    public class ServerModule extends AbstractModule {
        
        @Override
      protected void configure() {
            bind(Long.class).annotateWith(SessionId.class).toInstance(System.currentTimeMillis());
     bind(Long.class).annotateWith(Nameds.named("appId")).toInstance(123L);
      }
    }
    

    泛型绑定

    public class ServerModule extends AbstractModule {
        
        @Override
      protected void configure() {
          // 也可以添加命名绑定,使用annotateWith
            bind(new TypeLiteral<List<String>>(){}).toInstance(Arrays.asList("CNY","USD"));
      }
    }
    

    集合绑定

    上面泛型绑定的例子也是集合绑定,集合绑定还可以使用Multibinder在不同的module中配置到同一个集合中。

    // 1、不同的module中使用Multibinder绑定set集合
    public class GlobalModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
    		// Adds EUR, USD support
            // 此处也可以不单独提取currencyBinder,直接使用Multibinder.newSetBinder(binder(), String.class).addBinding...的形式,也是可以根据类型获取到set<String>
    		Multibinder<String> currencyBinder =
    				Multibinder.newSetBinder(binder(), String.class);
    		currencyBinder.addBinding().toInstance("EUR");
    		currencyBinder.addBinding().toInstance("USD");
    	}
    
    }
    public class ChinaModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
    		// Adds CNY support
    		Multibinder.newSetBinder(binder(), String.class)
    			.addBinding().toInstance("CNY");
    
    	}
    
    }
    // 组合 module
    public class ServerModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
    		install(new ChinaModule());
    		install(new GlobalModule());
    	}
    }
    // 2、注入Set
    public class PriceServiceImpl implements PriceService {
    	private final Set<String> supportedCurrencies;
    	@Inject
    	public PriceServiceImpl(
    			Set<String> supportedCurrencies) {
    		super();
    		this.supportedCurrencies = supportedCurrencies;
    
    	}
    
    	@Override
    	public Set<String> getSupportedCurrencies() {
    		return supportedCurrencies;
    	}
    }
    

    Module的组织

    Module主要有以下三种关系

    1、并列

    Guice,createInjector(module1,module2,...)
    

    2、嵌套

    通过如下语句将一个module嵌套另一个module中

    install(module)
    

    3、覆盖

    一个module中的绑定可能覆盖另一个module的绑定。下面的意思是如果遇到冲突绑定,则用后面module的配置

    Modules.override(module1).with(module2)
    

    Guice的启动

    Guice通过如下方式启动。

    Injector injector = Guice.createInjector(new ServerModule());
    
    OrderService orderService = injector.getInstance(OrderService.class);
    
    

    Module中配置的各种bind是何时被运行的?

    • Module里存放了很多表达式,启动时并不会将bind的实例初始化
    • Module不被”运行“
    • Guice.createInjector()时记录所有表达式

    系统何时初始化?

    • Guice中没有”初始化“概念,没有Spring 的Configuration Time
    • injector.getInstance()时根据表达式调用构造函数

    因此在执行 Guice.createInjector时会比较快,真正花时间的是调用injector.getInstance()方法调用构造函数,要善于使用Provider,将比较耗时的操作使用Provider进行懒加载,即使用时再调用。

    生命周期/作用域

    如果不显式指定Scope,Guice默认为每次注入生成新的实例。

    但是像数据库连接实例,通常只需要一个,需要显式配置其为Singleton。

    作用域分类

    默认作用域

    一般实例,无状态,构造速度快,通常使用默认即可

    Singleton作用域

    • 有状态的实例
    • 构造速度慢的实例
    • 需要线程安全的实例
    • 如:数据库,网络连接

    Session/Request作用域

    • 含有session/request信息的实例
    • 有状态的实例
    • 如SessionState等

    绑定方式

    有三种方式配置作用域,以下三种选其一即可。

    方式一、可以通过在类上添加@Singleton注解声明作用域为单例,其他两种类似@SessionScoped/@RequestScoped,后两种需要servlet环境

    @Singleton
    public class SingletonDemo{}
    

    方式二、通过在module的configure方法的bind中,使用in语句

    public class ServerModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
    		
    		// 绑定map
    			bind(new TypeLiteral<Cache<String, String>>(){})
    			.to(GuiceDemoCache.class).in(Singleton.class);
    	}
    }
    

    方式三、通过@Provides方法属性指定

    @Provides
    @Singleton
    Cache<String,String> getCache(){
        return new GuiceDemoCache();
    }
    

    实例

    // 1、类定义
    @Singleton
    public class GuiceDemoCache
    	extends AbstractCache<String, String> {
    
    	private final Map<String, String> keyValues =
    			new ConcurrentHashMap<>();
    
    	@Override
    	public String getIfPresent(Object key) {
    		return keyValues.get(key);
    	}
    
    	@Override
    	public void put(String key, String value) {
    		keyValues.put(key, value);
    	}
    }
    // 2、绑定
    public class ServerModule extends AbstractModule {
    
    	@Override
    	protected void configure() {
    		install(new ChinaModule());
    		install(new GlobalModule());
    
    		bind(OrderService.class).to(OrderServiceImpl.class);
    		bind(PaymentService.class).to(PaymentServiceImpl.class);
    		bind(PriceService.class).to(PriceServiceImpl.class);
    		// 绑定map
    		bind(new TypeLiteral<Cache<String, String>>(){})
    			.to(GuiceDemoCache.class);
    
    	}
    
    }
    // 3、注入
    public class PaymentServiceImpl implements PaymentService {
    	private final Cache<String, String> cache;
    
    	@Inject
    	public PaymentServiceImpl(Cache<String, String> cache) {
    		super();
    		this.cache = cache;
    	}
    
    	@Override @Logged
    	public void pay(long orderId, long price, Long sessionId) {
    		// TODO Auto-generated method stub
    	}
    
    	void putCache(String key, String value) {
    		cache.put(key, value);
    	}
    }
    
    public class PriceServiceImpl implements PriceService {
    	private final Set<String> supportedCurrencies;
    	private final Cache<String, String> cache;
    
    	@Inject
    	public PriceServiceImpl(
    			Set<String> supportedCurrencies,
    			Cache<String, String> cache) {
    		super();
    		this.supportedCurrencies = supportedCurrencies;
    		this.cache = cache;
    	}
    
    	@Override
    	public Set<String> getSupportedCurrencies() {
    		return supportedCurrencies;
    	}
    
    	@Override
    	public long getPrice(long orderId) {
    		throw new UnsupportedOperationException();
    	}
    
    	String getCachedValue(String key) {
    		return cache.getIfPresent(key);
    	}
    }
    

    AOP

    直接学习视频 Guice AOP

    Guice vs Spring

    1、Guice不是Spring的重制版本

    2、Spring绑定

    • 配置文件体现完整装配结构
    • 大量使用字符串:实例名:属性名
    • 在Config Time完成组装

    3、Guice绑定

    • Java代码描述绑定规则
    • 每个注入/绑定处仅描述局部依赖
    • 没有Config Time

    4、Guice的优点

    • 代码量少
    • 性能优异
    • 支持泛型
    • Constructor绑定:Immutable objects,不再需要getter/setter
    • 强类型
    • 易于重构

    5、Guice的缺点

    • Module和绑定规则不易于理解
    • 文档教程少,社区资源少
    • 无法方便搭出特殊结构:如循环依赖
    • Guice仅仅是依赖注入框架,而Spring更重量级,涵盖更多

    6、从Spring 迁移到Guice?

    • 不建议这么做
    • Spring 与Guice整合,两者可以兼容

    7、新项目需要选择依赖注入方案?

    • 可以尝试Guice
    • 与其他组件/解决方案整合:注意对象生命周期
  • 相关阅读:
    spring揭秘 读书笔记 六 bean的一生
    分枝限界算法
    libLAS1.8.0 编译和配置(VS2013+Win7 64)(一)
    Unity学习笔记 之 发射小球碰撞物体的代码记录
    hdu1281 棋盘游戏 --- 最大匹配
    javascript设计模式
    3、Android中Activity的跳转
    2.11 确定运行计划
    php扩展之 pdo_mysql.so
    POJ 1061 青蛙的约会(扩展欧几里得)
  • 原文地址:https://www.cnblogs.com/z00377750/p/14046190.html
Copyright © 2020-2023  润新知