System, but they may not be reproduced for publication



Yüklə 83 Mb.
Pdf görüntüsü
səhifə70/82
tarix19.04.2023
ölçüsü83 Mb.
#106251
1   ...   66   67   68   69   70   71   72   73   ...   82
Java A Beginner’s Guide, Eighth Edition ( PDFDrive )

how. This concept can be expanded so that the implementing class is provided by code
that is outside your program, through the use of a plug­in. Using such an approach, the
capabilities of an application can be enhanced, upgraded, or altered by simply changing
the plug­in. The core of the application itself remains unchanged. One way that Java
supports a pluggable application architecture is through the use of services and service
providers. Because of their importance, especially in large, commercial applications,
Java’s module system provides support for them.
Before we begin, it is necessary to state that applications that use services and service
providers are typically fairly sophisticated. Therefore, you may find that you do not
often need the service­based module features. However, because support for services
constitutes a rather significant part of the module system, it is important that you have
a general understanding of how these features work. Also, a simple example is
presented that illustrates the core techniques needed to use them.
Service and Service Provider Basics
In Java, a service is a program unit whose functionality is defined by an interface or an
abstract class. Thus, a service specifies in a general way some form of program activity.
A concrete implementation of a service is supplied by a service provider. In other
words, a service defines the form of some action, and the service provider supplies that
action.
As mentioned, services are often used to support a pluggable architecture. For example,
a service might be used to support the translation of one language into another. In this
case, the service supports translation in general. The service provider supplies a specific
translation, such as German to English or French to Chinese. Because all service
providers implement the same interface, different translators can be used to translate


different languages without having to change the core of the application. You can
simply change the service provider.
Service providers are supported by the ServiceLoader class. ServiceLoader is a
generic class packaged in java.util. It is declared like this:
class ServiceLoader
Here, S specifies the service type. Service providers are loaded by the load( ) method.
It has several forms; the one we will use is shown here:
public static  ServiceLoader load(Class  serviceType)
Here, serviceType specifies the Class object for the desired service type. Recall from
Chapter 13
that Class is a class that encapsulates information about a class. There are a
variety of ways to obtain a Class instance. The way we will use here is called a class
literal. A class literal has this general form:
className.class
Here, className specifies the name of the class.
When load( ) is called, it returns a ServiceLoader instance for the application. This
object supports iteration and can be cycled through by use of a for­each for loop.
Therefore, to find a specific provider, simply search for it using a loop.
The Service-Based Keywords
Modules support services through the use of the keywords provides, uses, and with.
Essentially, a module specifies that it provides a service with a provides statement. A
module indicates that it requires a service with a uses statement. The specific type of
service provider is declared by with. When used together, they enable you to specify a
module that provides a service, a module that needs that service, and the specific
implementation of that service. Furthermore, the module system ensures that the
service and service providers are available and will be found.
Here is the general form of provides:
provides serviceType with implementationTypes;
Here, serviceType specifies the type of the service, which is often an interface, although
abstract classes are also used. A comma­separated list of the implementation types is


specified by implementationTypes. Therefore, to provide a service, the module
indicates both the name of the service and its implementation.
Here is the general form of the uses statement:
uses serviceType;
Here, serviceType specifies the type of the service required.
A Module-Based Service Example
To demonstrate the use of services, we will add a service to the modular application
example that we have been using. For simplicity, we will begin with the first version of
the application shown at the start of this chapter. To it we will add two new modules.
The first is called userfuncs. It will define interfaces that support functions that
perform binary operations in which each argument is an int and the result is an int.
The second module is called userfuncsimp, and it contains concrete implementations
of the interfaces.
Begin by creating the necessary source directories.
1. Under the appsrc directory, add directories called userfuncs and userfuncsimp.
2. Under userfuncs, add the subdirectory also called userfuncs. Under that
directory, add the subdirectory binaryfuncs. Thus, beginning with appsrc, you will
have created this tree:
3. Under userfuncsimp, add the subdirectory also called userfuncsimp. Under that
directory, add the subdirectory binaryfuncsimp. Thus, beginning with appsrc, you
will have created this tree:
This example expands the original version of the application by providing support for
functions beyond those built into the application. Recall that the SimpleMathFuncs
class supplies three built­in functions: isFactor( ), lcf( ), and gcf( ). Although it
would be possible to add more functions to this class, doing so requires modifying and
recompiling the application. By implementing services, it becomes possible to “plug in”
new functions at run time without modifying the application, and that is what this
example will do. In this case, the service supplies functions that take two int arguments


and return an int result. Of course, other types of functions can be supported if
additional interfaces are provided, but support for binary integer functions is sufficient
for our purposes and keeps the source code size of the example manageable.
The Service Interfaces
Two service­related interfaces are needed. One specifies the form of an action, and the
other specifies the form of the provider of that action. Both go in the binaryfuncs
directory, and both are in the userfuncs.binaryfuncs package. The first, called
BinaryFunc, declares the form of a binary function. It is shown here:
BinaryFunc declares the form of an object that can implement a binary integer
function. This is specified by the func( ) method. The name of the function is
obtainable from getName( ). The name will be used to determine what type of
function is implemented. This interface is implemented by a class that supplies a binary
function.
The second interface declares the form of the service provider. It is called
BinFuncProvider and is shown here:


BinFuncProvider declares only one method, get( ), which is used to obtain an
instance of BinaryFunc. This interface must be implemented by a class that wants to
provide instances of BinaryFunc.
The Implementation Classes
In this example, two concrete implementations of BinaryFunc are supported. The
first is AbsPlus, which returns the sum of the absolute values of its arguments. The
second is AbsMinus, which returns the result of subtracting the absolute value of the
second argument from the absolute value of the first argument. These are provided by
the classes AbsPlusProvider and AbsMinusProvider. The source code for these
classes must be stored in the binaryfuncsimp directory, and they are all part of the
userfuncsimp.binaryfuncsimp package.
The code for AbsPlus is shown here:
AbsPlus implements func( ) such that it returns the result of adding the absolute
values of a and b. Notice that getName( ) returns the "absPlus" string. It identifies


this function.
The AbsMinus class is shown next:
Here, func( ) is implemented to return the difference between the absolute values of a
and b, and the string "absMinus" is returned by getName( ).
To obtain an instance of AbsPlus, the AbsPlusProvider is used. It implements
BinFuncProvider and is shown here:
The get( ) method simply returns a new AbsPlus( ) object. Although this provider is
very simple, it is important to point out that some service providers will be much more
complex.
The provider for AbsMinus is called AbsMinusProvider and is shown next:


Its get( ) method returns an object of AbsMinus.
The Module Definition Files
Next, two module definition files are needed. The first is for the userfuncs module. It
is shown here:
This code must be contained in a module­info.java file that is in the userfuncs
module directory. Notice that it exports the userfuncs.binaryfuncs package. This is
the package that defines the BinaryFunc and BinFuncProvider interfaces.
The second module­info.java file is shown next. It defines the module that contains
the implementations. It goes in the userfuncsimp module directory.
This module requires userfuncs because that is where BinaryFunc and
BinFuncProvider are contained, and those interfaces are needed by the
implementations. The module provides BinFuncProvider implementations with the
classes AbsPlusProvider and AbsMinusProvider.
Demonstrate the Service Providers in MyModAppDemo
To demonstrate the use of the services, the main( ) method of MyModAppDemo is


expanded to use AbsPlus and AbsMinus. It does so by loading them at run time by
use of ServiceLoader .load( ). Here is the updated code:


Let’s take a close look at how a service is loaded and executed by the preceding code.
First, a service loader for services of type BinFuncProvider is created with this
statement:
Notice that the type parameter to ServiceLoader is BinFuncProvider. This is also
the type used in the call to load( ). This means that providers that implement this
interface will be found. Thus, after this statement executes, BinFuncProvider classes
in the module will be available through ldr. In this case, both AbsPlusProvider and
AbsMinusProvider will be available.
Next, a reference of type BinaryFunc called binOp is declared and initialized to null.


It will be used to refer to an implementation that supplies a specific type of binary
function. Next, the following loop searches ldr for one that has the "absPlus" name.
Here, a for­each loop iterates through ldr. Inside the loop, the name of the function
supplied by the provider is checked. If it matches "absPlus", that function is assigned to
binOp by calling the provider’s get( ) method.
Finally, if the function is found, as it will be in this example, it is executed by this
statement:
In this case, because binOp refers to an instance of AbsPlus, the call to func( )
performs an absolute value addition. A similar sequence is used to find and execute
AbsMinus.
Because MyModAppDemo now uses BinFuncProvider, its module definition file
must include a uses statement that specifies this fact. Recall that MyModAppDemo
is in the appstart module. Therefore, you must change the module­info.java file for
appstart as shown here:
Compile and Run the Module-Based Service Example


Once you have performed all of the preceding steps, you can compile and run the
example by executing the following commands:
Here is the output:
As the output shows, the binary functions were located and executed. It is important to
emphasize that if either the provides statement in the userfuncsimp module or the
uses statement in the appstart module were missing, the application would fail.
ADDITIONAL MODULE FEATURES
Before concluding our discussion of modules, there are three more features that require
a brief introduction. These are the open module, the opens statement, and the use of
requires static. Each of these features is designed to handle a specialized situation,
and each constitutes a fairly advanced aspect of the module system. That said, it is
important for you to have a general understanding of their purpose. As you gain more
experience with Java, you may encounter situations for which they provide elegant
solutions.
Open Modules
As you learned earlier in this chapter, by default, the types in a module’s packages are
accessible only if they are explicitly exported via an exports statement. While this is
usually what you will want, there can be circumstances in which it is useful to enable
run­time access to all packages in the module, whether a package is exported or not. To
allow this, you can create an open module. An open module is declared by preceding
the module keyword with the open modifier, as shown here:


In an open module, types in all packages are accessible at run time. Understand,
however, that only those packages that are explicitly exported are available at compile
time. Thus, the open modifier affects only run­time accessibility.
The primary reason for an open module is to enable the packages in the module to be
accessed through reflection. Reflection is the feature that lets a program analyze code at
run time. Although the topic of and techniques required to use reflection are beyond the
scope of this book, it can be quite important to certain types of programs that require
run­time access to a third­party library.

Yüklə 83 Mb.

Dostları ilə paylaş:
1   ...   66   67   68   69   70   71   72   73   ...   82




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə