Level: Introductory David Carew (carew@us.ibm.com), Senior e-Business Architect, IBM Jeff Wilson (wilsonje@us.ibm.com), e-Business Architect, IBM
20 Feb 2004 This article is the first installment in Getting on the open road, a three-part series of roadmaps designed to assist developers of .NET, Windows client/server, and ASP applications make the jump to the Java platform. In Getting on the open road, the authors help you leverage your existing development knowledge to ease the way onto the path of open-standards-based programming. In this article, veteran enterprise architects David Carew and Jeff Wilson provide a high-level guide for .NET developers who want to migrate their e-business applications to J2EE or build J2EE applications from the ground up. Share your thoughts on these articles with the authors and other readers in the accompanying discussion forum. (You can also click Discuss at the top or bottom of the article to access the forum.)
Are you a .NET developer looking to port a .NET e-business application to the Java 2 Platform Enterprise Edition (J2EE)? Or, do you need to write your first J2EE application from scratch? Either way, this roadmap is for you. It's designed specifically for .NET developers who want to make the jump to J2EE, a platform-independent set of open standards for developing Web and other types of distributed applications in the Java language. We'll introduce you to programming in the Java language and developing J2EE Web applications. Better yet, we'll relate this information to concepts and techniques you already know from your experience writing .NET applications.
Why J2EE?
If you're not exactly eager to take the plunge into a new development environment, consider these J2EE benefits:
- Choices, choices: Because J2EE is a well-defined set of standards, you have a choice of J2EE implementations when you deploy your code. As long as you stick with the standard APIs and avoid any vendor-specific extensions, your application will run on a variety of implementations without coding changes.
- Did we say choices?: J2EE implementations are available on a variety of platforms, from mainframes to Wintel, UNIX, and Linux. You can write your application once and deploy it on a variety of platforms.
- Can't we all just get along?: J2EE includes a standard API for accessing many legacy EIS systems like CICS, IMS, ERP, and CRM systems. It also includes support for Web services, so you can integrate with .NET systems and other systems supporting industry Web services standards. J2EE also includes support for a standard messaging API (Java Message Service; JMS) and an API for access to relational databases (Java Database Connectivity; JDBC). This wide array of choices lets you integrate with a variety of existing systems without losing your investment in them.
- No smoke-filled back rooms: Experts from around the world develop J2EE specifications through the Java Community Process (JCP). The JCP releases initial drafts of specifications for public review. Even if you don't actively participate, you always know what's in store for future specs. Specifications also include a reference implementation you can use to check out new technology before you decide to implement it.
 |
Migrate to the Java platform
If you develop .NET, Windows client/server, and ASP applications, and are contemplating making the leap to the J2EE platform, our series Getting on the open road will guide you through the process, leveraging your existing development knowledge to ease the way onto the path of open-standards-based programming.
|
|
Introducing J2EE
J2EE, Java 2 Enterprise Edition, is a specification made up of many component specifications related to developing distributed applications in the Java language. You can use J2EE components to write Web-based applications and traditional client-server applications, and to connect to legacy resources like relational databases using a standard API. If you come from an ASP.NET development background, the Java Servlets and JavaServer Pages (JSP) technologies are the components of the most interest to you.
Java Servlets
Java Servlets are Java classes that run as an extension of a Web server like IIS or the Apache Web server. A Java Servlet is analogous to an ISAPI filter, the ASP.NET HttpHandler class, or a cgi-bin program/script. A Java Servlet runs when a client browser invokes a specifically configured URL directly or indirectly. A servlet has access to all the information in an HTTP request and can handle the request directly by providing content to be returned to the client. Alternatively, a servlet can redirect the client browser to another resource. Most J2EE Web applications use servlets primarily as targets of HTML forms, to handle user input and then process it accordingly. Generation of the response page is typically delegated to a JSP page.
JavaServer Pages technology
JSP pages are analogous to ASP.NET pages. That is, they are HTML pages that also contain scripting elements that execute on the server when a user requests the page. A key difference between ASP.NET pages and JSP pages is that ASP.NET pages use a .NET language (such as C# and VB.NET) as their scripting language, whereas JSP pages use the Java language. A typical JSP page contains snippets of Java code and some special HTML-like tags defined in the JSP specification, interleaved with standard HTML to provide a combination of static and dynamic content. The difference between Java Servlets and JSP pages is conceptually similar to the difference between the ASP.NET HttpHandler class and an ASP.NET page. In both cases the former is a piece of code you can use to emit HTML directly or redirect to other resources, and the latter is an HTML document that can contain embedded code.
Web servers and application servers
If you're familiar with ASP.NET pages, you know that the ASP.NET runtime -- which works in concert with IIS -- runs them. You can also add your own HttpHandler classes and managed and unmanaged components to your Web applications, which the ASP.NET runtime also invokes. This makes it easy to deploy Web applications on IIS. But it limits you to the Windows platform, the only one where IIS can run. J2EE uses a different approach because it's designed to run on a variety of operating systems (including Windows). Rather than attempt to embed the code to run Java Servlets and JSP pages directly into a Web server, it uses a separate server component called an application server to run them. Most application servers (such as IBM WebSphere Application Server) also have separate plug-in components to bridge between the application server and specific Web servers. For example, WebSphere Application Server ships with separate plug-ins for IIS and the Apache Web server. This lets you use the Web server of your choice when you run J2EE components.
The application server's role as a separate, pluggable server component gives you several advantages:
- Choice of Web server: You're not tied to any one Web server to serve up your static HTML pages. You can continue to use your favorite Web server for this purpose and use any application server to handle your Java Servlets and JSP pages. This capability is especially useful when you're porting an ASP.NET application to J2EE. You can continue to run IIS and the ASP.NET runtime and port your application in stages. You needn't rewrite the whole application in one fell swoop.
- Choice of platform: You can write your J2EE application once and deploy it on a variety of operating systems -- including Windows, AIX, and Linux -- where the application server can run. You're not limited by where a particular Web server can run.
- Choice of application server vendors: Because industry-standard specifications define the Java Servlets and JavaServer Pages technologies, you can write your J2EE application once and then deploy it to multiple application-server environments, such as WebSphere Express or Apache Tomcat, a popular open source application server. J2EE also defines how you must package a Web application, so you can take an application you develop in one J2EE environment and redeploy it to another application server without needing to change your code or recompile the application. The same holds true for deploying an application to multiple platforms.
How an application server runs servlets and JSP code
As we mentioned earlier, the J2EE specification mandates a standard format for deploying Java Servlets and other J2EE components. An XML document called a deployment descriptor is part of this standard format. The deployment descriptor contains a mapping from each of your servlets to the URLs you use to invoke a particular servlet. An application server uses the information in the deployment descriptor to decide which servlet to invoke for a given request.
The way an application server invokes a JSP page is similar to how the ASP.NET runtime invokes ASP.NET pages. A J2EE application server converts each JSP page into a separate special servlet that compiles and runs when the page is requested. The special servlet remains loaded in memory until the JSP file changes. This minimizes the performance hit that might result if you had to create and compile a class for each JSP page.
Model-View-Controller architecture
J2EE was developed with a particular application structure in mind, called Model-View-Controller (MVC). MVC defines a clear separation among three tiers of an application:
- The model: The set of data and business rules for the application -- commonly called the application's business logic
- The view: The application's user interface
- The controller: The definition how the application reacts to user input or to changes in the model tier -- commonly called application logic
Advantages of a MVC architecture
Nothing in J2EE forces you to structure your applications with a MVC architecture, but there are good reasons to do so. By defining a clear separation among the three tiers, MVC allows for loose coupling among the components that comprise each tier. This makes the components more reusable and flexible. For example, suppose one of your requirements is to support different types of views of the same data in a Web application, because different departments need different subsets of the same data in a database. You need to develop new view components specific to each required subset. If the view logic and database-access code were tightly coupled -- as they are an ASP.NET page that interleaves database-access code with HTML -- then each view would contain the database-access code. Maintaining duplicate code not only takes a lot of work, but it's likely to lead to errors. A MVC architecture in this scenario uses the database-access code as part of the model, and the various view components can reuse it.
J2EE components and MVC
Figure 1 shows how the J2EE components we've discussed so far could map to a MVC architecture. Note that no connection exists between the model and the view. The controller's function is to serve as a conduit between the two.
Figure 1. MVC with J2EE Web applications
In a typical scenario, the user submits an HTML form whose target is a servlet. The servlet parses the input and uses classes in the model to invoke the business logic to satisfy the request. The servlet then passes the results to a JSP page to display them to the user.
JavaServer Faces
You've probably used Web Forms to develop the user interface and UI control logic for ASP.NET applications. The JavaServer Faces (JSF) specification provides the equivalent functionality in the world of J2EE programming. Like Web Forms, JSF provides the runtime components that let tool vendors provide drag-and-drop capability for the development of Web-based UIs. It also lets vendors develop custom components their development tools can use.
To see JSF in action, take a look at the WebSphere Studio version 5.1.1 family of tools (see Resources). WebSphere Studio has a fully integrated tool called Page Designer that you can use to develop HTML pages and JSP pages visually with drag and drop. Page Designer has implemented the JavaServer Faces specification, and when you use it you should see some familiar components, such as HTML forms, DataGrids, and DataLists. You'll also see the familiar "code behind" files (in the Java language) that contain the UI event-handling code for a particular page.
One of the nice features of the JSF environment is that it lets you group pages into logical tree-like structures. For example, if you have a set of pages that comprise a multipage form, the JSF tree structure is ideal -- especially if a user might take a different path through the pages based on previous input or on some user characteristic. The structure assigns a logical name to each page in the tree. You use these names in your event-handling code to navigate through different branches, depending on particular runtime conditions.
Other J2EE technologies
The Java Servlets and JSP technologies give you the tools you need to build platform-independent Web applications in the Java language. Some other J2EE specifications and components give you more-advanced capabilities:
- Enterprise JavaBeans (EJB) technology: Enterprise components (or beans) that come in three varieties:
- Session beans: Special Java classes that are analogous to COM+ services in .NET. Like COM+ services, session beans run in a container that provides services such as declarative transaction management, role-based security, seamless access in a distributed environment, and activation on an as-needed basis. Session beans come in two flavors:
- Stateless: No state is maintained between method calls, so you must provide all the information needed to invoke a method via parameters. The advantage is that the container can use any instance to service any client invocation.
- Stateful: State is maintained between method calls so clients are always associated with a particular instance. The advantage is that clients can use a conversational mode to interact with stateful session beans. This is especially useful when the intermediate state information is costly to re-create.
- Entity beans: Special Java classes you can think of as a more sophisticated implementation of ADO.NET DataSets. Although conceptually similar to DataSets, Entity beans are implemented more like COM+ services. Like DataSets, they are object representations of persistent data stored in a relational database or other persistent stores and can encapsulate relationships between tables in the data model. Like COM+ services, they run in a container that provides services such as declarative transaction management, role-based security, and seamless access in a distributed environment. Entity beans are shared objects, so the container also handles concurrency control and ensures that the underlying persistent data maintains its ACID (Atomicity, Consistency, Isolation, and Durability) properties. (Unlike session beans, entity beans are shared objects, so multiple clients can access individual instances concurrently.) In a nutshell, entity beans shield you from direct access to the underlying persistent store. You can deploy them to a variety of persistent stores without any application changes. (That is, you can map an entity bean to its persistent store at deployment time without any code changes.)
- Message-driven beans: Special Java classes that serve as listeners for JMS-compliant messaging middleware. JMS is a standard Java API for accessing message queues. You can configure message-driven beans to point at a particular message queue; the container activates them when a message arrives in that queue. With message-driven beans, you provide the application logic that's invoked when the message arrives. Each J2EE 1.3 compliant application server must provide a JMS implementation, but you can also use popular messaging middleware like WebSphere MQ (formerly MQSeries).
- Java Connector Architecture (JCA): A standard API for accessing legacy EIS systems like CICS, IMS, ERP, and CRM systems. JCA frees you from having to learn a separate API for each EIS system.
 |
Java SDK basics
- Write your Java source code in plain ASCII format with a text editor that supports long filenames.
- Save the file with a .java extension.
All Java keywords are lowercase. You can use any case for variable and method names, but you must use it consistently throughout a class.
- Compile your program from a command-line prompt, using the javac compiler that comes with the SDK. Always specify the .java extension.
- Run your program from a command-line prompt, using the Java interpreter that comes with the SDK. Do not specify the .class extension.
- Debug your code if necessary. Errors can occur when a Java program compiles or runs.
|
|
Java programming fundamentals
Before diving in to some J2EE programming concepts, we'll introduce you to the Java programming language. You can use the Java language to write server-side applications as well as desktop applications with GUIs. This article assumes you want to use the Java language on the server side to complement a Web-based interface, so we'll skip the GUI programming environment and focus instead on the platform's nonvisual aspects. We'll start with an introduction to the Java Software Development Kit (SDK) and then show you how to write the time-honored Hello World application in Java code. Then, we'll delve more deeply into the differences between the two most popular .NET languages -- Visual Basic .NET and C# -- and the Java language. Thanks to Scott Stricker for his contributions to this and the next two main sections, adapted from his tutorial, "Java programming for C/C++ developers" (see Resources).
Introducing the Java SDK
The Java SDK is a group of command-line tools and packages you need in order to write and run Java programs. Java programs are compiled into platform-independent bytecode by a Just In Time (JIT) compiler, and then the bytecode is compiled into native code at runtime. The most important tools are the Java compiler (javac.exe) and the Java interpreter (java.exe), which you use to run Java programs. The SDK also includes the base classes (called the Java platform), which provide you with the basic functionality and APIs you need to start writing applications.
Sun Microsystems has released an SDK for each of its five major releases of the Java platform. We recommended you get the latest SDK version (Java 1.4.2) for this article. The Java SDK is freely available. If you don't have it already, download it now (see Resources).
You can reference the Java 2 Standard Edition (J2SE) API documentation online (see Resources). It's a collection of HTML documents that you can navigate in a standard Web browser. The API documentation is an essential reference, and you'll probably use it frequently.
Installing the SDK
After you download the SDK, you need to install it on your machine. The installation is straightforward. If the setup gives you a choice between a typical or a custom install, choose the typical install. (You should choose the custom install only if you know exactly what you do and don't want to load on your machine.) The installation usually gives you the option of installing the source code for the standard Java platform classes. If you have sufficient disk space on your machine, we recommend that you take this option. These files will give you a chance to look at the implementation of the classes that make up the Java language and standard APIs. They're particularly well designed and implemented, and you can learn a lot from them.
After you install the SDK, you might need to configure it to work on your system. How you configure the SDK depends on your operating system and the SDK version you're using. The SDK includes complete installation and configuration instructions.
Your first Java program
Now you can write your first Java program -- the ubiquitous Hello World. Open your text editor and enter the source code exactly as you see it in Listing 1.
Listing 1. Hello World program
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
|
The Java language is case-sensitive, and language keywords such as class and public are always lowercase. You can use any case for your variable and method names, as long as you are consistent in the usage throughout a given class. When you're done typing, save the code in a file called HelloWorld.java. You have no choice when it comes to this filename. Java source files use a .java extension, and every Java source code file must have the same name as the class you define inside it. We can't state it enough: Case is significant, so file names like HELLOWORLD.JAVA and Helloworld.java will result in a compile error. You can save HelloWorld.java in any appropriate directory on your machine. You need to go to this directory to use the command-line tools, so make sure it's convenient.
Compiling the program
Now you're ready to compile the HelloWorld program. The Java language compiler that comes with the SDK is a command-line application called javac.exe. To compile a Java source code file, you simply pass the name of the .java file to the javac.exe program. To compile your HelloWorld program, open a command-line prompt and change your directory to the location where you saved the HelloWorld.java file. Then execute this command:
Like a Visual Basic .NET or C# compiler, the Java compiler can generate any number of errors. Naturally, you need to correct any errors before the Java compiler will compile the HelloWorld program successfully. A successful compile generates a class file called HelloWorld.class. This file represents the executable you run in the Java interpreter.
Running the program
The Java interpreter that comes with the SDK is a command-line application called java.exe. To run a Java bytecode executable, you simply pass the name of the Java program to the Java interpreter. Don't specify the .class extension when you use the Java interpreter. The interpreter expects only class files, so adding the .class extension will produce an error. To run your HelloWorld program, open a command-line prompt and change your directory to the location where you compiled the HelloWorld.java file. Your bytecode executable file, HelloWorld.class, should be there. Then execute this command:
The Java interpreter tries to execute the HelloWorld program's main() method. The Java program's main() method is equivalent to the Visual Basic .NET Sub Main() that console applications use or the C# Main()method. Java methods that list void as the return type are equivalent to a Sub in Visual Basic .NET. Methods with other return types are equivalent to a Function in Visual Basic .NET.
The Java interpreter might report a runtime error, which usually terminates program execution. As in Visual Basic .NET and C#, Java runtime errors are more difficult to debug than compile-time errors but occur less frequently. Like .NET programs, Java programs execute in a managed environment, so you can handle runtime errors gracefully.
The Java language vs. Visual Basic .NET
Now let's take a look at the differences between the Java language and Visual Basic .NET. (If you're strictly a C# developer, you can skip to The Java language vs. C#, later in this article.)
Types
Both the Java language and Visual Basic .NET are single-inheritance, object-oriented languages that have a class from which all other classes derive: System.Object in Visual Basic .NET and java.lang.Object in the Java language. This means that for the class hierarchies you develop, the two languages are similar. If you follow the hierarchy up the inheritance tree, you eventually end up at the corresponding root class.
The Java language uses the concept of a primitive types that work much like their counterparts in C and C++. They're not part of any class hierarchy and don't have any methods. Furthermore, you always pass them by value when you use them as parameters. Table 1 lists the primitive types in the Java language and their Visual Basic .NET equivalent types:
Table 1. Java language primitive types and their Visual Basic .NET equivalents
| Java primitive | Description | Equivalent Visual Basic .NET type | Description | int | 32-bit signed integer | Integer | 32-bit signed integer | long | 64-bit signed integer | Long | 64-bit signed integer | short | 16-bit signed integer | Short | 16-bit signed integer | char | 16-bit unsigned | Char | 16-bit unsigned | byte | 8-bit unsigned | Byte | 8-bit unsigned | boolean | Valid values are true or false | Boolean | Valid values are True or False | float | 32-bit floating point number | Single | 32-bit floating point number | double | 64-bit floating point number | Double | 64-bit floating point number |
In the Java language, each primitive type has a corresponding wrapper class that you can use to treat the type as an object rather than as a primitive type. Each wrapper type has a constructor that lets you create an instance of the wrapper type from the data in a primitive type. In Visual Basic .NET, you can implicitly convert the corresponding types to an instance of Object, so you don't need to use a wrapper class in this situation. The example in Listing 2 highlights the differences.
Listing 2. Primitive types and their wrapper classes
Visual Basic .NET Java
Module Foo public class Foo
{
Sub someMethod(ByRef arg As Object) private void someMethod(Object arg) {
' do something with arg // do something with arg
end Sub }
Sub Main() public static void main(String[] args) {
Dim i = 0 As Integer int i=0;
Foo x = new Foo();
someMethod(i); x.someMethod(new Integer(i));
End Sub }
End Module }
|
The Java primitive type in Listing 2 is explicitly wrapped in a descendent of Object, whereas in Visual Basic .NET the conversion is implicit.
Note that in the Java language, primitive types are passed by value. Object instances are represented internally by pointers that are also passed by value (that is, the value of the pointer, not what it points to). In Visual Basic .NET, the default behavior is that value types are passed by value, and reference types are passed by reference. The Java language doesn't have any equivalent to the Visual Basic .NET ByRef and ByVal keywords. Table 2 shows the Java language equivalents to Visual Basic .NET types that don't map to Java primitives.
Table 2. Common Java system classes and their Visual Basic .NET equivalents
| Java class | Description | Equivalent Visual Basic .NET class | Description | java.lang.Object | Any nonprimitive is a descendant of Object | Object | Every type is a descendant of Object | java.lang.String | Unicode characters | String | Unicode characters | java.math.BigDecimal | 32-bit decimal with adjustable scale | Decimal | 32-bit decimal with adjustable scale | java.util.Date | Date (excluding time component) | Date | Date (excluding time component) |
The Java language, unlike Visual Basic .NET, doesn't have either structures or modules. All Java code must be part of a class or an interface. If you port Visual Basic .NET code that contains modules and structures, you must rewrite them as Java classes.
Inheritance and interfaces
Both languages allow only single inheritance but also let you implement multiple interfaces. You do this differently in the two languages. For example, Listing 3 shows how you subclass a class called Parent and implement two interfaces, called IOne and ITwo.
Listing 3. Subclassing and implementing interfaces
Visual Basic .NET Java
Public Class Child public class Child extends Parent implements IOne, ITwo
Inherits Parent {
Implements IOne ...
Implements ITwo }
...
End Class
|
In the Java language, the extends keyword denotes inheritance, and the implements keyword denote the implementation of one or more interfaces, with multiple interfaces separated by a comma.
Packages
If you're familiar with namespaces in Visual Basic .NET, then you should have no conceptual problems with packages in the Java language. Like namespaces, packages let you organize your classes so that naming collisions won't occur if you use two same-named classes in different contexts. Namespaces' logical grouping of classes promotes class reuse and makes it easier to navigate through large numbers of classes. In the Java language, you need to address this kind of grouping in two ways: how you declare a class to be a member of a specific package, and how you refer to a class in a specific package. The example in Listing 4 illustrates the handling of namespaces and packages.
Listing 4. Namespaces and packages
Visual Basic .NET Java
'Foo will be in this namespace // Foo will be in this package
Namespace MyApp.Utilities package com.mycompany.myapp.utilities;
Public class Foo public class Foo
{
... ...
End Class }
End Namespace
'using Foo in another class // using Foo in another class
Imports MyApp.Utilities.Foo import com.mycompany.myapp.utilities.Foo;
Public Class Foo2 public class Foo2
... {
...
End Class }
|
In the Java language, the convention is to use all lowercase for package names and a reverse Internet domain name as the prefix for each package. You use the Java import statement -- like the Visual Basic .NET Imports statement -- so that you can refer to the Foo class in Foo2 without using the fully qualified name (MyApp.Utilities.Foo in Visual Basic .NET or com.mycompany.myapp.utilities.Foo in Java code).
Packaging for reuse
In Visual Basic .NET, you can write sets of classes and build them as assemblies, which are represented in the file system as dynamic link libraries. Other classes can refer to these assemblies in order to use the classes they contain. The Java language also allows you to package sets of classes, in file called a Java Archive (JAR), for reuse. You can combine sets of classes into a file with a .jar extension and reference the JAR file from other classes. Files with a .jar extension are standard zip files that can be manipulated with WinZip or other zip utilities. However, for convenience, the Java SDK contains a utility called jar.exe (on the Windows platform) that you can use to combine sets of classes into a single file with a .jar extension.
Before we walk you through an example of using the jar.exe utility, it's important that you understand the relationship between package names and the directory structure the Java platform uses to build classes and load them at runtime. Consider a class called Test whose source is in a file called Test.java. If you define Test.java to be part of the package com.mycompany.test, then the compiler creates a directory tree for the resulting .class module. The directory tree is based on the package name. In this case the tree is com\mycompany\test, with the periods in the package name converted to directory boundaries.
Now, open a command prompt and create a directory (for example, c:\javapack). Change to that directory (cd javapack). Use your favorite text editor to add the code in Listing 5 to a new file called Test.java.
Listing 5. An example of using packages
package com.mycompany.test;
public class Test
{
public static void main(String[] args) {
System.out.println("In test");
}
}
|
Now, compile Test.java with the following command. (The -d option should point to the directory that you created for this example):
java -d c:\javapack Test.java
|
You should now have a subdirectory called com in your c:\javapack directory. In fact, you can see that the fully qualified name of the comTest.class file that resulted from the compilation is Test.class. Note how the package name (com.mycompany.test) was translated to a corresponding directory structure (com\mycompany\test) rooted at the directory you specified with the -d option.
Now we'll show you how you can package Test.class for easy reuse by other classes. Run this command from the c:\javapack directory:
The command creates a file, called Test.jar, that contains all the classes under the com subdirectory.
Run this command to use the class in the Test.jar file:
java -classpath Test.jar com.mycompany.test.Test
|
Note that you have to use the fully qualified class name to run the command from the command line, and note the way you use the -classpath option to point to the Test.jar file. Alternatively, you could add the Test.jar file to the CLASSPATH environment variable, which is a semicolon-separated list of JAR files and directories the Java compiler and the Java virtual machine (JVM) use to look for classes that need to be loaded.
Access modifiers
The access modifiers public, private, and protected work the same in both languages, for the most part. In Visual Basic .NET the default is Friend. In the Java language the default is to allow any subclass or any class in the same package to access the class, field, or method in question. This is roughly equivalent to the Protected Friend modifier in Visual Basic .NET, which allows access only from classes in the same assembly or from subclasses.
Method overriding
For a subclass in Visual Basic .NET to override a method in a parent class:
- The method must not be declared with the
private access modifier in the parent class.
- The method must be declared as
Overridable in the parent class.
- The method in the subclass must have the same name, return type, and parameter signature as the method in the parent class.
- The method in the subclass must be declared with the
Overrides keyword. (You can use the new keyword instead, but this is not generally recommended).
- The method in the parent class must not be declared as
NotOverridable.
The prerequisites for method overriding in the Java language are a tad less stringent:
- The method must not be declared with the
private access modifier in the parent class.
- The method in the subclass must have the same name, return type, and parameter signature as the method in parent class.
- The method in the parent class must not be declared as
final.
The implications of the differences are that in Java code, you can't have a method in a subclass with the same name and signature as a nonprivate method in the parent class, without implicitly overriding it. In Visual Basic .NET, you're required to indicate explicitly when you want to override a method in the parent class. Note also how the final keyword in the Java language is more or less equivalent to the NotOverridable keyword in Visual Basic .NET.
Exception handling
Structured exception handling in both languages is almost identical. (Both can trace their roots back to a seminal paper, written by Andrew Koenig and Bjarne Stroustrup in 1990, called Exception Handling for C++.) Both languages use the notion of two kinds of exceptions: exceptions that applications generate, and exceptions that the system runtime (the Common Language Runtime for Visual Basic .NET, the JVM for the Java language) generates. Both have a base Exception class that application and system exceptions both derive from. Figure 2 shows the Exception class hierarchy in each language.
Figure 2. Exception class hierarchies in Java language and Visual Basic .NET
However, in one instance the two languages' compilers have different expectations for how your code handles exceptions. In Visual Basic .NET, you have the option of catching exceptions or having them propagate up the call stack to the caller of your class's methods (and/or constructors). Java language allows the same luxury, but in the case of uncaught application exceptions (that is, subclasses of java.lang.Exception) you must explicitly list them as part of the method declaration. So the Java compiler expects you either to catch all application exceptions yourself or to tell the compiler about the uncaught exceptions. For example, assuming the constructor of the Foo class can throw an application exception, then neither the Visual Basic .NET code nor the Java code in Listing 6 is a problem for its respective compiler.
Listing 6. Handling application exceptions
Visual Basic .NET Java
... ...
Sub SomeMethod() public void someMethod() {
Try try {
Dim Foo X As New Foo() Foo x = new Foo();
}
Catch e As ApplicationException catch (Exception e)
{
... ...
End Try }
End Sub }
|
However, if you change the code not to catch the exception, then you'd have to change the Java method declaration, as in Listing 7, to avoid a compile error.
Listing 7. Uncaught application exceptions
Visual Basic .NET Java
... ...
Sub SomeMethod() public void someMethod() throws Exception {
Dim Foo X As New Foo(); Foo x = new Foo();
End Sub }
|
Another difference between the two languages in this area is that in Visual Basic .NET the parameter for the catch block is optional. If you omit it, then any exception will be caught. The Java language doesn't allow this, but for the equivalent functionality you can catch the parent of all exception classes (java.lang.Throwable), as in Listing 8.
Listing 8. Catching all exceptions
Visual Basic .NET Java
... ...
Sub SomeMethod() public void someMethod() {
Try try {
Dim Foo X As New Foo() Foo x = new Foo();
}
Catch catch (Throwable e)
{
... ...
End Try }
|
Array declarations
The Java language gives you two ways to declare arrays; Visual Basic .NET has only one. Java arrays work like dynamic arrays in Visual Basic .NET: An explicit step is required to allocate storage for it. The code in Listing 9 illustrates the difference. As in Visual Basic .NET, Java arrays are indexed from 0.
Listing 9. Declaring arrays
Visual Basic .NET Java
... ...
' This is how an array is // In Java the following
'declared in Visual Basic .NET // are both allowed
Dim MyArray() As Integer private int[] myArray;
// or
private int myArray[];
'Allocate some storage for the array // Allocate some storage
// for the array
ReDim MyArray(30) myArray = new int[30];
|
Delegates
The Java language doesn't have a construct directly equivalent to a Visual Basic .NET delegate. You can mimic delegate functionality easily by declaring and implementing an interface with a single method definition.
Variable declaration
The Java language is strongly typed, and the rules that govern variable declaration are equivalent to Visual Basic .NET with the Option Explicit option turned on. That is, you must declare variables before you use them. The Java language doesn't let you change this behavior.
OnError GoTo
The Java language has no direct equivalent to Visual Basic .NET's GoTo statement. However, you can use the exception-handling mechanism to perform error handling quite easily.
Constructors
Like Visual Basic .NET, Java classes can have different constructors with different parameter lists. In Visual Basic .NET you declare the constructors with a Sub called New(). Java language constructors are named after the class. The code in Listing 10 illustrates the difference.
Listing 10. Constructors
Visual Basic .NET Java
Public Class Foo public class Foo {
Private MyVar As Integer private int myVar;
Public Sub New(ByVal NewVal As Integer) public Foo(int newVal) {
MyVar = NewVal myVar = newVal;
End Sub }
... ...
End Class }
|
Properties
The Java equivalents of Visual Basic .NET Properties are called fields. In the Java language, you can't define getters and setters as part of the field definition, but you can add getters and setters to the class where you declare the fields. The code in Listing 11 illustrates the difference.
Listing 11. Properties
Visual Basic .NET Java
Public Class Foo public class Foo {
Private MyPropVal As String private String myProp;
Public Property MyProp() As String public String getMyProp() {
Get return myProp;
Return MyPropVal }
End Get
Set(ByVal NewValue As String) public void setMyProp(String newValue) {
MyPropVal = NewValue myProp = newValue;
End Set }
End Property
... ...
End Class }
|
Summary
Visual Basic .NET's syntax is similar to previous Visual Basic versions, but the latest version includes many of the object-oriented features you'll find in the Java language. Inheritance, interface, and exception handling are some of the areas where the two languages' implementations are similar. The similarity should make your transition to the Java platform much easier than it might have been from previous Visual Basic versions. We encourage you to start by converting some small Visual Basic .NET programs to the Java language. Remember to use the Java platform documentation, which you'll find useful for finding Java classes that provide functionality equivalent to Visual Basic .NET classes in the System... namespaces.
The Java language vs. C#
Now let's take a look at the differences between Java language and C#. The two languages share many similarities, so we'll focus on where the differences lie.
Types
Both the Java language and C# are single-inheritance, object-oriented languages that have a class from which all other classes derive: System.object in C# and java.lang.Object in the Java language. This means that for the class hierarchies you develop, the two languages are similar. If you follow the hierarchy up the inheritance tree, you eventually end up at the corresponding root class.
The Java language uses the concept of a primitive types that work much like their counterparts in C and C++. They're not part of any class hierarchy and don't have any methods. Furthermore, you always pass them by value when you use them as parameters. Table 3 lists the primitive types in the Java language and their C# equivalent types.
Table 3. Java language primitive types and their C# equivalents
| Java primitive | Description | Equivalent C# type | Description | int | 32-bit signed integer | int | 32-bit signed integer | long | 64-bit signed integer | long | 64-bit signed integer | short | 16-bit signed integer | short | 16-bit signed integer | char | 16-bit unsigned | char | 16-bit unsigned | byte | 8-bit unsigned | byte | 8-bit unsigned | boolean | Valid values are true or false | bool | Valid values are true or false | float | 32-bit floating point number | float | 32-bit floating point number | double | 64-bit floating point number | double | 64-bit floating point number |
In the Java language, each primitive type has a corresponding wrapper class that you can use to treat the type as an object rather than as primitive type. Each wrapper type has a constructor that lets you create an instance of the wrapper type from the data in a primitive type. In C#, you can implicitly convert the corresponding types to an instance of object, so you don't need to use a wrapper class in this situation. The code in Listing 12 highlights the differences.
Listing 12. An example of primitive types and their wrapper classes
C# Java
public class Foo public class Foo
{ {
private void someMethod(object arg) { private void someMethod(Object arg) {
// do something with arg // do something with arg
} }
public static void Main(string[] args) { public static void main(String[] args) {
int i = 0; int i=0;
Foo x = new Foo(); Foo x = new Foo();
x.someMethod(i); x.someMethod(new Integer(i));
} }
} }
|
The Java primitive type in Listing 12 is explicitly wrapped in a descendent of Object, whereas in C# the conversion is implicit. (The implicit conversion in C# is called boxing.)
Note that in the Java language, primitive types are passed by value, and object instances are represented internally by pointers that are also passed by value. In C# the default behavior is the same, but the language includes the ref keyword to force value types to be passed by reference.
Table 4 shows the Java language equivalents to some of the C# types that don't map to Java primitives:
Table 4. C# types and their Java equivalents
| Equivalent Java class | Description | C# type | Description | java.lang.Object | Any nonprimitive is a descendant of Object | object | Every type is a descendant of object | java.lang.String | Unicode characters | string | Unicode characters | java.math.BigDecimal | 32-bit decimal with adjustable scale | decimal | 32-bit decimal with adjustable scale |
Inheritance and interfaces
Both languages allow only single inheritance but also let you implement multiple interfaces. You do this differently in the two languages. For example, Listing 13 shows how to subclass a class called Parent and implement two interfaces, called IOne and ITwo.
Listing 13. Subclassing and implementing interfaces
C# Java
public class Child : Parent, IOne, ITwo public class Child extends Parent implements IOne, ITwo
{ {
... ...
} }
|
Note that in the Java language, the extends keyword denotes inheritance, and the implements keyword denotes the implementation of one or more interfaces.
Packages
If you're familiar with namespaces in C#, then you should have no conceptual problems with packages in Java language. Like namespaces, packages let you organize your classes so that naming collisions won't occur if you use two same-named classes in different contexts. Namespaces' logical grouping of classes promotes class reuse and makes it easier to navigate through large numbers of classes. In the Java language, you need to address this kind of grouping in two ways: how you declare a class to be a member, and how you refer to a class in a specific package. The example in Listing 14 illustrates the handling of namespaces and packages.
Listing 14. Namespaces and packages
C# Java
// Foo will be in this namespace // Foo will be in this package
namespace MyApp.Utilities package com.mycompany.myapp.utilities;
public class Foo public class Foo
{ {
... ...
} }
// using Foo in another class // using Foo in another class
using MyApp.Utilities.Foo; import com.mycompany.myapp.utilities.Foo;
public class Foo2 public class Foo2
{ {
... ...
} }
|
Note that in the Java language, the convention is to use all lowercase for package names and a reverse Internet domain name as the prefix for each package. You use the Java import statement, like the C# using statement, so that you can refer to the Foo class in Foo2 without using the fully qualified name (MyApp.Utilities.Foo in C# or com.mycompany.myapp.utilities.Foo in Java language).
Packaging for reuse
In C#, you can write sets of classes and build them as assemblies, which are represented in the file system as dynamic link libraries. Other classes can refer to these assemblies in order to use the classes they contain. The Java language also allows you to package sets of classes, in a file called a Java Archive (JAR) file, for reuse. You can combine sets of classes into a file with a .jar extension and reference the JAR file with other classes. Files with a .jar extension are standard zip files that can be manipulated with WinZip or other zip utilities. However, for convenience, the Java SDK contains a utility called jar.exe (on the Windows platform) that you can use to combine sets of classes into a single file with a .jar extension.
Before we walk you through an example of using the jar.exe utility, it's important to understand the relationship between package names and the directory structure the Java platform uses to build classes and load them at runtime. Consider a class called Test whose source is in a file called Test.java. If you define Test.java to be part of the package com.mycompany.test, then the compiler creates a directory tree for the resulting .class module. The directory tree is based on the package name. In this case the tree is com\mycompany\test, with the periods in the package name converted to directory boundaries.
Now, open a command prompt and create a directory (for example, c:\javapack). Change to that directory (cd javapack). Use your favorite text editor to add the code in Listing 15 to a new file called Test.java.
Listing 15. An example of using packages
package com.mycompany.test;
public class Test
{
public static void main(String[] args) {
System.out.println("In test");
}
}
|
Compile Test.java with the following command. (Note that the -d option should point to the directory that you created for this example):
java -d c:\javapack Test.java
|
You should now have a subdirectory called com in your c:\javapack directory. In fact, you can see that the fully qualified name of the comTest.class file that resulted from the compilation is Test.class. Note how the package name (com.mycompany.test) was translated to a corresponding directory structure (com\mycompany\test) rooted at the directory you specified with the -d option.
Now we'll show you how you can package Test.class for easy reuse by other classes. Run this command from the c:\javapack directory:
The jar command creates a file, called Test.jar, that contains all the classes under the com subdirectory.
Execute the following command to use the class in the Test.jar file:
java -classpath Test.jar com.mycompany.test.Test
|
Note that you must use the fully qualified class name to run it from the command line, and note the way you use the -classpath option to point to the Test.jar file. Alternatively, you could add the Test.jar file to the CLASSPATH environment variable, which is a semicolon-separated list of JAR files and directories the Java compiler and the Java virtual machine (JVM) use to look for classes that need to be loaded.
Access modifiers
The access modifiers public, private, and protected work the same in both languages, for the most part. In C# the default is private; in the Java language the default is to allow any subclass or any class in the same package to access the class, field, or method in question. This is roughly equivalent to the internal keyword in C#, which allows access only from classes in the same assembly.
Method overriding
For a subclass in C# to override a method in a parent class:
- The method must not be declared with the
private access modifier (or no access modifier) in the parent class.
- The method must be declared as
virtual in the parent class.
- The method in the subclass must have the same name, return type, and parameter signature as the parent class.
- The method in the subclass must be declared with the
override keyword.(You can use the new keyword instead, but this isn't generally recommended.)
- The method in the parent class must not be declared as
sealed.
The prerequisites for method overriding in the Java language are a tad less stringent:
- The method must not be declared with the
private access modifier in the parent class.
- The method in the subclass must have the same name, return type, and parameter signature as the method in the parent class.
- The method in the parent class must not be declared as
final.
The implications of the differences are that in Java code, you can't have a method in a subclass with the same name and signature as a nonprivate method in the parent class, without implicitly overriding it. In C#, you're required to indicate explicitly when you want to override a method in the parent class. Note also how the final keyword in the Java language is more or less equivalent to the sealed keyword in C#.
Exception handling
Structured exception handling in both languages is almost identical. (Both can trace their roots back to a seminal paper, written by Andrew Koenig and Bjarne Stroustrup in 1990, called Exception Handling for C++). Both languages use the notion of two kinds of exceptions: exceptions that applications generate, and exceptions that the system runtime (the Common Language Runtime for C#, the JVM for the Java language) generates. Both have a base Exception class that application and system exceptions both derive from. Figure 3 shows the Exception class hierarchy in both languages.
Figure 3. Exception class hierarchies in Java language and C#
However, in one instance the two languages' compilers have different expectations for how your code handles exceptions. In C#, you have the option of catching exceptions or having them propagate up the call stack to the caller of your class's methods (and/or constructors). Java language allows the same luxury, but in the case of uncaught application exceptions (that is, subclasses of java.lang.Exception) you must explicitly list them as part of the method declaration. So the Java compiler expects you either to catch all application exceptions yourself or to tell the compiler about the uncaught exceptions. For example, assuming the Foo class's constructor can throw an application exception, then neither the C# code nor the Java code in Listing 16 is a problem for its respective compiler.
Listing 16. Handling application exceptions
C# Java
... ...
public void someMethod() { public void someMethod() {
try { try {
Foo x = new Foo(); Foo x = new Foo();
} }
catch (ApplicationException e) catch (Exception e)
{ {
... ...
} }
} }
|
However, if you change the code not to catch the exception, then you'd have to change the Java method declaration, as in Listing 17, to avoid a compile error.
Listing 17. Uncaught application exceptions
C# Java
... ...
public void someMethod() { public void someMethod() throws Exception {
Foo x = new Foo(); Foo x = new Foo();
} }
|
Another difference between the two languages in this area is that in C# the parameter for the catch block is optional. If you omit it, then any exception will be caught. The Java language doesn't allow this, but for the equivalent functionality you can catch the parent of all exception classes (java.lang.Throwable), as in Listing 18.
Listing 18. Catching all exceptions
C# Java
... ...
public void someMethod() { public void someMethod() {
try { try {
Foo x = new Foo(); Foo x = new Foo();
} }
catch catch (Throwable e)
{ {
... ...
} }
} }
|
Array declarations
The Java language gives you two ways to declare arrays; C# has only one. The code in Listing 19 illustrates the difference.
Listing 19. Declaring arrays
C# Java
... ...
// This is how an array is declared in C# // In Java the following are both allowed
private int[] myArray; private int[] myArray;
// or
private int myArray[];
|
Delegates and indexers
The Java language doesn't have a construct directly equivalent to a C# delegate. You can mimic delegate functionality by declaring and implementing an interface with a single method definition.
The Java language doesn't have indexers either; you'd need to write them as regular class methods.
Operator overloading
The Java language doesn't allow operator overloading (a feature of C++ that made its way into C#). You easily can write methods to mimic the behavior of the overloaded operators.
Unsafe mode
C#'s unsafe mode lets you use pointers and pin blocks of memory in order to circumvent garbage collection. The Java runtime itself makes extensive use of pointers, but the Java language has no pointers and no equivalent of unsafe mode. This is in keeping with the Java platform's "Write Once, Run Anywhere" philosophy, which lets you safely avoid platform dependencies, memory leaks, and "runaway" code.
Summary
Your background in C# should make the transition to the Java language fairly easy. The side-by-side code samples in this article have probably made you realize that the languages use quite similar syntax. They're conceptually quite similar too. Inheritance, interface, and exception handling are some of the areas where their implementations are almost identical. We encourage you to convert some small C# programs to the Java language. Remember to use the Java platform documentation, which you'll find useful for finding Java classes that provide functionality equivalent to C# classes in the System... namespaces.
.NET and J2EE application models: Understanding the differences
Gaining an understanding of the basic concepts of J2EE application architectures is the first step in determining how to migrate an existing ASP.NET application to a J2EE-based model, or how to write a J2EE application from scratch. We'll take a look at a few ASP.NET models and how they might translate into ones you build from J2EE components. You'll see an evolution from simple "spaghetti code" to a more elegant, reusable, and scalable environment. Before we get into some code examples, let's take a closer look at the two J2EE components used most often in Web applications: Java Servlets and JavaServer Pages technologies.
Programming with Java Servlets: The basics
You write Java Servlets to allow programmatic control of a URL request from the browser. A typical servlet invocation goes something like this:
- The client makes a request to the Web server, naming a servlet as part of the URL -- for example:
<FORM action="/myWebApp/LoginServlet" method="POST">
.
- The Web server forwards the request to the application server, which locates an instance of the
servlet class.
- The Web container invokes the servlet. (A single instance of a servlet is loaded into memory, and each request invokes the single instance on a different thread.)
Note that the URL of the servlet in the HTML form includes the servlet's name and a prefix that's called the context root (in the above example, /myWebApp). The context root is roughly equivalent to an IIS virtual directory.
Figure 4 illustrates the steps.
Figure 4. Servlet invocation
Servlets extend the HttpServlet class. You can override the following methods of the HttpServlet class as needed:
init(): called when the application server loads a servlet
destroy(): called when the application server unloads a servlet
doGet(): called when a servlet is invoked via an HTTP GET
doPost(): called when a servlet is invoked via an HTTP POST
Writing a servlet involves writing the code to handle an HTTP request, processing any parameters, and returning HTML content directly or delegating another resource (like a JSP page) to handle the response. Returning HTML directly from a servlet isn't recommended, because managing HTML code inside a Java class is awkward.
When the application server calls the doGet() or doPost() methods, two objects are passed in as parameters:
HttpServletRequest allows access to any request parameters and other information from the HTTP request that caused the invocation of the servlet.
HttpServletResponse serves as the communication channel back to the client and allows the servlet to return content directly or return other HTTP return codes (errors, redirects, and so on).
Listings 20 and 21 show a couple of Hello World servlets. Listing 20 returns content directly, and Listing 21 uses a JSP page to return content.
Listing 20. HelloWorld servlet: Returning content directly
public class HelloWorldServlet extends HttpServlet {
/**
* Handles all HTTP POST requests
*
* @param request Object that encapsulates the request to the servlet
* @param response Object that encapsulates the response from the servlet
*/
public void doPost(
javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws ServletException, IOException {
try {
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("Hello World");
out.println("</html>");
} catch (Throwable t) {
...
}
}
}
|
Note that you get a PrintWriter from the response object and output the HTML to the client one line at a time.
Listing 21. HelloWorld servlet: Using a JSP page to return content
public class HelloWorldServlet extends HttpServlet {
/**
* Handles all HTTP POST requests
*
* @param request Object that encapsulates the request to the servlet
* @param response Object that encapsulates the response from the servlet
*/
public void doPost(
javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws ServletException, IOException {
try {
RequestDispatcher rd = getServletContext().getRequestDispatcher("helloworld.jsp");
rd.forward(request, response);
} catch (Throwable t) {
...
}
}
}
|
The RequestDispatcher is a wrapper around the resource that you want to forward the request to. Note that you include both the original request and response objects so that the target resource can have access to them. The ServletContext returned from getServletContext() allows the servlet to communicate with the underlying application server to get a RequestDispatcher. Note that all servlets have access to their ServletContext via the getServletContext() method.
Programming with JSP technology: The basics
JSP technology gives you the ability to do server-side scripting in the Java language. A JSP page is a composite page that consists of HTML and Java code that's processed by an application server before it's returned to the client. The application server processes the embedded code to produce static content prior to returning the page to the client. Like .aspx files, JSP page files typically look like HTML files with some additional tags and snippets of Java code.
The application server converts each JSP page into a special servlet when it's requested the first time. The servlet compiles and loads into memory. It services requests for the page for as long as the JSP source isn't modified. Upon source modification, the process starts over again and a new version of the servlet is produced.
You can use several special JSP tags in your JSP pages, and users can also define the behavior of tags they develop themselves. These custom tags are roughly equivalent to custom components in ASP.NET. You can also add chunks of Java code to different parts of the page. The J2EE runtime environment makes a number of variables -- known as implicit variables -- available to you for these snippets of Java code. Examples of implicit variables are:
- request: the
HttpServletRequest associated with a particular invocation of the page
- response: the
HttpServletResponse associated with a particular invocation of the page
- out: a
PrintWriter associated with the HttpServletResponse
Listing 22 shows an example of a JSP page that contains a mix of HTML and Java code. The Java code portions between the <% and the %> tags are known as scriptlets.
Listing 22. HelloWorld servlet: Using a JSP page to return content
<html>
<title>JSP page example</title>
The date is:
<%
Date date = new Date();
out.println(date);
%>
Some more HTML
The value of <em>e</em> is:
<%
double e = Math.exp(1.0);
out.println(e);
%>
Yet even more HTML
The value of PI is:
<%
double pi = 22.0/7.0;
out.println(pi);
%>
</html>
|
Note the use of the JSP implicit variable out to write content back the client, and note the interleaving of HTML and Java code.
An example scenario
To illustrate the various architectural options we'll use a simple user-login scenario that consists of:
- A login page with an HTML form that collects a username and a password from the user
- Application logic that validates the user's credentials (possibly using a database) and redirects to the site's main page
This simple example lets us illustrate a variety of application architectures.
"Spaghetti" code
On the ASP.NET side, a "spaghetti" code approach uses a single .aspx file to contain both the application logic and the HTML form. No code-behind file exists. (We don't recommend this approach for real-world examples, because all the presentation logic and application logic would be a single file, preventing you from reusing the code to validate the user's credentials.) The outline of the code looks something like the code in Listing 23.
Listing 23. "Spaghetti" code in ASP.NET
<html>
<head>
<title>Login example</title>
<script language="C#" runat=server>
...
private void btnLogin_Click(object sender, System.EventArgs e)
{
// Get the form field values
...
// Validate the username and password
if (ValidateUser(username, password) {
Response.Redirect("mainpage.aspx");
}
}
private bool ValidateUser(string userName, string password) {
...
}
...
</script>
</head>
<body>
<form runat="server">
<!-- login form fields go here -->
</form>
</body>
</html>
|
As Listing 24 shows, you can take the same approach in J2EE using a single JSP page to contain both the form and the application logic.
Listing 24. "Spaghetti" code in J2EE
<html>
<head>
<title>Login example</title>
<%!
private boolean validateUser(String userName, String password) {
...
}
%>
...
</head>
<body>
<%
if (request.getMethod().equals("GET") ) { %>
<form method="POST" target="login.jsp">
<!-- login form fields go here -->
</form> <% }
else {
String userName = request.getParameter("username");
String password = request.getParameter("password");
if (validateUser(userName, password)) {
response.sendRedirect("mainpage.jsp");
}
...
} %>
</body>
</html>
|
The JSP model isn't event-driven, so you need to check to see whether the form is being posted back by looking at the request and including the HTML for the form when it isn't a POST request. If it is a POST, you use a method declared in the JSP page to validate the login. Note the use of <%! to indicate that the code is a method. Scriptlets of code execute as they're encountered during JSP page processing, so the tags for scriptlets (<% %>) won't work for methods. Also note how you can easily include or exclude large chunks of HTML using an if/then/else programming construct. As in the ASP.NET example, this isn't a recommended approach for J2EE development. The mixing of presentation code (HTML) and application logic allows very little reuse and makes the code difficult to maintain.
Improved "spaghetti" code
On the ASP.NET side, a better approach builds on the previous example and uses a code-behind file in addition to the .aspx file. The event-handling code and the user-validation code are moved to a code-behind file, leaving the original .aspx file with only the HTML form and other HTML elements. This is certainly an improvement over the previous approach; the presentation code is more cleanly separated, which might let an HTML designer work on the presentation and a programmer work on the code-behind file.
If you use standard J2EE components, you don't have ASP.NET's event-driven, code-behind-file approach. A better approach on the J2EE side moves the application logic to a Java Servlet and limits the JSP page to the HTML components. This is an improvement for the same reasons the code-behind-file approach in ASP.NET is an improvement over the spaghetti code. Listing 25 shows how you can simplify the JSP page by putting the application logic in a servlet.
Listing 25. Improved "spaghetti" code in J2EE: JSP page
<html>
<head>
<title>Login example</title>
...
</head>
<body>
<form method="POST" target="LoginServlet">
<!-- login form fields go here -->
</form>
</body>
</html>
|
Listing 26 shows the validation code and navigation logic in a servlet.
Listing 26. Improved "spaghetti" code in J2EE: Java Servlet
public class LoginServlet extends HttpServlet {
/**
* Handles all HTTP POST requests
*
* @param request Object that encapsulates the request to the servlet
* @param response Object that encapsulates the response from the servlet
*/
public void doPost(
javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws ServletException, IOException {
try {
String userName = request.getParameter("username");
String password = request.getParameter("password");
if (validateUser(userName, password)) {
response.sendRedirect("mainpage.jsp");
}
...
} catch (Throwable t) {
...
}
}
private boolean validateUser(String userName, String password) {
...
}
}
|
The servlet in Listing 26 is the target of the form submission and acts like a controller -- handling user input and bringing up the appropriate page based on that input. Note that the HttpServlet parent class lets you handle both GET and POST requests by providing overridable methods (doGet() and doPost()).
The major drawback of this approach is that the credential-validation code (which would most likely access a database) is part of the code-behind file for the ASP.NET example and part of the servlet in the J2EE example. If different pages needed to use this logic, you'd have to duplicate it. Duplicate code is harder to maintain and more error prone because you must keep track of several copies.
Model-View-Controller approach
Now we'll show you a purer MVC approach to the example. On the ASP.NET side, this entails moving the credential-validation logic to a separate class library that you then access in the code-behind file. On the J2EE side, you move the credential-validation code to a separate class and access it from the servlet. The code snippets in Listing 27 show what the servlet would look like.
Listing 27. MVC in J2EE: Java Servlet
import com.ibm.businessobjects.UserValidation
public class LoginServlet extends HttpServlet {
/**
* Handles all HTTP POST requests
*
* @param request Object that encapsulates the request to the servlet
* @param response Object that encapsulates the response from the servlet
*/
public void doPost(
javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws ServletException, IOException {
try {
String userName = request.getParameter("username");
String password = request.getParameter("password");
UserValidation user = new UserValidation();
if (user.validate(userName, password)) {
response.sendRedirect("mainpage.jsp");
}
...
} catch (Throwable t) {
...
}
}
}
|
Listing 28 shows the credential-validation code in a separate class.
Listing 28. MVC in J2EE: User-validation class
package com.ibm.businessobjects;
public class UserValidation {
public boolean validate(String username, String password) {
...
}
}
|
The credential validation occurs inside a class that the servlet uses. Now the servlet doesn't need have to have any direct knowledge of how the validation is done. You're free to change the UserValidation class, and as long as you keep the same public interface, you don't need to change the servlet code.
Other J2EE components
The previous example showed how you separate out the business logic into a separate class or set of classes. This allows you to implement the business logic in a variety of ways without affecting the servlet and JSP page. Rather than use a business object that accesses a database (using JDBC), you can implement this logic in a variety of ways. For example:
- Use JCA to access legacy systems like mainframes: JCA is a J2EE API that allows a J2EE application to interact with a legacy system (such as CICS or IMS) using a standard interface.
- Use a Web services client API to access a Web service: This approach entails using Apache Axis or another Java Web services client API to access Web services.
- Use JMS to send the validation request as a message: This approach involves accessing JMS-compliant messaging middleware (like WebSphere MQ) using a standard API.
- Use EJB technology to implement the validation logic: This approach entails using EJB components -- instead of simple Java classes -- to implement the business logic. EJB components can use services in the application server to manage (database and other) transactions, manage method-level security, and provide clustering and caching. Delegating these sorts of tasks to the application server lets you focus on your business logic without having to worry about system-level issues.
 |
J2EE data access
Now we'll introduce you to the JDBC API, an industry-standard set of interfaces under the J2EE umbrella that lets Java programs access relational databases using a uniform set of API calls. JDBC lets you develop your applications once and then deploy them against any JDBC-compliant database -- DB2, Oracle, SQL Server, Sybase, mySQL, Informix, and others.
The JDBC API allows any Java component (including Java Servlets and JSP pages) to seamlessly access data from relational databases. JDBC consists of two parts: the API you use to access the database, and the pluggable JDBC drivers that database vendors provide for their respective databases. One of the most convenient features of the JDBC API is that you don't have to use any vendor-specific classes. You can specify the appropriate driver at runtime using a string constant and from that point on, you use the same programming interface regardless of which database you're using. This lets you switch out databases without having to change your code (as long as you provide the string constant that specifies the driver at runtime).
A JDBC example
A typical JDBC application must go through the following steps in order to interact with a database:
- Identify the driver. (You need to do this only once per application.)
- Identify the database and connect to it (providing authentication information when required).
- Execute queries and/or updates.
- Handle results.
- Disconnect from the database.
Listing 29 illustrates the previous steps.
Listing 29. A JDBC example
// Step 1. SPECIFY THE APPROPRIATE DRIVER TO USE
Class.forName("COM.ibm.db2.jdbc.app.DB2Driver");
// Step 2. Identify the database and connect to it
Connection conn = DriverManager.getConnection("jdbc:db2:SAMPLE","userid","password");
// Step 3. Execute query
Statement stmt = conn.createStatement();
stmt.execute("SELECT * FROM USERID.EMPLOYEE");
// Step 4. Get the results
ResultSet rs = stmt.getResultSet();
while (rs.next()) {
String firstCol = rs.getString(1);
int secondCol = rs.getInt(2);
...
}
// Step 5. Disconnect from the database
conn.close();
|
Step 1 in Listing 29 dynamically loads the appropriate driver. This example uses DB2; other JDBC drivers would have different fully qualified names. Step 2 provides a driver-specific String to indicate which database to connect to. Each JDBC driver has it own format for this String. DB2 uses "jdbc:db2:DBNAME", so in this example you're connecting to a database called SAMPLE. You're also providing authentication information so that the database server can authenticate your request for a connection. In Step 4, note how you can loop through a result set until the next() method returns false. You also have to know each column's type and call the appropriate getXXXX(n) method for each column and each type. Also note that the integer you pass to the getXXXX(n) methods is the column number.
This approach is quite similar to the approach an ADO.NET DataReader uses, in that the programmer is responsible for opening and closing the database connections. In ADO.NET, you initially use classes furnished by the ADO.NET provider for your database, so you can't switch databases at runtime as easily as you can with JDBC.
Connection pooling
ADO.NET also provides a set of classes called DataAdapters to allow operation in disconnected mode, freeing you from needing to manage the database connections explicitly. With a DataAdapter the typical application flow is:
- Use the provider's
DataAdapter class to identify the database (providing authentication information when required) and the query.
- Execute the query.
- Handle results.
With JDBC you can free yourself from explicitly managing connections as well. J2EE application servers can be configured to maintain pools of database connections for databases that are accessible via JDBC. Your application code then requests connections from a pool managed by the application server and returns them to the pool when it's finished using them, . Most application servers have fairly advanced options for managing the pools, including the ability to "recall" connections from errant applications that haven't returned a connection to the pool in a specified amount of time.
The typical flow for an application using connection pooling is:
- Look up the
DataSource (used to access a specific connection pool) in the application server's repository. (Each application server has a repository that contains all the configured connections pools and other resources like message queues and SMTP providers. You access the repository using a standard J2EE API called JNDI.) You need to do this only once.
- Get a connection from the pool (providing authentication information when required).
- Execute queries and/or updates.
- Handle results.
- Return the connection to the pool.
Listing 30 shows an example of using connection pooling.
Listing 30. A connection-pooling example
// Step 1. Look up the DataSource
InitialContext ic = new InitialContext();
DataSource ds = (DataSource) ic.lookup("jdbc/MyDS");
// Step 2. Get a connection from the pool
Connection conn = ds.getConnection();
// Step 3. Execute query
Statement stmt = conn.createStatement();
stmt.execute("SELECT * FROM USERID.EMPLOYEE");
// Step 4. Get the results
ResultSet rs = stmt.getResultSet();
while (rs.next()) {
String firstCol = rs.getString(1);
int secondCol = rs.getInt(2);
...
}
// Step 5. Return connection to the pool
conn.close();
|
Step 1 assumes that the application server has been configured with a DataSource named "jdbc/MyDS". This encapsulates a particular JDBC driver and database. You can also add the authentication information to the DataSource definition so your application doesn't need to provide it. You can use the InitialContext class, which is part of the JNDI API, to look up a variety of resources the application server controls. Steps 3 through 5 are the same as in the previous example, but note that calling the close() method in Step 5 only returns the connection to the connection pool, as opposed to closing the database connection.
J2EE application state management
When you write J2EE Web applications, you have a rich set of classes and interfaces at your disposal for managing your application's state. We'll introduce you the J2EE HttpSession interface (analogous to the ASP.NET HttpSessionState class) and other classes and interfaces that let you manage your application's state. And we'll discuss how you can use these classes and interfaces in both Java Servlets and JSP pages. First, though, we'll introduce you to the concept of scope, which is key to understanding application state management in J2EE.
Scope
From a programmer's point of view, state management involves storing data temporarily and then retrieving it as needed. In J2EE you have a choice of several "storage locations," each one with its own rules that dictate how long any stored data will be available. This duration ranges from storing some data temporarily only while a particular page is being processed, to storing data for the application's running life. In J2EE your choice of a "storage location" is referred to as the scope of a particular storage request or retrieval. The scope determines which J2EE object you'll attach your data to and how long that data will be available to you. The available scopes are:
- session: This is similar to session scope in ASP.NET. You can place any Java object in a user's session scope and retrieve it for as long as the session is active. J2EE applications use the
HttpSession interface (analogous to the HttpSessionState class in ASP.NET). You add objects to the session using a String as a label and use the same label to retrieve them.
- request: In J2EE, the
HttpServletRequest object lets you attach data to it, much like the HttpSession interface. This is useful when more than one resource handles a single request. For example, a Java Servlet could be the target of an HTML form submission and then forward the request to a JSP page to complete it. In this case the servlet can attach data to the HttpRequest object, and the JSP page has access to it. Note that in this scenario both the servlet and the JSP page use the same HttpRequest object. The ability to forward to a different resource within the same request is analogous to the Server.Transfer method in ASP.NET.
- application: All J2EE Web applications are packaged into a file with a .war extension before being deployed. The format of the file is standard, so you can deploy the same application to different application servers. All the J2EE components in a single .war file are considered to be part of the same application and share a common application context. This is exposed to developers via the
ServletContext interface, which -- like the HttpSession and HttpRequest interfaces -- lets you attach and remove any Java object. Items added to ServletContext are available for as long as the application is running and will survive the creation and destruction of individual sessions.
- page: Page context is available during the processing of a single page. For example, a Java scriptlet at the top of a JSP page can place an object in the
PageContext, which other scriptlets in the same page can then access.
Managing application state
Now that you have a bette |