1. Въведение
Тази статия ще разгледа основите на Google Guice . Ще разгледаме подходите за изпълнение на основните задачи за инжектиране на зависимост (DI) в Guice.
Също така ще сравним и сравним подхода на Guice с тези на по-утвърдени DI рамки като Spring и Contexts и Dependency Injection (CDI).
Тази статия предполага, че читателят разбира основите на модела на инжектиране на зависимост.
2. Настройка
За да използвате Google Guice във вашия проект Maven, ще трябва да добавите следната зависимост към вашия pom.xml :
com.google.inject guice 4.1.0
Тук има и колекция от разширения на Guice (ще ги разгледаме малко по-късно), както и модули на трети страни за разширяване на възможностите на Guice (главно чрез осигуряване на интеграция в по-утвърдени Java рамки).
3. Инжектиране на основна зависимост с Guice
3.1. Нашата примерна заявка
Ще работим със сценарий, при който проектираме класове, които поддържат три средства за комуникация в бизнес бюро за помощ: Имейл, SMS и IM.
Помислете за класа:
public class Communication { @Inject private Logger logger; @Inject private Communicator communicator; public Communication(Boolean keepRecords) { if (keepRecords) { System.out.println("Message logging enabled"); } } public boolean sendMessage(String message) { return communicator.sendMessage(message); } }
Този клас на комуникация е основната комуникационна единица. Екземпляр от този клас се използва за изпращане на съобщения чрез наличните комуникационни канали. Както е показано по-горе, комуникацията разполага с комуникатор, който използваме, за да извършим действителното предаване на съобщението.
Основната входна точка в Guice е инжекторът:
public static void main(String[] args){ Injector injector = Guice.createInjector(new BasicModule()); Communication comms = injector.getInstance(Communication.class); }
Този основен метод извлича екземпляр от нашия клас за комуникация . Той също така въвежда фундаментална концепция на Guice: Модулът (използвайки BasicModule в този пример). В модула е основната единица на определение на автомати (или кабели, както е известен през пролетта).
Guice е възприел първоначален код за инжектиране и управление на зависимости, така че няма да се занимавате с много XML, които не са готови.
В горния пример дървото на зависимостите на комуникацията ще бъде имплицитно инжектирано с помощта на функция, наречена свързване точно навреме , при условие че класовете имат конструктора по подразбиране no-arg. Това е функция в Guice от самото начало и е достъпно само през пролетта от v4.3.
3.2. Обвързвания на Guice
Обвързването е към Guice, както окабеляването е към Spring. С обвързването дефинирате как Guice ще инжектира зависимости в клас.
Обвързването е дефинирано в изпълнение на com.google.inject.AbstractModule :
public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communicator.class).to(DefaultCommunicatorImpl.class); } }
Тази реализация на модула указва, че екземпляр на Default CommunicatorImpl трябва да се инжектира навсякъде, където се намери променлива на Communicator .
Друго въплъщение на този механизъм е нареченото обвързване . Обмислете следната декларация на променлива:
@Inject @Named("DefaultCommunicator") Communicator communicator;
За това ще имаме следната обвързваща дефиниция:
@Override protected void configure() { bind(Communicator.class) .annotatedWith(Names.named("DefaultCommunicator")) .to(Communicator.class); }
Това свързване ще осигури екземпляр на Communicator към променлива, коментирана с анотацията @Named („DefaultCommunicator“) .
Ще забележите, че поясненията @Inject и @Named изглеждат като заемни пояснения от CDI на Джакарта EE и те са. Те са в пакета com.google.inject. * - трябва да внимавате да импортирате от правилния пакет, когато използвате IDE.
Съвет: Въпреки че току-що казахме да използваме предоставените от Guice @Inject и @Named , заслужава да се отбележи, че Guice предоставя поддръжка за javax.inject.Inject и javax.inject.Named, наред с други анотации на Джакарта EE.
Можете също така да инжектирате зависимост, която няма конструктор no-arg по подразбиране, използвайки свързване на конструктор :
public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Boolean.class).toInstance(true); bind(Communication.class).toConstructor( Communication.class.getConstructor(Boolean.TYPE)); }
Фрагментът по-горе ще инжектира екземпляр на Communication, използвайки конструктора, който приема булев аргумент. Ние доставяме истинския аргумент на конструктора, като дефинираме ненасочено свързване на булевия клас.
Това нецелево свързване ще бъде доставено с желание на всеки конструктор в обвързването, който приема булев параметър. С този подход се инжектират всички зависимости на комуникацията .
Друг подход към специфичното за конструктора обвързване е обвързването на екземпляра , където ние предоставяме екземпляр директно в обвързването:
public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communication.class) .toInstance(new Communication(true)); } }
Това обвързване ще предостави екземпляр на класа Communication, където и да е декларирана комуникационна променлива.
В този случай обаче дървото на зависимостите на класа няма да бъде автоматично свързано. Трябва да ограничите използването на този режим, когато не е необходима тежка инициализация или инжектиране на зависимост.
4. Видове инжектиране на зависимост
Guice поддържа стандартните видове инжекции, които бихте очаквали с DI модела. В класа Communicator трябва да инжектираме различни видове CommunicationMode .
4.1. Полево инжектиране
@Inject @Named("SMSComms") CommunicationMode smsComms;
Използвайте незадължителната анотация @Named като квалификатор, за да приложите насочена инжекция въз основа на името
4.2. Метод Инжектиране
Тук използваме метод за настройка, за да постигнем инжектирането:
@Inject public void setEmailCommunicator(@Named("EmailComms") CommunicationMode emailComms) { this.emailComms = emailComms; }
4.3. Инжектиране на конструктор
Можете също така да инжектирате зависимости с помощта на конструктор:
@Inject public Communication(@Named("IMComms") CommunicationMode imComms) { this.imComms= imComms; }
4.4. Неявни инжекции
Guice will implicitly inject some general purpose components like the Injector and an instance of java.util.Logger, among others. You'll notice we are using loggers all through the samples but you won't find an actual binding for them.
5. Scoping in Guice
Guice supports the scopes and scoping mechanisms we have grown used to in other DI frameworks. Guice defaults to providing a new instance of a defined dependency.
5.1. Singleton
Let's inject a singleton into our application:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class).in(Scopes.SINGLETON);
The in(Scopes.SINGLETON) specifies that any Communicator field with the @Named(“AnotherCommunicator”) will get a singleton injected. This singleton is lazily initiated by default.
5.2. Eager Singleton
Now, let's inject an eager singleton:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class) .asEagerSingleton();
The asEagerSingleton() call defines the singleton as eagerly instantiated.
In addition to these two scopes, Guice supports custom scopes as well as the web-only @RequestScoped and @SessionScoped annotations, supplied by Jakarta EE (there are no Guice-supplied versions of those annotations).
6. Aspect Oriented Programming in Guice
Guice is compliant with the AOPAlliance's specifications for aspect-oriented programming. We can implement the quintessential logging interceptor, which we will use to track message sending in our example, in only four steps.
Step 1 – Implement the AOPAlliance's MethodInterceptor:
public class MessageLogger implements MethodInterceptor { @Inject Logger logger; @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object[] objectArray = invocation.getArguments(); for (Object object : objectArray) { logger.info("Sending message: " + object.toString()); } return invocation.proceed(); } }
Step 2 – Define a Plain Java Annotation:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MessageSentLoggable { }
Step 3 – Define a Binding for a Matcher:
Matcher is a Guice class that we use do specify the components that our AOP annotation will apply to. In this case, we want the annotation to apply to implementations of CommunicationMode:
public class AOPModule extends AbstractModule { @Override protected void configure() { bindInterceptor( Matchers.any(), Matchers.annotatedWith(MessageSentLoggable.class), new MessageLogger() ); } }
We have specified a Matcher here that will apply our MessageLogger interceptor to any class, that has the MessageSentLoggable annotation applied to its methods.
Step 4 – Apply Our Annotation to Our Communicationmode and Load Our Module
@Override @MessageSentLoggable public boolean sendMessage(String message) { logger.info("SMS message sent"); return true; } public static void main(String[] args) { Injector injector = Guice.createInjector(new BasicModule(), new AOPModule()); Communication comms = injector.getInstance(Communication.class); }
7. Conclusion
Having looked at basic Guice functionality, we can see where the inspiration for Guice came from Spring.
Заедно с подкрепата си за JSR-330, Guice се стреми да бъде насочена към инжектиране DI рамка (докато Spring предоставя цяла екосистема за удобство при програмиране, не непременно само DI), насочена към разработчици, които искат гъвкавост на DI.
Guice също е силно разширяем, което позволява на програмистите да пишат преносими плъгини, което води до гъвкаво и креативно използване на рамката. Това е в допълнение към обширната интеграция, която Guice вече предлага за най-популярните рамки и платформи като Servlets, JSF, JPA и OSGi, за да назовем само няколко.
Можете да намерите целия изходен код, използван в този урок в нашия проект GitHub.