Discovering AOP

This chapter covers ■ Understanding crosscutting concerns ■ Modularizing crosscutting concerns using AOP ■ Understanding AOP languages

Reflect back on your last project, and compare it with a project you worked on a few years back. What’s the difference? One word: complexity. Today’s software systems are complex, and all indications point to even faster growth in software complexity in the coming years. What can a software developer do to manage complexity? If complexity is the problem, modularization is the solution. By breaking the problem into more manageable pieces, you have a better shot at implementing each piece. When you’re faced with complex software requirements, you’re likely to break those into multiple parts such as business functionality, data access, and presentation logic. We call each of these functionalities concerns of the system. In a banking system, you may be concerned with customer management, account management, and loan management. You may also have an implementation of data access and the web layer. We call these core concerns because they form the core functionality of the system. Other concerns, such as security, logging, resource pooling, caching, performance monitoring, concurrency control, and transaction management, cut across—or crosscut—many other modules. We call these functionalities crosscutting concerns. For core concerns, object-oriented programming (OOP), the dominant methodology employed today, does a good job. You can immediately see a class such as LoanManagementService implementing business logic and AccountRepository implementing data access. But what about crosscutting concerns? Wouldn’t it be nice if you could implement a module that you identify as Security, Auditing, or PerformanceMonitor? You can’t do that with OOP alone. Instead, OOP forces you to fuse the implementation of these functionalities in many modules. This is where aspect-oriented programming (AOP) helps. AOP is a methodology that provides separation of crosscutting concerns by introducing a new unit of modularization—an aspect. Each aspect focuses on a specific crosscutting functionality. The core classes are no longer burdened with crosscutting concerns. An aspect weaver composes the final system by combining the core classes and crosscutting aspects through a process called weaving. Thus, AOP helps to create applications that are easier to design, implement, and maintain. In this chapter, we’ll examine the fundamentals of AOP, the problems it addresses, and why you need to know about it. In the rest of the book, we’ll examine AspectJ, which is a specific implementation of AOP. Let’s start by discussing how you manage various concerns without AOP, which will help you understand why you need AOP.

1.1 Life without AOP

How do you implement crosscutting concerns using OOP alone? Typically, you add the code needed for each crosscutting concern in each module, as shown in figure 1.1. This figure shows how different modules in a system implement both core concerns and crosscutting concerns. Let’s illustrate the same idea through a code snippet. Consider the skeleton implementation of a representative class that encapsulates some business logic in a conventional way, shown in listing 1.1. A system consists of many such classes. Although the details will vary, the listing shows a common problem many developers face: a conceptual separation exists between multiple concerns at design time, but implementation tangles them together. Such an implementation also breaks the Single Responsibility Principle (SRP)1 by making the class responsible for implementing core and crosscutting concerns. If you need to change the invocation of the code related to crosscutting concerns, you must change each class that includes such an invocation. Doing so breaks the Open/Close principle2 —open for extension, but closed for modifications. The overall consequence is a higher cost of implementing features and fixing bugs. With conventional implementations, core and crosscutting concerns are tangled in each module. Furthermore, each crosscutting concern is scattered in many modules. The presence of code tangling and code scattering is a tell-tale sign of the conventional implementation of crosscutting concerns.3 Let’s examine them in detail.

1.1.1 Code tangling

Code tangling is caused when a module is implemented to handle multiple concerns simultaneously. Developers often consider concerns such as business logic, performance, synchronization, logging, security, and so forth when implementing a module. This leads to the simultaneous presence of elements from each concern’s implementation and results in code tangling. Figure 1.2 illustrates code tangling in a module. Another way to look at code tangling is to use the notion of a multidimensional concern space. Imagine that you’re projecting the application requirements onto a multidimensional concern space, with each concern forming a dimension. Here, all the concerns are mutually independent and therefore can evolve without affecting the rest. For example, changing the security requirement from one kind of authorization scheme to another shouldn’t affect the business logic. But as you see in figure 1.3, a multidimensional concern space collapses into a one-dimensional implementation space. Because the implementation space is one-dimensional, its focus is usually the implementation of the core concern that takes the role of the dominant dimension; other concerns then tangle the core concern. Although you may naturally separate the individual requirements into mutually independent concerns during the design phase, OOP alone doesn’t let you retain the separation in the implementation phase. We’ve looked at the first symptom of crosscutting concerns when implemented using traditional techniques; now, let’s move on to the next.

1.1.1 Code scattering

Code scattering is caused when a single functionality is implemented in multiple modules. Because crosscutting concerns, by definition, are spread over many modules, related implementations are also scattered over all those modules. For example, in a system using a database, performance concerns may affect all the modules accessing the database. Figure 1.4 shows how a banking system implements security using conventional techniques. Even when using a well-designed security module that offers an abstract API and hides the details, each client—the accounting module, the ATM module, and the database module—still needs the code to invoke the security API to check permission. The code for checking permission is scattered across multiple modules, and there is no single place to identify the concern. The overall effect is an undesired tangling between the modules to be secured and the security module. Code tangling and code scattering together impact software design and development in many ways: poor traceability, lower productivity, lower code reuse, poor quality, and difficult evolution. All of these problems lead us to search for better approaches to architecture, design, and implementation. Aspect-oriented programming is one viable solution. In the next section, we’ll introduce you to AOP. Later in this chapter, we’ll examine alternatives to AOP as well.

1.2 Modularizing with AOP

In OOP, the core concerns can be loosely coupled through interfaces, but there is no easy way to do the same for crosscutting concerns. This is because a concern is implemented in two parts: the server-side piece and the client-side piece. OOP modularizes the server part quite well in classes and interfaces. But when the concern is of a crosscutting nature, the client part (consisting of the requests to the server) is spread over all the clients.

NOTE:We use the terms server and client here in the classic OOP sense to mean the objects that are providing a certain set of services and the objects using those services. Don’t confuse them with networking servers and clients.

As an example, let’s take another look at the typical implementation of a crosscutting concern in OOP, shown in figure 1.4. The security module provides its services through an interface. The use of an interface loosens the coupling between the clients and the implementations of the interface. Clients that use the security services through the interface are oblivious to the exact implementation they’re using; any changes to the implementation don’t require changes to the clients themselves. Likewise, replacing one security implementation with another is just a matter of instantiating the right kind of implementation. The result is that you can replace one security implementation with another with little or no change to the individual client modules. But this arrangement still requires that each client have the embedded code to call the API. Such calls must be included in all the modules requiring security and are tangled with their core logic. Using AOP, none of the core modules contain calls to the security API. Figure 1.5 shows the AOP implementation of the same security functionality shown in figure 1.4. The security concern—implementation and invocations—now resides entirely inside the security module and the security aspect. For now, don’t worry about the way in which AOP achieves this; we’ll explain in the next section. The fundamental change that AOP brings is the preservation of the mutual independence of the individual concerns. Implementations can be easily mapped back to the corresponding concerns, resulting in a system that is simpler to understand, easier to implement, and more adaptable to changes.

1.3 Anatomy of an AOP language

The AOP methodology is just that—a methodology. In order to be of any use in the real world, it must be implemented, or realized. Each realization of AOP involves specifying a language or a framework and associated tools. Like any other programming methodology, an AOP implementation consists of two parts: ■ The language specification describes the language constructs and syntax to express implementation of the core and crosscutting concerns. ■ The language implementation verifies the code’s adherence to the language specification and translates the code into an executable form.

1.3.1 The AOP language specification

Any implementation of AOP must specify a language to implement the individual concerns and a language to implement the weaving rules. Note that an AOP system may offer a homogeneous language that doesn’t distinguish between the two parts. This is likely to be the case in future AOP languages. Let’s take a closer look at these two parts. IMPLEMENTATION OF CONCERNS As in other methodologies, the concerns of a system are implemented into modules that contain the data and behavior needed to provide their services. A module that implements the core part of the caching concern maintains a collection of cached objects, manages the validity of the cached objects, and ensures bounded memory consumption. To implement both the core and crosscutting concerns, we normally use standard languages such as C, C++, and Java. WEAVING RULES SPECIFICATION Weaving rules specify how to combine the implemented concerns in order to form the final system. After you implement the core part of the caching concern in a module (perhaps through a third-party class library), you must introduce caching into the system. The weaving rule in this case specifies the data that needs to be cached, the information that forms the key into the cache storage, and so forth. The system then uses these rules to obtain and update cache from the specified operations. The power of AOP comes from the economical way of expressing the weaving rules. For instance, to modularize tracing concerns in listing 1.1, you can add a few lines of code to specify that all the public operations in the system should be logged. Here is a weaving specification for the tracing aspect: ■ Rule 1: Create a logger object. ■ Rule 2: Log the beginning of each public operation. ■ Rule 3: Log the completion of each public operation. This is much more succinct than modifying each public operation to add logging code. Because the tracing concern is modularized away from the class, it may focus only on the core concern, as follows: Compare this class with the one in listing 1.1: all the code to perform tracing—the ancillary concerns from the class’s point of view—have been removed. When you apply the same process to other crosscutting concerns, only the core business logic remains in the class. As you’ll see in the next section, an AOP implementation combines the classes and aspects to produce a woven executable. Weaving rules can be general or specific in the ways they interact with the core modules. In the previous logging example, the weaving rules don’t need to mention any specific classes or methods in the system. On the other end of the spectrum, a weaving rule may specify that a business rule should be applied only to specific methods, such as the credit() and debit() operations in the Account class or the ones that carry the @ReadOnly annotation. The specificity of the weaving rules determines the level of coupling between the aspect and core logic. The language used to specify weaving rules can be a natural extension of that language or something entirely different. For example, an AOP implementation using Java as the base language might introduce new extensions that blend well with the base language, or it could use a separate XML-based language to express weaving rules.

1.3.2 The AOP language implementation

The AOP language implementation performs two logical steps: It first combines the individual concerns using the weaving rules, and then it converts the resulting information into executable code. AOP implementation thus requires the use of a processor—weaver—to perform these steps. An AOP system can implement the weaver in various ways. A simple approach uses source-to-source translation. Here, the weaver processes source code for individual classes and aspects to produce woven source code. The aspect compiler then feeds this woven code to the base language compiler to produce the final executable code. This was the implementation technique used in early implementations of AspectJ. The approach suffers from several drawbacks because the executable code can’t be easily traced back to the original source code. For example, stack traces indicate line numbers in woven source code. Another approach first compiles the source code using the base language compiler. Then, the resulting files are fed to the aspect compiler, which weaves those files. Figure 1.6 shows a schematic of a compiler-based AOP language implementation. An AOP system may also be able to push the weaving process close to execution of the system. If the implementation of AOP is Java-based, a special class loader or a virtual machine (VM) agent can perform the weaving. Such an implementation first loads the byte code for the aspects, weaves them into the classes as they’re being loaded, and supplies those woven versions of the classes to the underlying VM. Yet another implementation could use automatically created proxies. In this case, each object that needs weaving is wrapped inside a proxy. Such an implementation typically works well in conjunction with another framework that controls the creation of objects. In this way, the framework can wrap each created object in a proxy. So far, we’ve looked at the mechanics of an AOP system. Now, let’s examine AOP’s fundamental concepts.

1.4 Fundamental concepts in AOP

By now, it should be clear that AOP systems help in modularizing crosscutting concerns. But so do many other technologies, such as byte-code manipulation tools, direct use of the proxy design pattern, and meta-programming. How do you differentiate AOP from these options? To find out, we need to distill the core characteristics of AOP systems into a generic model. If a system fits that model, it’s an AOP system. To implement a crosscutting concern, an AOP system may include many of the following concepts: ■ Identifiable points in the execution of the system —The system exposes points during the execution of the system. These may include execution of methods, creation of objects, or throwing of exceptions. Such identifiable points in the system are called join points. Note that join points are present in all systems—even those that don’t use AOP—because they’re points during execution of a system. AOP merely identifies and categorizes these points. ■ A construct for selecting join points —Implementing a crosscutting concern requires selecting a specific set of join points. For example, the tracing aspect discussed earlier needs to select only the public methods in the system. The pointcut construct selects any join point that satisfies the criteria. This is similar to an SQL query selecting rows in database (we’ll compare AOP with databases in section 1.5.2). A pointcut may use another pointcut to form a complex selection. Pointcuts also collect context at the selected points. For example, a pointcut may collect method arguments as context. The concept of join points and the pointcut construct together form an AOP system’s join point model. We’ll study AspectJ’s join point model in chapter 3. ■ A construct to alter program behavior —After a pointcut selects join points, you must augment those join points with additional or alternative behavior. The advice construct in AOP provides a facility to do so. An advice adds behavior before, after, or around the selected join points. Before advice executes before the join point, whereas after advice executes after it. Around advice surrounds the join point execution and may execute it zero or more times. Advice is a form of dynamic crosscutting because it affects the execution of the system. We’ll study AspectJ’s dynamic crosscutting implementation in chapter 4. ■ Constructs to alter static structure of the system —Sometimes, to implement crosscutting functionality effectively, you must alter the static structure of the system. For example, when implementing tracing, you may need to introduce the logger field into each traced class; inter-type declaration constructs make such modifications possible. In some situations, you may need to detect certain conditions, typically the existence of particular join points, before the execution of the system; weave-time declaration constructs allow such possibilities. Collectively, all these mechanisms are referred to as static crosscutting, given their effect on the static structure, as opposed to dynamic behavior changes to the execution of the system. We’ll study AspectJ’s static crosscutting support in chapter 5. ■ A module to express all crosscutting constructs —Because the end goal of AOP is to have a module that embeds crosscutting logic, you need a place to express that logic. The aspect construct provides such a place. An aspect contains pointcuts, advice, and static crosscutting constructs. It may be related to other aspects in a similar way to how a class relates to other classes. Aspects become a part of the system and use the system (for example, classes in it) to get their work done. We’ll examine AspectJ’s implementation of aspect in chapter 6. Figure 1.7 shows all these players and their relationships to each other in an AOP system. Each AOP system may implement a subset of the model. For example, Spring AOP (discussed in chapter 9) doesn’t implement weave-time declarations due to its emphasis on its runtime nature. On the other hand, the join point model is so central to AOP that every AOP system must support it—everything else revolves around the joinpoint model. When you encounter a solution that modularizes crosscutting concerns, try to map it onto the generic AOP model. If you can, then that solution is indeed an AOP system. Otherwise, it’s an alternative approach for solving the problem of crosscutting concerns.

1.5 AOP by analogy

When you’re learning a new technology, it sometimes helps to compare it with existing technologies. In this section, we’ll attempt to help you understand AOP by comparing it with Cascading Style Sheets (CSS), database programming, and eventoriented systems. The purpose of this section is to help those familiar with at least one of these technologies to understand AOP by analogy.

1.5.1 Cascading Style Sheets (CSS)

CSS is a widely supported mechanism to separate content from presentation in HTML pages. Without CSS, formatting information is fused with content (causing tangling), and similar content elements have presentational information spread into multiple places (causing scattering). CSS helps the situation by letting the main document focus on content by separating the formatting information into a document called a stylesheet. A core concept in CSS is a selector that selects document elements matching a certain specification. For example, the body p selector can select paragraphs inside the body element. You can then associate presentational information with a selector and, for example, set the background color of such elements to blue using the body p {background: blue;} element. AOP acts on classes in the same way that CSS acts on documents. AOP lets you separate crosscutting logic from the main-line logic. AOP’s pointcuts have the same selection role as CSS selectors. Whereas CSS selectors select structural elements in a document, pointcuts select program elements. Similarly, the blocks describing the formatting information are analogous to AOP advice in functionality. Often, the selection mechanism requires more information than merely using the inherent characteristics of a structure such as body p. It’s common practice to supplement content elements with additional metadata through the class attribute. For example, you can mark an HTML paragraph element as menu by using the tag

. Then, in the stylesheet, you can select such an element by using the p.menu selector and apply appropriate presentation characteristics. In AOP, practitioners face the same problem—selection through a pointcut often requires information beyond merely relying on inherent characteristics of the program elements such as class and method names. The use of Java annotations plays a role similar to the class attribute in HTML documents. You can, for example, mark a method as @Transactional and utilize it in a pointcut expression. There are similarities from the adoption perspective as well. Through WYSIWYG HTML editors, it’s easy to create a good-looking HTML. That apparent simplicity led to many initial web documents embedded with formatting information. But when we realized that it’s difficult to create a consistent look when every element’s formatting is specified independently, developers started to look favorably at CSS. AOP has encountered a similar trend. There is a level of comfort in embedding the implementation of crosscutting functionality inside classes; you can see exactly what’s going to happen. But you soon realize that creating a consistent implementation is nearly impossible when similar code is scattered in many places. In addition, using CSS requires a level of expertise and understanding of the semantics associated with the elements in a document. Using AOP requires similar understanding of the semantic separation between core and crosscutting elements. CSS works at the structure level, and database triggers offer similar separation at the programming level. Let’s see how that technique compares with AOP.

1.5.2 Database systems

Database systems offer “dynamic crosscutting” targeted toward data access, whereas AOP offers a similar mechanism toward general programming. It offers two good analogies to AOP concepts: SQL with pointcuts and triggers with advice. SQL AND POINTCUTS A join point is like a row in a database, whereas a pointcut is like an SQL query. An SQL query selects rows according to a specified criterion such as “rows in accounts table, where the balance is greater than 50”. It provides access to the content of the selected rows. Similarly, a pointcut is a query over program execution that selects join points according to a specified criterion such as “method execution in the Account class, where the method name starts with ‘set’”. It also provides access to the join point context (objects available at the join point, such as method arguments). TRIGGERS AND ADVICE Database programming often uses triggers to respond to changes made in data. For example, you can use a trigger to audit changes in certain tables. The following snippet calls the logInventoryIncrease() procedure when inventory increases: The static condition, such as the name of the table and the modified column, as well as the dynamic condition, such as the difference in the column value, are analogous to AOP’s pointcut concept. Both describe a selection criterion to “trigger” certain actions. The stored procedure specified in the trigger is analogous to AOP’s advice. Database triggers and AOP’s advice both modify the normal program execution to carry additional or alternative actions. But there are some obvious differences. Database triggers are useful only for database operations. AOP has a more general approach that can be used for many other purposes. But note that AOP doesn’t necessarily obviate the need for database triggers, for reasons such as performance and bringing uniformity to multiple applications accessing the same tables. Similar to database triggers, event-oriented programming includes the notion of responding to events.

1.5.3 Event-oriented programming

Event-oriented programming is essentially the observer design pattern (we’ll discuss it as an alternative to AOP in section 1.7.3). Each interested code site notifies the observers by firing events, and the observers respond by taking appropriate action, which may be crosscutting in nature. In AOP, the program is woven with logic to fire virtual events and to respond to the events with an action that corresponds to the crosscutting concern it’s implementing. But note this important difference: Unlike in event-based programming, there is no explicit code for the creation and firing of events in the subject classes. Executing part of the program constitutes the virtual-event generation. Also, event systems tend to be more coarse-grained than an AOP solution implements. Note that you can effectively combine event-oriented programming with AOP. Essentially, you can modularize the crosscutting concern of firing events into an aspect. With such an implementation, you avoid tangling the core code with the event-firing logic. Now that you have a good understanding of AOP, let’s turn our attention to a bit of history and the current status of AOP implementations.

文章来源

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。