Home  >  Article  >  Web Front-end  >  Detailed introduction to Avalonjs

Detailed introduction to Avalonjs

怪我咯
怪我咯Original
2017-06-27 11:46:421926browse

A brief history of Avalon and an overview of all the design principles that created it
Things started with the Apache JServ project. Stefano Mazzocchi and others who helped develop Apache JServ realized that some of the patterns used in the project were general enough to be used to create a server framework. On Wednesday, January 27, 1999 (about a month after the release of JServ 1.0b), Stefano came up with a proposal to start a project called Java Apache Server Framework. Its goal is to become the foundation for all Java server code at Apache. The idea is to centralize some components and reuse code across projects by providing a framework.
Stefano Mazzocchi, Federico Barbieri and Pierpaolo Fumagalli created the original version. In late 2000, Berin Loritsch and Peter Donald joined the project. By that time, Pierpaolo and Stefano had turned to the development of other projects, and the Java Apache Server Framework began to be called Avalon. These five developers are primarily responsible for the design and concepts used in the current version of the framework. The current version is very similar to the version released in June 2000. In fact, the main difference is the reorganization of packages and the division of projects into subprojects. The same design patterns and interfaces still exist today.

What is Avalon?
Avalon is the parent project of five sub-projects: Framework, Excalibur, LogKit, Phoenix, and Cornerstone. When they hear Avalon, most people think of Framework, but Avalon includes more than just Framework. Avalon began as the Java Apache Server Framework consisting of the framework, tools, components, and a server core implementation, all in one project. Because different parts of Avalon have different levels of maturity and different release cycles, we decided to divide Avalon into the small projects mentioned earlier. Doing so also makes it easier for new developers to understand and learn the different parts of Avalon - something that was almost impossible to do before. Framework
Avalon Framework is the foundation for all other projects under the Avalon umbrella. It defines the interfaces, contracts, and default implementation of Avalon. Framework places most of the work in it, so it is also the most mature project.
Excalibur
Avalon Excalibur is a set of server-side components that you can use in your own projects. It includes the implementation of pooling, database connection management and other component management implementations.
LogKit
Avalon LogKit is a high-speed logging toolset used by Framework, Excalibur, Cornerstone and Phoenix. Its model uses the same principles as the JDK 1.4 Logging package, but is compatible with JDK 1.2+.
Phoenix
Avalon Phoenix is ​​the core of the server, which manages the release and execution of services (Services, implemented as server-side components, called Blocks).
Cornerstone
Avalon Cornerstone is a set of Blocks or services that can be deployed in a Phoenix environment. These Blocks include socket management and task scheduling between Blocks.
Scratchpad
Scratchpad is not really a formal project, but a staging area for components that are not yet ready to be put into Excalibur. The quality of these components varies greatly, and their APIs are not guaranteed to remain unchanged until they are promoted to the Excalibur project.

Highlights of this Overview
In this overview, we focus on the Avalon Framework, but cover enough about Avalon Excalibur and Avalon LogKit to get you started. We'll use a hypothetical business server to show how to use Avalon in practice. It is beyond the scope of this overview to define a complete and comprehensive methodology, or to present all aspects of all sub-projects. We focus on the Avalon Framework because it is the foundation for all other projects. If you can understand the framework, you can understand any Avalon-based project. You will also become familiar with some common programming idioms (idioms) commonly used in Avalon. Another reason to focus on frameworks and involve the Avalon Excalibur and Avalon LogKit projects is that they are officially released and supported.

Where can Avalon be used?
I have been asked several times to clarify what Avalon is suitable for and what it is not suitable for. Avalon focuses on server-side programming and makes it easier to design and maintain server application-centric projects. Avalon can be described as a framework that includes an implementation. Although Avalon's focus is on server-side solutions, many people find it useful for general applications as well. The concepts used in Framework, Excalibur, and LogKit are general enough to be applied in any project. Two projects that focus more directly on servers are Cornerstone and Phoenix. Framework
1. A supporting or closed structure 2. A basic system or arrangement containing ideas
The word framework has a broad meaning in applications. Frameworks that focus on a single industry, such as pharmaceutical systems or communications systems, are called vertical market frameworks. The reason is that the same framework is not suitable for other industries. A framework that is very versatile and can be used in multiple industries is called a horizontal market framework. Avalon is a horizontal market framework. You can use Avalon's Framework to build a vertical market framework. The most convincing example of a vertical market framework built with Avalon is the Apache Cocoon publishing framework. Apache Cocoon version 2 is built using Avalon's Framework, Excalibur and LogKit projects. It takes advantage of the interfaces and contracts in the Framework, allowing developers to spend less time understanding how Cocoon works. It also makes efficient use of the data source management and component management code provided by Excalibur so that it doesn't have to reinvent the wheel. Finally, it uses LogKit to handle all logging issues in the publishing framework. Once you understand the principles behind the Avalon Framework, you can understand any system built on Avalon. Once you understand the system, you will be able to catch bugs caused by misuse of the framework more quickly. There is no magic formula
It is worth mentioning that any attempt to use a tool as a magic formula for success is asking for trouble. Avalon is no exception. Because Avalon's Framework is designed for server-side solutions, using it to build graphical user interfaces (GUIs) is not a good idea. Java already has a framework for building GUIs called Swing. Although you need to consider whether Avalon is suitable for your project, you can still learn something from its principles and design. The question you need to ask yourself is: "Where will the project be used?" If the answer is that it will run in a server environment, then Avalon would be a good choice, whether you are creating a Java Servlet or a special purpose Server application. If the answer is that it will run on a customer's machine and have no interaction with the server, then maybe Avalon is not a good fit. Even so, the component model is very flexible and helps manage complexity in large applications.

Principles and Patterns
Avalon is built based on some specific design principles. The two most important modes are Inversion of Control and Separation of Concerns. Component Oriented Programming, Aspect Oriented Programming, and Service Oriented Programming also have an impact on Avalon. Each programming principle could fill volumes of books, but they are all design thinking habits. Inversion of Control
The concept of Inversion of Control (IOC) means that components are always managed externally. This phrase was first used by Brian Foote in one of his papers. Everything the component needs is given to the component through Contexts, Configurations and Loggers. In fact, every stage in a component's life cycle is controlled by the code that creates the component. When you use this pattern, you implement a method for components to interact safely with your system. IOC is not synonymous with security! IOC provides a mechanism that allows you to implement an extensible security model. For a system to be truly secure, every component must be secure, no component can modify the contents of objects passed to them, and all interactions must use known entities. Security is a major concern, and IOC is a tool in a programmer's arsenal for achieving security goals.

Separation of Concerns
The idea that you should look at your system from different directions of thinking led to the Separation of Concerns (SOC) pattern. An example is looking at a web server from different perspectives on the same problem space. The web server must be secure, stable, manageable, configurable and meet HTTP specifications. Each attribute is a separate consideration. Some of these considerations are related to others, such as security and stability (if a server is unstable, it cannot be secure). The separate consideration model in turn led to Aspect Oriented Programming (AOP). Researchers have discovered that many considerations cannot be addressed at the class or method granularity. These considerations are called aspects. Examples of aspects include managing object life cycles, logging, handling exceptions, and cleaning up and releasing resources. Since there is no stable AOP implementation, the Avalon development team chose to implement aspects or considerations by providing some small interfaces that are then implemented by components.
Component-oriented programming
Component-oriented programming (Component Oriented Programming, COP) is an idea of ​​dividing the system into some components or facilities. Each facility has a working interface and a contract surrounding that interface. This approach allows instances of components to be easily replaced without affecting the code in other parts of the system. The main difference between Object Oriented Programming (OOP) and COP is the level of integration. The complexity of COP systems is easier to manage, thanks to fewer interdependencies between classes. This increases the degree of code reuse. One of the main benefits of COP is that modifying some parts of the project's code does not break the entire system. Another benefit is that you can have multiple implementations of a component and choose between them at runtime.
Service-oriented programming
The idea of ​​Service Oriented Programming (SOP) is to divide the system into some services provided by the system. Service
1. Work or duties performed for others 2. A facility that provides repair or maintenance 3. A facility that provides tools to the public
Avalon's Phoenix views every facility to be provided as Is a service consisting of a specific interface and related contracts. The implementation of a service is called a Block. It is important to understand that a server program is composed of multiple services. Taking a mail server as an example, it will have protocol processing services, authentication and authorization services, management services and core mail processing services. Avalon's Cornerstone provides some low-level services that you can take advantage of in your own system. The services provided include connection management, socket management, participant/role management and scheduling, etc. We introduce services here as they relate to the process of decomposing our hypothetical system into different facilities.

Breaking down a system

How do you decide what makes up a component? The key is to define the facilities your solution requires in order to operate efficiently.
We will use an imaginary business server to show how to identify and determine services and components. After we have defined some services used by the system, we will take one of these services as an example and define the different components required for that service. My goal is to impart to you some concepts that will help you define your system into manageable pieces.

System Analysis - Identifying Components
Although it is beyond the scope of this article to provide a complete and comprehensive methodology, I would like to discuss a few issues. We will start with implementation-oriented definitions of components and services and then provide a practical definition. Component
A component is a combination of a worker interface and an implementation of the worker interface. Using components provides loose coupling between objects, allowing the implementation to be changed without affecting the code that uses it.
Service
A service consists of one or more components, providing a complete solution. Examples of services include protocol handlers, task schedulers, authentication and authorization services, and so on.
Although these definitions provide a starting point, it does not provide a complete picture. In order to break down a system (defined as a set of facilities that make up a project) into its necessary components, I recommend using a top-down approach. This approach avoids getting bogged down in details before you know exactly what's available. Determine the scope of your project
What functions are expected to be completed by your project? You should always have a general idea at the beginning. In the business world, an initial statement of work does the job. In the open source world, this is usually done with an idea or a brainstorming process. I think the importance of having a high-level view of a project cannot be overstated. It is obvious that a large project will consist of many different services, while a small project will only have one or two services. If you start to feel a little overwhelmed, just remind yourself that big projects are actually many smaller projects under one big umbrella. Eventually, you'll be able to understand the big picture of the entire system.

Work Description: Business Server
Business Server (Business Server) is a hypothetical project. For the purposes of our discussion, its functionality is to process sales orders, automate billing to customers, and manage inventory control. Sales orders must be processed when they arrive, through some type of transaction system. The server automatically issues a bill to the customer 30 days after the sales order is filled. Inventory is managed both by the server and by the current inventory levels in the factory or warehouse. The business server will be a distributed system, and each server will communicate with other servers through a messaging service.
Discovering Services
We will use this Business Server project to discover services. Considering the overly general statement of work above, we can immediately see some of the services defined in the project description. The list of services can be divided into two broad categories: explicit services (services that can be derived directly from the work description) and implicit services (services discovered based on similar work, or services that support explicit services). Note that the company implementing the system does not have to develop all services themselves - some can be purchased as commercial solutions. In those cases, we might develop a wrapper that allows us to interoperate with commercial products in a deterministic way. The company implementing the system will build most of the services. Explicit services
From the work description, we can quickly export some services. But this initial analysis does not mean that our work is complete, because the definition of some services requires the existence of other services to ensure. Transaction processing service
The job description clearly states that "sales orders must be processed as they arrive." This suggests that we need a mechanism to accept sales requests and process them automatically. This is similar to how web servers work. They receive a request for a resource, process it and return a result (such as an HTML page). This is called transaction processing. To be complete, there are different types of transactions. This general transaction processing service will most likely have to be broken down into something more specific, like a "sales order processor". The exact method depends on the generality of your service. There is a balance between usability and reusability. The more generic a service is, the more reusable it is. It is also usually more difficult to understand.
Scheduling Service
In some cases, when a transaction is completed and a specific period of time has passed, an event must be scheduled. Furthermore, the inventory control process must be able to issue purchase orders periodically. Because the job description states that "30 days after the sales order is filled, the server automatically issues a bill to the customer", we need a scheduling service. Fortunately, Avalon Cornerstone provides one for us so that we don't have to write one ourselves.
Message Service
The work description states that in our distributed system "each server will communicate with other servers through a message service." Let's think about it, sometimes users want a specific product or a method they want to use. Messaging services are a prime example of leveraging other companies' products. Most likely, we will use Java Messaging Service (JMS) as the interface of Messaging Service. Because JMS is a standard, its interface is unlikely to change anytime soon. From practical experience, a well-defined message-oriented system is more scalable than an object-oriented system (such as EJB). One reason for better scalability is that messages generally have smaller concurrent memory overhead. Another reason is that it is easier to spread the load of message processing across all servers, rather than concentrating all processing on a small cluster of servers (or even on a single server).
Inventory Control Service
Although this is not a textbook classic service, it is a requirement for this system. The Inventory Control service constantly monitors records of factory or warehouse inventory and triggers events when inventory begins to run low.

Implicit services
Use the experience gained in the past system to further decompose the system into other services, and you will get some services that are not explicitly pointed out but are needed by the system. Due to space constraints, we will not do a comprehensive breakdown. Authentication and Authorization Services
Authentication and authorization services are not explicitly mentioned in the job description, but all business systems must carefully consider security. This means that all clients of the system must be authenticated and all user actions must be authorized.
Workflow Automation Service
Workflow automation is a popular development area in enterprise systems. If you don't use a third-party workflow management server, you'll need to write one yourself. Typically what workflow automation does is use software systems to schedule tasks throughout a company's business processes. For more information, please refer to the Workflow Management Council's website at http://www.wfmc.org/.
Document Center Service
As the current status information of a task, the definition of the word "Document Center" is very imprecise. In other words, when a company receives a purchase order, our system needs to be able to store and recall the purchase order information. Billing has the same requirements as any other process in the system, from inventory to new user requests.

Summary
I hope the examples of services in the Business Server project can help you discover more. You will find that as you move from higher to lower abstraction layers, you will find that more types of services are required, such as a connection service to handle requests on an open port. Some of the services we define will be implemented through third-party systems, such as messaging services and workflow management services. For these services, it is in your best interest to use a standard interface so that you can switch providers later. Some services are actually large services composed of multiple services. Some services are already provided in Avalon Excalibur or Avalon Cornerstone. One thing you should keep in mind when discovering services in a system is that a service should be a high-level subsystem. This will help you define components through a team of analysts. Because we've identified the major services, you can have multiple individuals (or teams) break down each service in parallel. Subsystem boundaries are also well defined and there is little chance of overlap. If you decide to do a parallel analysis, you should go back and identify common components so that they can be reused as much as possible.

UML Diagram for the Business Server

Discovering Components
We will use the Document Center service mentioned earlier as an example to illustrate the process of identifying appropriate components. For the sake of discussion, we now list the requirements for Document Center services. The Document Center will use a database as persistent storage, authorize clients, and cache documents in memory. Practical definition of components
When we talk about components, you should think about it in terms of: What facilities does my service need to operate? Avalon believes in the concept of casting a system. A system developer is faced with a list of responsibilities for a component, called its role. What is a role?
The concept of role comes from theater. A play, musical, or movie will have a certain number of characters played by actors. While there never seems to be a shortage of actors, the number of roles is limited. The script of a show defines the function or behavior of a character. Just like what happens in theater, the script determines how you interact with the components. Think about the different actors in the system. You will project the components into the actors and talk to them. A role is a contract for a class of components. For example, our Document Center service needs to operate a database. Avalon Excalibur defines a component that meets the needs of the "Data Source" role. There are two different components in Excalibur, both meeting the needs of this role. Which one to use depends on the environment the service is in, but they all satisfy the same contract. Many Avalon-based systems will use only one active component per character. Scripts are working interfaces: interfaces with which other components interact. When determining the interface of a component, you must have a definite contract and keep it in mind. The contract stipulates what users of the component must provide and what the component produces. Sometimes usage semantics must be included in the contract. An example is the difference between temporary storage components and persistent storage components. Once the interfaces and protocols are defined, you can work on implementing them.

What is a good candidate component?
In our Document Center service, we have identified four possible components: DataSourceComponent (from Excalibur), Cache, Repository, Guardian. You should seek roles that are likely to have multiple implementations with which interaction can occur seamlessly. Through this example, you will see that there are situations where you need to use alternative facilities. In most cases, you will only use one implementation of this facility, but you need to be able to upgrade it independently without affecting the rest of the system. In other cases, you need to use different implementations depending on the environment. For example, the "Data Source" defined by Excaliber will usually handle all the JDBC connection pooling itself, but sometimes you may want to take advantage of the facilities provided in Java 2 Enterprise Edition (J2EE). Excalibur solves this problem by having one "Data Source" component directly manage JDBC connections and pools, and another component using Java's Naming and Directory Interface (JNDI) to get specific connections.
How is it not considered a good component?
People who are used to using JavaBeans like to implement everything as a JavaBean. This means everything from data models to transaction processing. If you approach components this way, you may end up with an overly complex system. Think of a component as a model of a service or facility rather than a model of data. You can have components that pull data from other sources, but the data should still remain data. An example of this philosophy in Avalon Excalibur is that the Connection is not a component. Another example is the Guardian component we mentioned earlier. It could be argued that the logic contained in Guardian is too relevant to the Document Center service and cannot be used as a component in a completely different service. While there are many ways to manage complexity, and many ways to make it flexible, sometimes it's not worth the extra work. In this case, you must weigh your decision carefully. If a potential component's logic will be applied consistently, it might make sense to treat it as a component. There can be multiple instances of a component in a system, and they can be selected at runtime. If the underlying component's logic is determined only by another component, it may be possible to put the logic into that other component. Through the examples of Guardian component and Repository component, we can argue that Guardian is too focused on Repository and is not implemented as a component.
Breaking down the Document Center Service
We will list the components that will be implemented, along with their roles, root causes, and sources (if the components already exist). DocumentRepository
DocumentRepository is the parent component of the entire service. In Avalon, services are implemented as Blocks, which are components of a specific type. Block must have a working interface that extends the Service marker interface. The Block interface also extends Avalon's Component interface. Please note that Block and Service are interfaces included in Avalon Phoenix. Finally, Service is still technically a specific type of Component. DocumentRepository is how we obtain Document objects from persistent storage. It interacts with other components in the service to provide security, functionality, and speed. This specific DocumentRepository will connect to the database and use the database logic internally to build Document objects.
DataSourceComponent
DataSourceComponent is provided by Avalon Excalibur. It's how we get a valid JDBC connection object.
Cache
Cache is a short-term in-memory storage facility. DocumentRepository will use it to save Document objects and reference them through a hashing algorithm. In order to improve the reusability of Cache components, stored objects must implement a Cacheable interface.
Guardian
The role of the Guardian component is based on participant management permissions. Guardian will load the licensing ruleset from the database. Guardian will use the standard Java security model to guarantee access to a specific Document.

Summary
By now, you should have some idea of ​​what makes a good component. The example describes all the components in the Document Center service and briefly introduces the work they will accomplish. A quick glance at this list illustrates the approach of implementing facilities as components rather than data. By now, you should be able to determine what components your service needs to operate.

Framework and Foundation

We will describe Avalon's contracts and interfaces to lay the foundation for us to actually write components.
Avalon Framework is the central part of the entire Avalon project. If you understand the contracts and structures defined by a framework, you can understand any code that leverages that framework. Remember the principles and patterns we have discussed. In this section, we explain in detail how the concept of roles works in practice, the lifecycle of components and how interfaces work.

Define the role of components
In Avalon, all components play a role. The reason is that you get your components through roles. In this arena, the only thing we have to consider is the character's signature. Recall from Part 2 that we defined a component as "a combination of a working interface and an implementation of that working interface". The work interface is the role. Creating a role's interface
Below you will see an example of an interface, as well as some best practices and reasons why. package org.apache.bizserver.docs;public interface DocumentRepository extends Component{ String ROLE = DocumentRepository.class.getName(); Document getDocument(Principal requestor, int refId);}
Best Practice
· Contains a name A string for "ROLE", which is the official name of the role. This name is the same as the fully qualified name of the worker interface. This will help in the future when we need to get an instance of a component. · If possible, please extend the component interface. This will make it easier when you publish your components. This is of no use to you if you are not responsible for controlling the working interface. It's not too much of a problem since you can always cast it to an instance of Component when publishing. · Do one thing and do it well. A component's interface should be as simple as possible. If your working interface extends some other interface, it will make the component's contract difficult to understand. An old American acronym expresses this point well: Keep It Simple, Stupid (KISS). It's not hard to be smarter (and stupider) than yourself, I've done it a few times myself. · Only identify the methods you need. The client program should not know any implementation details, and too many alternative methods will only introduce unnecessary complexity. In other words, choose a way and stick with it. · Don’t let your character interface extend any lifecycle or survival interface. If you implement any such class or interface, you are trying to implement the specification. This is not a good pattern and will only create debugging and implementation problems in the future.
Select a character name
In Avalon, every character has a name. It's how you get references to other components in the system. The Avalon development team has outlined some conventions for naming characters. Naming convention
· The fully qualified name of the work interface is usually the role name. Exceptions are listed below these general rules. In this example, our theoretical component name should be "org.apache.bizserver.docs.DocumentRepository". This is the name that should be included in the "ROLE" attribute of your interface. · If we get a reference to the component through a component selector, we usually use the role name deduced from the first rule, plus the word "Selector" at the end. The result of this naming rule will be "org.apache.bizserver.docs.DocumentRepositorySelector". You can get this name via DocumentRepository.ROLE + "Selector". · If we have multiple components that implement the same working interface but serve different purposes, we will separate roles. A role is a component's purpose in a system. Each character name will begin with the original character name, but the name indicating the character's purpose will be appended in the form /${purpose}. For example, for DocumentRePository we can have the following purposes: PurchaseOrder (purchase order) and Bill (bill). These two roles can be expressed as DocumentRepository.ROLE + "/PurchaseOrder" and DocumentRepository.ROLE + "/Bill" respectively.

Framework interface overview
The entire Avalon Framework can be divided into seven main categories (according to API): Activity, Component, Configuration, Context, Logger, Parameters, Thread, and Miscellany. Each category (except Miscellany) represents a concern area. A component usually implements several interfaces to indicate the direction of consideration it cares about. This enables the component container to manage each component in a consistent manner. Life cycle of Avalon interface
When a framework implements multiple interfaces to consider various aspects of the component separately, there is the potential for confusion about the order of method calls. The Avalon Framework realizes this, so we developed a protocol for event lifecycle order. If your component doesn't implement the relevant interface, it simply jumps to the next event to handle. Because there is a correct way to create and prepare components, you can set up the component when the event is received. The life cycle of a component is divided into three phases: initialization phase, activity service phase and destruction phase. Because these stages occur sequentially, we will discuss these events in sequence. In addition, because of the Java language, the behavior of Construction and Finalization is performed implicitly, so we will skip it. We will list the method name and the required interface. Within each phase, there are steps identified by method names. If the component extends the interface specified in brackets, these steps are performed sequentially. The following steps in the initialization phase
occur sequentially and only occur once during the component lifetime. 1. enableLogging() [LogEnabled] 2. contextualize() [Contextualizable] 3. compose() [Composable] 4. configure() [Configurable] or parameterize() [Parameterizable] 5. initialize() [Initializable] 6. start () [Startable]
Activity Service Phase
The following steps occur sequentially, but may occur multiple times during the component's lifetime. Note that if you choose not to implement the Suspendable interface, it is your component's responsibility to ensure correct functionality when performing any steps starting with re. 1. suspend() [Suspendable] 2. recontextualize() [Recontextualizable] 3. recompose() [Recomposable] 4. reconfigure() [Reconfigurable] 5. resume() [Suspendable]
Destruction phase
The following The steps occur sequentially and only once during the lifetime of the component. 1. stop() [Startable] 2. dispose() [Disposable]

Avalon Framework Contract
In this section, we will introduce everything in alphabetical order, except the most important part: Component, We put it at the front. When I use "container" or "container" to describe components, I have a special meaning. I mean those child components that have been instantiated and controlled by the parent component. I don't mean the components you get via ComponentManager or ComponentSelector. Furthermore, some Avalon step execution commands received by the container component must be propagated to all its subcomponents, as long as these subcomponents implement the corresponding interface. Specific interfaces are Initializable, Startable, Suspendable, and Disposable. The reason for arranging the contracts this way is that these interfaces have special execution conventions. Component
This is the core of Avalon Framework. The interface defined by this consideration direction will throw ComponentException. Component
Each Avalon component must implement the Component interface. Component Manager and Component Selector only deal with Component. This interface has no defined methods. It just serves as a token interface. Any component must use the default constructor without parameters. All configuration is done through the Configurable or Parameterizable interface.
Composable
A component that uses other components needs to implement this interface. This interface has only one method compose(), which takes a single parameter of type ComponentManager. The contract surrounding this interface is that compose() is called once and only once during the lifetime of the component. This interface, like any other interface that defines methods, uses the reverse control pattern. It is called by the component's container, and only those components required by the component will appear in the ComponentManager.
Recomposable
In rare cases, a component will require a new ComponentManager and a new component-role mapping relationship. In these cases, the recomposable interface needs to be implemented. Its method name is also different from that of Composable, which is recompose(). The contract around this interface is that the recompose() method can be called any number of times, but not before the component is fully initialized. When this method is called, the component must update itself in a safe and consistent manner. Typically this means that all operations performed by the component must stop between updates and resume after the updates.

Activity
This set of interfaces is related to the contract of the component life cycle. If an error occurs during this set of interface calls, you can throw a generic Exception. Disposable
If a component needs to know in a structured way that it is no longer needed, it can use the Disposable interface. Once a component is deallocated, it can no longer be used. In fact, it's just waiting to be garbage collected. This interface has only one method dispose(), which has no parameters. The contract surrounding this interface is that the dispose() method is called once and is the last method called during the lifetime of the component. It also indicates that the component will no longer be used and the resources occupied by the component must be released.
Initializable
If any component needs to create other components, or needs to perform initialization operations to obtain information from other initialization steps, it must use the Initializable interface. This interface has only one initialize() method, which has no parameters. The contract surrounding this interface is that the initialize() method is called once, and it is the last method called during the initialization process. It also indicates that the component is active and can be used by other components in the system.
Startable
If any component continues to run during its lifetime, it must use the Startable interface. This interface defines two methods: start() and stop(). Both methods have no parameters. The contract surrounding this interface is that the start() method is called once after the component is fully initialized. The stop() method is called once before the component is destroyed. None of them are called twice. start() is always called before stop(). Implementations of this interface are required to execute the start() and stop() methods safely (unlike the Thread.stop() method) and without causing system instability.
Suspendable
If any component allows itself to be suspended during its lifetime, it must use the Suspendable interface. Although it is usually always used with the Startable interface, this is not required. This interface has two methods: suspend() and resume(). Both methods have no parameters. The contract surrounding this interface is: suspend() and resume() can be called any number of times, but not before the component is initialized and started, or after the component is stopped and destroyed. Calling the suspend() method on a suspended component or calling resume() on a component that is already running will have no effect.

Configuration
This set of interfaces describes configuration considerations. If any problem occurs, such as not having the required Configuration element, a ConfigurationException can be thrown. Configurable
Those components that need to determine their behavior based on configuration must implement this interface to obtain an instance of the Configuration object. This interface has a configure() method with only one parameter of type Configuration. The contract surrounding this interface is that the configure() method is called once during the lifetime of the component. The Configuration object passed in must not be null.
Configuration
Configuration object is a tree composed of configuration elements, which have some attributes. In a way, you can think of the configuration object as a greatly simplified DOM. There are too many methods in the Configuration class to be introduced in this article. Please refer to the JavaDoc documentation. You can get a String, int, long, float, or boolean value from the Configuration object. If the configuration does not exist, a default value will be provided. The same goes for attributes. You can also obtain child Configuration objects. The contract states that a Configuration object with a value should not have any child objects, and the same is true for child objects. You'll notice that you can't get the parent Configuration object. That's what design does. In order to reduce the complexity of configuring the system, in most cases the container will pass the child configuration object to the child component. Child components should not have access to parent configuration values. This approach may bring some inconvenience, but the Avalon team always chooses to put security first when compromises are needed.
Reconfigurable
Components that implement this interface behave very similarly to Recomposable components. It has only one reconfigure() method. This design decision is to reduce the learning difficulty of those interfaces starting with Re. Reconfigurable is to Configurable what Recomposable is to Composable.

Context
The concept of Context in Avalon originated from the need for a mechanism to pass simple objects from the container to the component. The exact protocol and name bindings are intentionally undefined to provide maximum flexibility to developers. The contract surrounding the use of Context objects is defined by you in your system, although the mechanics are the same. Context
Context interface only defines one get() method. It has a parameter of type Object and returns an object with the parameter object as the key value. The Context object is assembled by the container and then passed to the sub-component. The sub-component only has read permissions on the Context. There is no other contract except that Context is always read-only for child components. If you extend Avalon's Context, please be careful to abide by this contract. It's part of the reverse control model and part of the security design. In addition, it is not good to pass a reference to the container in the Context, for the same reason that the Context should be read-only.
Contextualizable
Components that wish to receive Context objects from the container should implement this interface. It has a method named contextualize(), and the parameter is the Context object assembled by the container. The contract surrounding this interface is that contextualize() is called once during the lifetime of the component, after LogEnabled, but before other initialization methods.
Recontextualizable
Components that implement this interface behave very similarly to Recomposable components. It has only one method called recontextualize(). This design decision is to reduce the learning difficulty of interfaces starting with Re. Recontextualizable is to Contextualizable as Recomposable is to Composable.
Resolvable
The Resolvable interface is used to identify objects that need to be resolved in certain contexts. An example is: an object is shared by multiple Context objects and changes its own behavior according to a specific Context. Context calls the resolve() method before the object is returned.

Logger
Every system needs to have the ability to log events. Avalon uses its LogKit project internally. Although LogKit has some methods for statically accessing a Logger instance, the Framework expects to use the reverse control pattern. LogEnabled
Every component that requires a Logger instance must implement this interface. This interface has a method named enableLogging() that passes the Avalon Framework Logger instance to the component. The contract around this interface is that it is called only once during the lifetime of the component, before any other initialization steps.
Logger
Logger interface is used to abstract different log libraries. It provides a unique client API. Avalon Framework provides three encapsulation classes that implement this interface: LogKitLogger for LogKit, Log4jLogger for Log4J, and Jdk14Logger for the JDK1.4 logging mechanism.

Parameters
Avalon recognizes that the Configuration object hierarchy is too heavyweight in many situations. Therefore, we propose a Parameters object to provide an alternative to the Configuration object, using a name-value pair approach. Parameterizable
Any component that wishes to use Parameters to replace the Configuration object will implement this interface. Parameterizable has only one method called parameterize(), with the parameter being the Parameters object. The contract surrounding this interface is that it is called once during the lifetime of the component. This interface is incompatible with the Configurable interface.
Parameters
The Parameters object provides a mechanism to obtain a value through a String type name. There are convenience methods that allow you to use the default value if the value does not exist, or you can get any value in the same format from the Configurable interface. Despite the similarities between Parameters objects and java.util.Property objects, there are important semantic differences. First, Parameters are read-only. Second, Parameters are always easily exported from the Configuration object. Finally, the Parameters object is exported from the XML fragment and looks like this:

Thread
Thread tag Markers are used to signal basic semantic information about the container based on component usage. They take into account thread safety and provide tags for component implementations. Best practice is to defer implementation of these interfaces until the class that ultimately implements the component. This avoids complications where a component is marked as ThreadSafe, but component implementations derived from it are not thread-safe. The interfaces defined in this package form part of what we call the LifeStyle family of interfaces. Another LifeStyle interface is part of the Excalibur package (so it is an extension of this core interface set), Poolable is defined in Excalibur's pool implementation. SingleThreaded
The contract surrounding the SingleThreaded component is that components that implement this interface are not allowed to be accessed by multiple threads at the same time. Each thread needs to have its own instance of this component. Another approach is to use a pool of components instead of creating a new instance every time the component is requested. In order to use a pool, you need to implement Avalon Excalibur's Poolable interface instead of this interface.
ThreadSafe
The contract surrounding ThreadSafe components is that their interfaces and implementations will work normally no matter how many threads access the component at the same time. Although this is a flexible design goal, sometimes depending on the technology you use, it just isn't achievable. A component that implements this interface usually has only one instance in the system, and other components will use this instance.

Others
These classes and interfaces in the Avalon Framework's root package include the Exception hierarchy and some common utility classes. But there is one class worth mentioning. Version
JavaTM version technology is specified in the manifest file in the jar package. The problem is that you lose the version information when the jar is unpacked, and the version information is placed in an easily modified text file. When you combine these issues with a steeper learning curve, checking versions of components and interfaces is difficult. The Avalon development team designed the Version object so that you can easily check and compare versions. You can implement the Version object in your component and it will be easier to test the appropriate component or minimum version number.

Realizing the Dream

We will show you how to use Avalon Framework and Avalon Excalibur to implement your service application. We'll show you how easy Avalon is to use.
After you complete the analysis, you need to create the components and services that make up your system. Avalon wouldn't be of much use if it just described some programming habits you could use. But even so, applying these programming habits and patterns will be helpful in understanding the entire system. Avalon Excalibur provides some useful components and tools that you can use in your own system to make your life easier. As our demonstration, we go through the entire process of defining a component and taking a document from a repository to implement it. If you remember our discussion about the theoretical business server, we identified this component as a service. In actual situations, there are many situations where a component is a service.

Implement the component
Here, we define how to implement our component. We will go through the entire process of implementing the DocumentRepository component mentioned earlier. The first thing we need to figure out is what area our component is focused on. Then we need to figure out how to create and manage our components. Select areas of focus
We have defined roles and interfaces for the DocumentRepository component earlier, and we are ready to create the implementation. Because the DocumentRepository interface defines only one method, we have the opportunity to create a thread-safe component. This is the most popular type of component because it allows only minimal resource consumption. In order for our implementation to be thread-safe, we really need to think carefully about how to implement this component. Since all our documents are stored in the database, and we want to use an external Guardian component, we will need to access other components. As responsible developers, we want to log information that can help us debug our components and track what's going on internally. The beauty of the Avalon framework is that you only implement the interfaces you need and can ignore the ones you don't. This is the benefit of Separation of Concerns. When you find a new aspect that needs to be considered, you can add new functionality to the component by simply implementing the relevant interface. No changes are required to the parts that use your component. Since thread safety is a design goal, we already know that we need to implement the ThreadSafe interface. The DocumentRepository interface has only one method, so the use of the working interface of this component meets this requirement. And we know that Component will not be used before it is fully initialized, nor will it be used after it is destroyed. In order to complete the design, we need to implement some implicit interfaces. We want the solution to be secure enough that we might explicitly know whether a component has been fully initialized. To achieve this goal, we will implement the Initializable and Disposable interfaces. Since information about the environment may change, or may need to be customized, we need to let the DocumentRepository implement the Configurable interface. The method provided by Avalon to obtain an instance of the required component is to use a ComponentManager. We need to implement the Composable interface to get component instances from ComponentManager. Because the DocumentRepository accesses documents in the database, we need to make a decision. Do we want to use the Avalon Excalibur DataSourceComponent, or do we want to implement the database connection management code ourselves. In this article, we will utilize DataSourceComponent. At this point, our class skeleton looks like this: public class DatabaseDocumentRepositoryextends AbstractLogEnabledimplements DocumentRepository , Configurable, Composable, Initializable, Disposable, Component, ThreadSafe{ private boolean initialized = false; private boolean disposed = false; private ComponentManager manager = null; private String dbResource = null; /*** Constructor. All Components need a public no argument constructor * to be a legal Component.*/ public DatabaseDocumentRepository() {} /*** Configuration. Notice that I check to see if the Component has * already been configured? This is done to enforce the policy of * only calling Configure once.*/ public final void configure(Configuration conf) throws ConfigurationException { if (initialized || disposed) { throw new IllegalStateException ( "Illegal call"); } if (null == this.dbResource) { this.dbResource = conf.getChild("dbpool").getValue(); getLogger().debug("Using database pool: " + this.dbResource ); // Notice the getLogger()? This is from AbstractLogEnabled // which I extend for just about all my components. } } /*** Composition. Notice that I check to see if the Component has * already been initialized or disposed? This is done to enforce * the policy of proper lifecycle management.*/ public final void compose(ComponentManager cmanager) throws ComponentException { if (initialized || disposed) { throw new IllegalStateException ("Illegal call"); } if (null == this.manager) { this.manager = cmanager; } } public final void initialize() throws Exception { if (null == this. manager) { throw new IllegalStateException("Not Composed"); } if (null == this.dbResource) { throw new IllegalStateException("Not Configured"); } if (disposed) { throw new IllegalStateException("Already disposed"); } this.initialized = true; } public final void dispose() { this.disposed = true; this.manager = null; this.dbResource = null; } public final Document getDocument(Principal requestor, int refId) { if (!initialized || disposed) { throw new IllegalStateException("Illegal call"); } // TODO: FILL IN LOGIC }}
You can find some structural patterns in the above code. When you design with security in mind, you should explicitly enforce each contract in your component. Security is only as strong as its weakest link. Only use a component when you are sure it has been fully initialized. After it is destroyed, never use it again. I put this logic here because you would do it the same way when writing your own classes.
Component instantiation and management components
In order for you to understand how the container/component relationship works, we will first discuss the manual way of managing components. Next we'll discuss how Avalon's Excalibur component architecture hides complexity from you. You'll still find times when you'd rather manage the components yourself. But most of the time, Excalibur has the power and flexibility to meet your needs. The Manual Method
All Avalon components are created somewhere. The code that creates the component is the container for the component. The container is responsible for managing the life cycle of components from construction to destruction. The container can have a static "main" method that can be called from the command line, or it can be another container. When you design your container, remember the pattern of reverse control. Information and method calls will only flow from the container to the component. Subversion of Control
Subversion of Control is the anti-pattern of reverse control. Subversion control is achieved when you pass a reference to a container to a component. This is also the case when you let a component manage its own lifecycle. Code that operates in this way should be considered defective. When you mix container/component relationships, their interactions make the system difficult to debug and audit for security.
In order to manage child components, you need to keep references to them throughout their lifetime. Before the container and other components can use the subcomponent, it must complete initialization. For our DocumentRepository, the code might look like this: class ContainerComponent implements Component, Initializable, Disposable{ DocumentRepository docs = new DatabaseDocumentRepository(); GuardianComponent guard = new DocumentGuardianComponent(); DefaultComponentManager manager = new DefaultComponentManager(); public void initialize() throws Exception { Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); this.docs.enableLogging( docLogger.childLogger( "repository" ) ); this.guard.enableLogging( docLogger .childLogger( "security" ) ); DefaultConfiguration pool = new DefaultConfiguration("dbpool"); pool.setValue("main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this. manager.addComponent( DocumentRepository.ROLE, this.docs ); this.manager.addComponent( GuardianComponent.ROLE, this.guard ); this.docs.compose( this.manager ); this.guard.compose( this.manager ); this.docs.configure(conf); this.guard.initialize(); this.docs.initialize(); } public void dispose() { this.docs.dispose(); this.guard.dispose(); }}
For the sake of brevity, I have removed the explicit check from the above code. You can see that creating and managing components manually is a detailed job. If you forget to do a step in the component life cycle, you will find bugs. This also requires some deep knowledge of the component you're instantiating. Another approach is to add some methods to the ContainerComponent above to dynamically handle the initialization of the component.
Automated Autonomy
Developers are lazy by nature, so they spend time writing a special ComponentManager that acts as a container for all components in the system. This way, they don't have to have a deep understanding of the interfaces of all the components in the system. This can be a frustrating task. The developers of Avalon have created such a monster. Avalon Excalibur's component architecture includes a ComponentManager, which is controlled through XML configuration files. There is a trade-off when you hand over the responsibility of managing components to Excalibur's ComponentManager. You give up fine-grained control over which components are included in the CompomentManager. However, if your system is fairly large, you may find manual control to be a frustrating endeavor. In this case, for the sake of system stability, it is best to centrally manage all components in the system from one place. Since there are different levels of integration with Excalibur's component architecture, we'll start with the lowest level. Excalibur has a set of ComponentHandler objects that serve as independent containers for each type of component. They manage the entire lifecycle of your components. Let's introduce the concept of lifestyle interfaces. A survival interface describes how the system treats a component. Since the way components live will have an impact on system operation, we need to discuss the implications of some of the current ways of living: · org.apache.avalon.framework.thread.SingleThreadedo is not thread-safe or reusable. o If no other survival mode interface is specified, the system will consider this one. o Each time a component is requested, a brand new instance will be created. o Instance creation and initialization are deferred until the component is requested. · The org.apache.avalon.framework.thread.Threadsafeo component is fully reentrant and complies with all thread-safe principles. o The system creates an instance, and access to it is shared by all Composable components. o The creation and initialization of the instance is completed when the ComponentHandler is created. · org.apache.avalon.excalibur.pool.Poolableo is not thread-safe, but is fully reusable. o Create a set of instances and put them in the pool. When the Composable component requests it, the system provides an available one. o The creation and initialization of the instance is completed when the ComponentHandler is created. The ComponentHandler interface is very simple to handle. You initialize the constructor through Java classes, Configuration objects, ComponentManager objects, Context objects, and RoleManager objects. If you know your component won't need one of the above, you can upload a null in its place. After this, when you need a reference to the component, you call the "get" method. When you're done, you call the "put" method to return the component to the ComponentHandler. The following code makes it easier for us to understand this. class ContainerComponent implements Component, Initializable, Disposable{ ComponentHandler docs = null; ComponentHandler guard = null; DefaultComponentManager manager = new DefaultComponentManager(); public void initialize() throws Exception { DefaultConfiguration pool = new DefaultConfiguration("dbpool"); pool.setValue( "main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this.docs.configure(conf); this.docs = ComponentHandler.getComponentHandler( DatabaseDocumentRepository.class, conf, this.manager , null, null); this.guard = ComponentHandler.getComponentHandler( DocumentGuardianComponent.class, null, this.manager, null, null); Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); this .docs.enableLogging( docLogger.childLogger( "repository" ) ); this.guard.enableLogging( docLogger.childLogger( "security" ) ); this.manager.addComponent(DocumentRepository.ROLE, this.docs); this.manager. addComponent(GuardianComponent.ROLE, this.guard); this.guard.initialize(); this.docs.initialize(); } public void dispose() { this.docs.dispose(); this.guard.dispose(); }}
Here, we only missed a few lines of code. We still created the Configuration object manually, set up the Logger, and still had to initialize and destroy the ComponentHandler object. What we do here is just to prevent being affected by interface changes. You may find it beneficial to code this way. Excalibur goes one step further. Most complex systems have some configuration files. They allow administrators to adjust critical configuration information. Excalibur can read configuration files in the following formats and create system components from them. false < ;driver>org.gjt.mm.mysql.Driver jdbc:mysql:localhost/mydb test test < ;/component-instance> false org.gjt.mm.mysql.Driver jdbc:mysql:localhost/myotherdb test test documents security
The root element can be any specified. You'll notice that we've already defined some components. We have the familiar DocumentRepository and GuardianComponent classes, as well as some Excalibur DataSourceComponent classes. Moreover, now we have some specific configuration information for the Guardian component. In order to read these systems into your system, the Avalon framework provides you with some conveniences: DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();Configuration systemConf = builder.buildFromFile("/path/to/file.xconf");
This It does simplify our previous code for manually building configuration elements, and it limits the information we need to know explicitly when programming. Let's take another look at the Container class to see if we really saved something. Remember that we specified 5 components (the ComponentSelector counts as one component), and the configuration information for each component. class ContainerComponent implements Component, Initializable, Disposable { ExcaliburComponentManager manager = new ExcaliburComponentManager(); public void initialize() throws Exception { DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); Configuration sysConfig = builder.buildFromFile("./conf/system.xconf") ; this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") ); this.manager.contextualize( new DefaultContext() ); this.manager.configure( sysConfig ); this.manager.initialize(); } public void dispose() { this.manager.dispose(); }}
Isn’t it surprising? We initialized more than twice the number of components with more than double the amount of code (6 lines of code instead of 13). This configuration file has a drawback, it looks a little crazy, but it minimizes the amount of code you need to write. There's a lot of activity happening behind the scenes of ExcaliburComponentManager. For each "component" element in the configuration file, Excalibur creates a ComponentHandler for each class entry and establishes a corresponding relationship with the role. The "component" element and all its child elements are configurations for the component. When the component is an ExcaliburComponentSelector, Excalibur will read each "component-instance" element and perform the same type of operation as before, this time establishing a corresponding relationship with the hint entry. Make the configuration file look better
We can use aliases to change the appearance of the configuration file. Excalibur uses a RoleManager to provide aliases for the configuration system. RoleManager can be a class you create specially, or you can use DefaultRoleManager and pass in a Configuration object. If I use DefaultRoleManager, I will hide the role configuration file and other parts of the system in a jar file. This is because character profiles will only be changed by developers. The following is the RoleManager interface: interface RoleManager{ String getRoleForName( String shorthandName ); String getDefaultClassNameForRole( String role ); String getDefaultClassNameForHint( String hint, String shorthand );}
Let’s take a look at how Excalibur is used in our framework RoleManager. First, Excalibur loops through all child elements of the root element. This includes all "component" elements, but this time Excalibur doesn't recognize the element name, it asks the RoleManager what role we will use for this component. If RoleManager returns null, the element and all its child elements are ignored. Next, Excalibur derives the class name from the role name. The final approach is to dynamically map class names to subtypes of ComponentSelector. Excalibur provides a default implementation of RoleManager, which uses an XML configuration file. Tags are pretty simple and hide all the additional information you don't want administrators to see. < hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSourceComponent"/>
In order to use RoleManager, you need to change The "initialization" method in the container class. You will use the configuration builder to construct a Configuration tree from this file. Remember, if you plan to use a RoleManager, you must call the "setRoleManager" method before calling the "configure" method. To show how you can get this XML file from the class loader, I will show the technique below: DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();Configuration sysConfig = builder.buildFromFile("./conf/system.xconf");Configuration roleConfig = builder.build( this.getClass().getClassLoader() .getResourceAsStream("/org/apache/bizserver/docs/document.roles"));DefaultRoleManager roles = new DefaultRoleManager();roles.enableLogging(Hierarchy.getDefaultHierarchy ().getLoggerFor("document.roles"));roles.configure(roleConfig);this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") );this.manager.contextualize( new DefaultContext() );this.manager.setRoleManager( roles );this.manager.configure( sysConfig );this.manager.initialize();
Now that we have added 6 lines of code, we need to take a look at the benefits it brings. Our final configuration file can be written like this: false org.gjt.mm.mysql.Driver jdbc:mysql:localhost/mydb testtest false< ;/auto-commit> org.gjt.mm.mysql.Driver jdbc:mysql:localhost/myotherdb test documents security
As you can see, this file is much more readable than the previous file. Now we can add any number of components to the system without writing more code to support them.

Using the Component
Now that we have created our components, we will use them. Regardless of how the component is initialized and managed, the way you access the component is the same. You must implement the Composable interface in order to get a reference from the ComponentManager. The ComponentManager holds references to all the components you need. For the sake of convenience of discussion, we will assume that the ComponentManager we get is configured according to the final configuration file in the previous section. This means we have a Repository, a Guardian, and two DataSources. Principles of Using Component Management Infrastructure
Component Management Infrastructure requires that you release the components to which you get references. The reason for this restriction is to properly manage component resources. The ComponentManager is designed taking into account that you have different types of components for specific roles. Another unique aspect of ComponentSelector is that it is also designed as a component. This allows us to get a ComponentSelector from a ComponentManager. There are two legal ways to handle references to external components. You can get references during initialization and release them on destruction. You can also put the code that handles components in try/catch/finally blocks. Both methods have advantages and disadvantages. Initialization and Disposal Approach
class MyClass implements Component, Composable, Disposable{ ComponentManager manager; Guardian myGuard; /*** Obtain a reference to a guard and keep the reference to * the ComponentManager.*/ public void compose(ComponentManager manager) throws ComponentException { if (this.manager == null) { this.manager = manager; myGuard = (Guardian) this.manager.lookup(Guardian.ROLE); } } /*** This is the method that uses the Guardian.*/ public void myMethod() throws SecurityException { this.myGuard.checkPermission(new BasicPermission(" test")); } /*** Get rid of our references*/ public void dispose() { this.manager.release(this.myGuard); this.myGuard = null; this.manager = null; }}
From the example As you can see from the code, it's easy to do this. When the object first receives the ComponentManager, it obtains a reference to the Guardian component. If you can ensure that the Guardian component is thread-safe (implements the ThreadSafe interface), then you only need to do these things. Unfortunately, you can't guarantee this in the long run. In order to manage resources correctly, we must release the reference to the component after we are done with it. That's why we keep a reference to the ComponentManager. The main disadvantage of this approach is when dealing with components in a component pool. A reference to a component keeps the component alive. If the object has a short lifetime, this may not be a problem; but if the object is a component managed by the Excalibur component management architecture, its lifetime will continue as long as there are references to it. This means that we actually turn the component pool into a component factory. The main benefit of this approach is that the code to get and release the component is clear. You don't have to understand the exception handling code. Another subtle difference is that you tie the existence of the Guardian with the ability to initialize this object. Once an exception is thrown during the initialization phase of an object, you have to assume that the object is not a valid object. Sometimes you want the program to fail if the required component does not exist, then this is not a problem. When designing components, you really need to be aware of this implicit meaning.
Exception Handling Approach
class MyClass implements Composable, Disposable{ ComponentManager manager; /*** Obtain a reference to a guard and keep the reference to * the ComponentManager.*/ public void compose(ComponentManager manager) throws ComponentException { if (this.manager == null) { this.manager = manager; } } /*** This is the method that gets the Guardian.*/ public void myMethod() throws SecurityException { Guardian myGuard = null; try { myGuard = (Guardian) this.manager.lookup(Guardian.ROLE); this.criticalSection(myGuard) ; } catch (ComponentException ce) { throw new SecurityException(ce.getMessage()); } catch (SecurityException se) { throw se; } finally { if (myGuard != null) { this.manager.release(myGuard); } } } /*** Perform critical part of code.*/ public void criticalSection(Guardian myGuard) throws SecurityException { myGuard.checkPermission(new BasicPermission("test")); }}
As you can see, this code is a bit complex. In order to understand it, you need to understand exception handling. This may not be a problem since the vast majority of Java developers know how to handle exceptions. This way, you don't have to worry too much about how the component lives, because it's released as soon as we no longer need it. The main disadvantage of this approach is the addition of exception handling code, which is more complex. In order to minimize the complexity and make the code easier to maintain, we extract the working code and put it in another method. Please remember that in the try block, we can get as many references to the component as we want. The main benefit of this approach is that you can manage component references more efficiently. Similarly, if you are using ThreadSafe components, there is no real difference, but if you are using components from the component pool, there is a difference. There is a slight overhead in getting a new reference to a component every time you use it, but the likelihood of being forced to create a new component instance is greatly reduced. Like the way initialization and destruction work, there is a subtle difference that you must understand. Exception handling is done in a way that the program will not fail during initialization if the manager cannot find the component. As mentioned before, this is not entirely without merit. Many times, you expect a certain component to be present, but the program does not need to fail if the expected component is not present.

Get a component from ComponentSelector
For most operations, you only need to use ComponentManager. Now that we've decided that we need multiple instances of DataSourceComponent, we need to know how to get the one we want. ComponentSelector is a little more complicated than ComponentManagers because there are hints to get the desired reference during processing. A component belongs to a specific role, as we have already made clear. However, sometimes we need to choose one from multiple components of a character. ComponentSelector uses an arbitrary object as a hint. Most of the time, this object is a String, although you may want to use a Locale object to obtain a correctly internationalized component. In the system we have set up, we choose to use a string to select the correct instance of DataSourceComponent. We even gave ourselves a Configuration element to specify what string is needed in order to get the correct component. This is a good practice to follow because it makes system administration easier. This makes it easier for system administrators to see references to other components than having to remember these magical configuration values. Conceptually, there is no difference between getting a component from a ComponentSelector and getting a component from a ComponentManager. You just have one more step. Remember that ComponentSelector is also a component. When you look up the ComponentSelect's role, the ComponentManager will prepare the ComponentSelector component and return it to you. Then you need to select the component through it. To illustrate this, I'll expand on the exception handling code discussed earlier. public void myMethod() throws Exception{ ComponentSelector dbSelector = null; DataSourceComponent datasource = null; try { dbSelector = (ComponentSelector) this.manager.lookup(DataSourceComponent.ROLE + "Selector"); datasource = (DataSourceComponent) dbSelector.select(this .useDb); this.process(datasource.getConnection()); } catch (Exception e) { throw e; } finally { if (datasource != null) { dbSelector.release(datasource); } if (dbSelector != null ) { this.manager.release(dbSelector); } }}
You can see that we get a reference to the ComponentSelector by using the role of the specified component. We followed the role naming convention mentioned earlier and added "Selector" as a suffix to the role name. You can use a static interface to handle all role names in the system to reduce the number of string concatenations in your code. It's also perfectly acceptable to do this. Next, we have to get a reference to the DataSource component from the ComponentSelector. Our example code assumes that we have obtained the required information from the Configuration object and placed it in a class variable named "useDb".

Excalibur's tool classes
The last section introduces you to several types of components and tool classes provided by Apache Avalon Excalibur. These tool classes are robust and can be used in actual production systems. We have an informal hierarchical project called "Scratchpad" where we work out the implementation details of potential new tool classes. The tools in Scratchpad vary in quality, and there is no guarantee that they will be used in the same way, although you may find them useful. Command Line Interface (CLI)
The CLI tool class is used in some projects, including Avalon Phoenix and Apache Cocoon, to process command line parameters. It provides a mechanism for printing help information, and can handle parameter options in the form of short or long names.
Collection tool class
Collection utility classes provide some enhancements to the JavaTM Collections API. These enhancements include the ability to find intersections between two lists and a PriorityQueue, which is an enhancement to Stack that allows object priority changes in a simple first-in-last-out Stack implementation.
Component Management
We have discussed the usage of this aspect before. This is the most complex monster in Excalibur, but it offers a lot of functionality in just a few classes. In addition to the simple SingleThreaded or ThreadSafe management types, there is also a Poolable type. If a component implements Excalibur's Poolable interface instead of the SingleThreaded interface, then it will maintain a pool of components and reuse instances. Most of the time this works fine. In the case where a few components cannot be reused, the SingleThreaded interface is used.
LogKit Management
The Avalon development team realized that many people needed a simple mechanism to create complex log target hierarchies. Based on similar ideas to RoleManager, the team developed LogKitManager, which can be used by the Excalibur Component Management system mentioned earlier. Based on the "logger" attribute, it will give corresponding Logger objects for different components.
Threading tool classes
The concurrent package provides some classes to assist multi-threaded programming: Lock (implementation of mutex), DjikstraSemaphore, ConditionalEvent, and ThreadBarrier.
Datasources
This is based on javax.sql.DataSource Class design, but simplified. There are two implementations of DataSourceComponent: one that explicitly uses the JDBC connection pool, and the other that uses the J2EE application server's javax.sql.DataSource class.
Input/Output (IO) tool class
The IO tool class provides some FileFilter classes and File and IO related tool classes.
Pool implementation
Pool implements a pool that can be used in various situations. One of the implementations is very fast, but can only be used in one thread. It is good to implement FlyWeight mode. There is also a DefaultPool, which does not manage the number of objects in the pool. SoftResourceManagingPool determines whether a threshold is exceeded when the object is returned. If it exceeds, the object is "retired". Finally, HardResourceManagingPool throws an exception when you reach the maximum number of objects. The next three pools are all ThreadSafe.
Property tool class
Property tool class is used together with the Context object. They allow you to extend "variables" in a Resolvable object. Here's how it works: "${resource}" will look for a value in the Context named "resource" and replace the symbol with that value.

Conclusion

Avalon has stood the test of time and is ready for you to use. The evidence provided in this section can help convince yourself and others that using a mature framework is better than creating one yourself.
You may already be convinced, but need some help convincing your colleagues that Avalon is the right choice. Maybe you need to convince yourself too. Regardless, this chapter will help you organize your thoughts and provide some convincing evidence. We need to fight the Fear, Uncertainty, and Doubt (FUD) of the open source model. Regarding the evidence for the effectiveness of open source, I recommend reading Eric S. Raymond's excellent treatment of the subject N400017. Whatever you think of his views, his articles are compiled into a book called The Cathedral and the Bazaar Information will be provided to promote acceptance of open source in general.

Avalon Works
Our bottom line is that Avalon accomplishes what it was originally designed to accomplish. Rather than introducing new concepts and ideas, Avalon uses some time-tested concepts and standardizes them. The latest concept to influence Avalon design is the Separation of Concerns pattern, which was proposed around 1995. Even then, separation of considerations was a formalized approach to systems analysis techniques. Avalon's user base is in the hundreds. Some projects such as Apache Cocoon, Apache JAMES, and Jesktop are built on Avalon. The developers of these projects are users of the Avalon Framework. Because Avalon has such a large number of users, it is well tested. Designed by the best
The authors of Avalon recognize that we are not the only group of experts in server-side computing. We used concepts and ideas from other people's research. We respond to feedback from our users. Avalon was not only designed by the five developers mentioned above, they brought the ideas of reverse control, separation considerations and component-oriented programming and designed it. The great thing about open source is that the results are a fusion of the best ideas and the best code. Avalon went through a phase of testing ideas and rejecting some because there were better solutions. You can take the knowledge gained by the Avalon development team and apply it to your own systems. You can use Excalibur's predefined components in your projects, and they are tested to run without errors under heavy load.
Compatibility License
The Apache Software License (ASL) is compatible with any other known license. The biggest known exceptions are the GNU Public License (GPL) and the Lesser GNU Public License (LGPL). The important thing is that ASL is quite friendly to collaborative development and doesn't force you to release source code if you don't want to. The highly respected HTTP server from the Apache Software Foundation is licensed under the same license.
Clustered R&D
Most Avalon users contribute in some way. This spreads the development, debugging and documentation workload over a number of users. It also shows that Avalon's code has undergone more extensive peer review than is possible in corporate development. Moreover, Avalon users support Avalon. Although open source projects typically don't have a help desk or phone support for hot money, we do have a mailing list. Many of your questions can be answered quickly via the mailing list, faster than some support hotlines.
Simplified Analysis and Design
Developing based on Avalon helps developers achieve a state of mind. In this state of mind, the developer's work focuses on how to discover components and services. Now that the details about the lifetime of components and services have been analyzed and designed, developers only need to choose what they need. It is important to point out that Avalon did not begin by replacing traditional object-oriented analysis and design, but by enhancing it. You're still using the same techniques you used before, but now you have a set of tools to implement your designs faster.

Avalon is ready
Avalon Framework, Avalon Excalibur, and Avalon LogKit are ready for you to use. They've matured and are only getting better. Although Avalon Phoenix and Avalon Cornerstone are under intensive development, the code you write based on them will work in the future with only minor changes.

The above is the detailed content of Detailed introduction to Avalonjs. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn