import org.jetbrains.annotations.Nullable; public class Jedi { private long id; private String name; @Nullable private Long masterId; // fields, constructors, getters/setters, equals, hashCode, toString, etc... public long getId() { return id; } public String getName() { return name; } @Nullable public Long getMasterId() { return masterId; } @Nullable public Jedi getMaster() { if (masterId == null) { return null; } return DBJedi.getJedi(masterId); } }
public class DBJedi { public static Jedi getJedi(long id) { DataSource dataSource = ConnectionPools.getDataSource("jedi"); Jedi jedi; // magic return jedi; } }
It is logical to convert such a class into a classic singleton, for example, like this: import javax.sql.DataSource; public class DBJedi { private static final DBJedi instance = new DBJedi(); private final ConnectionPools connectionPools; private DBJedi() { this.connectionPools = ConnectionPools.getInstance(); } public static DBJedi getInstance() { return instance; } public Jedi getJedi(long id) { DataSource dataSource = connectionPools.getDataSource("jedi"); Jedi jedi; // magic return jedi; } }
As a result, we get a more coherent and readable code structure (a very controversial fact, of course). If you finish what has been started, in general, the transition to DI can be done using standard guidelines. import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; import com.google.inject.Injector; // import com.google.common.base.Preconditions; // guava public class InjectorUtil { private static volatile Injector injector; public static void setInjector(@NotNull Injector injector) { // Preconditions.checkNotNull(injector); // Preconditions.checkState(InjectorUtil.injector == null, "Injector already initialized"); InjectorUtil.injector = injector; } @TestOnly public static void rewriteInjector(@NotNull Injector injector) { // Preconditions.checkNotNull(injector); InjectorUtil.injector = injector; } @Deprecated // use fair injection, Sith! @NotNull public static Injector getInjector() { // Preconditions.checkState(InjectorUtil.injector != null, "Injector not initialized"); return InjectorUtil.injector; } }
For Spring, the code will be similar, but instead of Injector, the ApplicationContext. Or another option: import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import javax.inject.Named; @Named public class ApplicationContextUtil implements ApplicationContextAware { private static volatile ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) { ApplicationContextUtil.applicationContext = applicationContext; } @Deprecated public static ApplicationContext getApplicationContext() { // Preconditions.checkState(applicationContext != null); return applicationContext; } }
@javax.inject.Singleton @javax.inject.Named // Spring component-scan, Guice public class DBJedi { private final ConnectionPools connectionPools; @javax.inject.Inject public DBJedi(ConnectionPools connectionPools) { this.connectionPools = connectionPools; } @Deprecated public static DBJedi getInstance() { return InjectorUtil.getInjector().getInstance(DBJedi.class); } public Jedi getJedi(long id) { DataSource dataSource = connectionPools.getDataSource("jedi"); Jedi jedi; // ... return jedi; } }
Please note that the JSR-330 annotations are used, the javax.inject package. Using them, you can later more easily switch from one DI to another, ideally abstracting from a specific framework altogether (assuming JSR-330 compatibility). The Named annotation will allow not to record the bean in the spring-context.xml, if the xml configuration still implies such an entry, the annotation should be removed. public class Jedi { private long id; private String name; @Nullable private Long masterId; private final DBJedi dbJedi; private Jedi(long id, String name, @Nullable masterId, DBJedi dbJedi) { this.id = id; this.name = name; this.masterId = masterId; this.dbJedi = dbJedi; } //... public long getId() { return id; } public String getName() { return name; } @Nullable public Long getMasterId() { return masterId; } @Nullable public Jedi getMaster() { if (masterId == null) { return null; } return dbJedi.getJedi(masterId); } @Singleton @Named public static class Factory { private final DBJedi dbJedi; @Inject public Factory(DBJedi dbJedi) { this.dbJedi = dbJedi; } @Deprecated // refactor Jedi class to simple bean, Sith! public Jedi create(long id, String name, @Nullable masterId) { return new Jedi(id, name, masterId, dbJedi); } } }
import javax.inject.Singleton; import javax.inject.Named; import javax.inject.Inject; import javax.inject.Provider; import javax.sql.DataSource; @Singleton @Named public class DBJedi { private final ConnectionPools connectionPools; private final Provider<Jedi.Factory> jediFactoryProvider; @Inject public DBJedi(ConnectionPools connectionPools, Provider<Jedi.Factory> jediFactoryProvider) { this.connectionPools = connectionPools; this.jediFactoryProvider = jediFactoryProvider; } @Deprecated public static DBJedi getInstance() { return InjectorUtil.getInjector().getInstance(DBJedi.class); } public Jedi getJedi(long id) { DataSource dataSource = connectionPools.getDataSource("jedi"); // ... final Jedi.Factory jediFactory = jediFactoryProvider.get(); return jediFactory.create(id, name, masterId); } }
It is true to note that generic declarations are not available through Reflection. As for Guice and Spring, they both read the bytecode of the class and thus get a generic type. import org.testng.annotations.*; import com.google.inject.Injector; import com.google.inject.AbstractModule; @Guice(modules = JediTest.JediTestModule.class) public class JediTest { private static final long JEDI_QUI_GON_ID = 12; private static final long JEDI_OBI_WAN_KENOBI_ID = 22; @Inject private Injector injector; @Inject private DBJedi dbJedi; @BeforeClass public void setInjector() { InjectorUtil.rewriteInjector(injector); } @Test public void testJedi() { final Jedi obiWan = dbJedi.getJedi(JEDI_OBI_WAN_KENOBI_ID); final Jedi master = obiWan.getMaster(); Assert.assertEquals(master.getId(), JEDI_QUI_GON_ID); } public static class JediTestModule extends AbstractModule { @Override public void configure() { // ConnectionPools , .. bind(ConnectionPools.class).toInstance(new ConnectionPools("pools.properties")); } } }
public class InjectorUtil { private static final ThreadLocal<Injector> threadLocalInjector = new InheritableThreadLocal<Injector>(); private InjectorUtil() { } /** * Get thread local injector for current thread * * @return * @throws IllegalStateException if not set */ @NotNull public static Injector getInjector() throws IllegalStateException { final Injector Injector = threadLocalInjector.get(); if (Injector == null) { throw new IllegalStateException("Injector not set for current thread"); } return Injector; } /** * Set Injector for current thread * * @param Injector * @throws java.lang.IllegalStateException if already set */ public static void setInjector(@NotNull Injector injector) throws IllegalStateException { if (injector == null) { throw new NullPointerException(); } if (threadLocalInjector.get() != null) { throw new IllegalStateException("Injector already set for current thread"); } threadLocalInjector.set(injector); } /** * Rewrite Injector for current thread, even if already set * * @param injector * @return previous value if was set */ public static Injector rewriteInjector(@NotNull Injector injector) { if (injector == null) { throw new NullPointerException(); } final Injector prevInjector = threadLocalInjector.get(); threadLocalInjector.set(injector); return prevInjector; } /** * Remove Injector from thread local * * @return Injector if was set, else null */ public static Injector removeInjector() { final Injector prevInjector = threadLocalInjector.get(); threadLocalInjector.remove(); return prevInjector; } }
Source: https://habr.com/ru/post/217523/
All Articles