반응형

Javascript Closures

Introduction

Closure
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

Closures are one of the most powerful features of ECMAScript (javascript) but they cannot be property exploited without understanding them. They are, however, relatively easy to create, even accidentally, and their creation has potentially harmful consequences, particularly in some relatively common web browser environments. To avoid accidentally encountering the drawbacks and to take advantage of the benefits they offer it is necessary to understand their mechanism. This depends heavily on the role of scope chains in identifier resolution and so on the resolution of property names on objects.

The simple explanation of a Closure is that ECMAScript allows inner functions; function definitions and function expressions that are inside the function bodes of other functions. And that those inner functions are allowed access to all of the local variables, parameters and declared inner functions within their outer function(s). A closure is formed when one of those inner functions is made accessible outside of the function in which it was contained, so that it may be executed after the outer function has returned. At which point it still has access to the local variables, parameters and inner function declarations of its outer function. Those local variables, parameter and function declarations (initially) have the values that they had when the outer function returned and may be interacted with by the inner function.

Unfortunately, properly understanding closures requires an understanding of the mechanism behind them, and quite a bit of technical detail. While some of the ECMA 262 specified algorithms have been brushed over in the early part of the following explanation, much cannot be omitted or easily simplified. Individuals familiar with object property name resolution may skip that section but only people already familiar with closures can afford to skip the following sections, and they can stop reading now and get back to exploiting them.

The Resolution of Property Names on Objects

ECMAScript recognises two categories of object, "Native Object" and "Host Object" with a sub-category of native objects called "Built-in Object" (ECMA 262 3rd Ed Section 4.3). Native objects belong to the language and host objects are provided by the environment, and may be, for example, document objects, DOM nodes and the like.

Native objects are loose and dynamic bags of named properties (some implementations are not that dynamic when it comes to the built in object sub-category, though usually that doesn't matter). The defined named properties of an object will hold a value, which may be a reference to another Object (functions are also Objects in this sense) or a primitive value: String, Number, Boolean, Null or Undefined. The Undefined primitive type is a bit odd in that it is possible to assign a value of Undefined to a property of an object but doing so does not remove that property from the object; it remains a defined named property, it just holds the value undefined.

The following is a simplified description of how property values are read and set on objects with the internal details brushed over to the greatest extent possible.

Assignment of Values

Named properties of objects can be created, or values set on existing named properties, by assigning a value to that named property. So given:-

var objectRef = new Object(); //create a generic javascript object.

A property with the name "testNumber" can be created as:-

objectRef.testNumber = 5;
/* - or:- */
objectRef["testNumber"] = 5;

The object had no "testNumber" property prior to the assignment but one is created when the assignment is made. Any subsequent assignment does not need to create the property, it just re-sets its value:-

objectRef.testNumber = 8;
/* - or:- */
objectRef["testNumber"] = 8;

Javascript objects have prototypes that can themselves be objects, as will be described shortly, and that prototype may have named properties. But this has no role in assignment. If a value is assigned and the actual object does not have a property with the corresponding name a property of that name is created and the value is assigned to it. If it has the property then its value is re-set.

Reading of Values

It is in reading values from object properties that prototypes come into play. If an object has a property with the property name used in the property accessor then the value of that property is returned:-

/* Assign a value to a named property. If the object does not have a
   property with the corresponding name prior to the assignment it
   will have one after it:-
*/
objectRef.testNumber = 8;

/* Read the value back from the property:- */

var val = objectRef.testNumber;
/* and  - val - now holds the value 8 that was just assigned to the
   named property of the object. */
   

But all objects may have prototypes, and prototypes are objects so they, in turn, may have prototypes, which may have prototypes, and so on forming what is called the prototype chain. The prototype chain ends when one of the objects in the chain has a null prototype. The default prototype for the Object constructor has a null prototype so:-

var objectRef = new Object(); //create a generic javascript object.

Creates an object with the prototype Object.prototype that itself has a null prototype. So the prototype chain for objectRef contains only one object: Object.prototype. However:-

/* A "constructor" function for creating objects of a -
   MyObject1 - type.
*/
function MyObject1(formalParameter){
    /* Give the constructed object a property called - testNumber - and
       assign it the value passed to the constructor as its first
       argument:-
    */
    this.testNumber = formalParameter;
}

/* A "constructor" function for creating objects of a -
   MyObject2 - type:-
*/
function MyObject2(formalParameter){
   /* Give the constructed object a property called - testString -
      and assign it the value passed to the constructor as its first
      argument:-
    */
    this.testString = formalParameter;
}

/* The next operation replaces the default prototype associated with
   all MyObject2 instances with an instance of MyObject1, passing the
   argument - 8 - to the MyObject1 constructor so that its -
   testNumber - property will be set to that value:-
*/
MyObject2.prototype = new MyObject1( 8 );

/* Finally, create an instance of - MyObject2 - and assign a reference
   to that object to the variable - objectRef - passing a string as the
   first argument for the constructor:-
*/

var objectRef = new MyObject2( "String_Value" );

The instance of MyObject2 referred to by the objectRef variable has a prototype chain. The first object in that chain is the instance of MyObject1 that was created and assigned to the prototype property of the MyObject2 constructor. The instance of MyObject1 has a prototype, the object that was assigned to the function MyObject1's prototype property by the implementation. That object has a prototype, the default Object prototype that corresponds with the object referred to by Object.prototype. Object.prototype has a null prototype so the prototype chain comes to an end at this point.

When a property accessor attempts to read a named property form the object referred to by the variable objectRef the whole prototype chain can enter into the process. In the simple case:-

var val = objectRef.testString;

- the instance of MyObject2 referred to by objectRef has a property with the name "testString" so it is the value of that property, set to "String_Value", that is assigned to the variable val. However:-

var val = objectRef.testNumber;

- cannot read a named property form the instance of MyObject2 itself as it has no such property but the variable val is set to the value of 8 rather than undefined because having failed to find a corresponding named property on the object itself the interpreter then examines the object that is its prototype. Its prototype is the instance of MyObject1 and it was created with a property named "testNumber" with the value 8 assigned to that property, so the property accessor evaluates as the value 8. Neither MyObject1 or MyObject2 have defined a toString method, but if a property accessor attempts to read the value of a toString property from objectRef:-

var val = objectRef.toString;

- the val variable is assigned a reference to a function. That function is the toString property of Object.prototype and is returned because the process of examining the prototype of objectRef, when objectRef turns out not to have a "toString" property, is acting on an object, so when that prototype is found to lack the property its prototype is examined in turn. Its prototype is Object.prototype, which does have a toString method so it is a reference to that function object that is returned.

Finally:-

var val = objectRef.madeUpProperty;

- returns undefined, because as the process of working up the prototype chain finds no properties on any of the object with the name "madeUpPeoperty" it eventually gets to the prototype of Object.prototype, which is null, and the process ends returning undefined.

The reading of named properties returns the first value found, on the object or then from its prototype chain. The assigning of a value to a named property on an object will create a property on the object itself if no corresponding property already exists.

This means that if a value was assigned as objectRef.testNumber = 3 a "testNumber" property will be created on the instance of MyObject2 itself, and any subsequent attempts to read that value will retrieve that value as set on the object. The prototype chain no longer needs to be examined to resolve the property accessor, but the instance of MyObject1 with the value of 8 assigned to its "testNumber" property is unaltered. The assignment to the objectRef object masks the corresponding property in its prototype chain.

Note: ECMAScript defines an internal [[prototype]] property of the internal Object type. This property is not directly accessible with scripts, but it is the chain of objects referred to with the internal [[prototype]] property that is used in property accessor resolution; the object's prototype chain. A public prototype property exists to allow the assignment, definition and manipulation of prototypes in association with the internal [[prototype]] property. The details of the relationship between to two are described in ECMA 262 (3rd edition) and are beyond the scope of this discussion.

Identifier Resolution, Execution Contexts and scope chains

The Execution Context

An execution context is an abstract concept used by the ECMSScript specification (ECMA 262 3rd edition) to define the behaviour required of ECMAScript implementations. The specification does not say anything about how execution contexts should be implemented but execution contexts have associated attributes that refer to specification defined structures so they might be conceived (and even implemented) as objects with properties, though not public properties.

All javascript code is executed in an execution context. Global code (code executed inline, normally as a JS file, or HTML page, loads) gets executed in global execution context, and each invocation of a function (possibly as a constructor) has an associated execution context. Code executed with the eval function also gets a distinct execution context but as eval is never normally used by javascript programmers it will not be discussed here. The specified details of execution contexts are to be found in section 10.2 of ECMA 262 (3rd edition).

When a javascript function is called it enters an execution context, if another function is called (or the same function recursively) a new execution context is created and execution enters that context for the duration of the function call. Returning to the original execution context when that called function returns. Thus running javascript code forms a stack of execution contexts.

When an execution context is created a number of things happen in a defined order. First, in the execution context of a function, an "Activation" object is created. The activation object is another specification mechanism. It can be considered as an object because it ends up having accessible named properties, but it is not a normal object as it has no prototype (at least not a defined prototype) and it cannot be directly referenced by javascript code.

The next step in the creation of the execution context for a function call is the creation of an arguments object, which is an array-like object with integer indexed members corresponding with the arguments passed to the function call, in order. It also has length and callee properties (which are not relevant to this discussion, see the spec for details). A property of the Activation object is created with the name "arguments" and a reference to the arguments object is assigned to that property.

Next the execution context is assigned a scope. A scope consists of a list (or chain) of objects. Each function object has an internal [[scope]] property (which we will go into more detail about shortly) that also consists of a list (or chain) of objects. The scope that is assigned to the execution context of a function call consists of the list referred to by the [[scope]] property of the corresponding function object with the Activation object added at the front of the chain (or the top of the list).

Then the process of "variable instantiation" takes place using an object that ECMA 262 refers to as the "Variable" object. However, the Activation object is used as the Variable object (note this, it is important: they are the same object). Named properties of the Variable object are created for each of the function's formal parameters, and if arguments to the function call correspond with those parameters the values of those arguments are assigned to the properties (otherwise the assigned value is undefined). Inner function definitions are used to create function objects which are assigned to properties of the Variable object with names that correspond to the function name used in the function declaration. The last stage of variable instantiation is to create named properties of the Variable object that correspond with all the local variables declared within the function.

The properties created on the Variable object that correspond with declared local variables are initially assigned undefined values during variable instantiation, the actual initialisation of local variables does not happen until the evaluation of the corresponding assignment expressions during the execution of the function body code.

It is the fact that the Activation object, with its arguments property, and the Variable object, with named properties corresponding with function local variables, are the same object, that allows the identifier arguments to be treated as if it was a function local variable.

Finally a value is assigned for use with the this keyword. If the value assigned refers to an object then property accessors prefixed with the this keyword reference properties of that object. If the value assigned (internally) is null then the this keyword will refer to the global object.

The global execution context gets some slightly different handling as it does not have arguments so it does not need a defined Activation object to refer to them. The global execution context does need a scope and its scope chain consists of exactly one object, the global object. The global execution context does go through variable instantiation, its inner functions are the normal top level function declarations that make up the bulk of javascript code. The global object is used as the Variable object, which is why globally declared functions become properties of the global object. As do globally declared variables.

The global execution context also uses a reference to the global object for the this object.

scope chains and [[scope]]

The scope chain of the execution context for a function call is constructed by adding the execution context's Activation/Variable object to the front of the scope chain held in the function object's [[scope]] property, so it is important to understand how the internal [[scope]] property is defined.

In ECMAScript functions are objects, they are created during variable instantiation from function declarations, during the evaluation of function expressions or by invoking the Function constructor.

Function objects created with the Function constructor always have a [[scope]] property referring to a scope chain that only contains the global object.

Function objects created with function declarations or function expressions have the scope chain of the execution context in which they are created assigned to their internal [[scope]] property.

In the simplest case of a global function declaration such as:-

function exampleFunction(formalParameter){
    ...   // function body code
}

- the corresponding function object is created during the variable instantiation for the global execution context. The global execution context has a scope chain consisting of only the global object. Thus the function object that is created and referred to by the property of the global object with the name "exampleFunction" is assigned an internal [[scope]] property referring to a scope chain containing only the global object.

A similar scope chain is assigned when a function expression is executed in the global context:-

var exampleFuncRef = function(){
    ...   // function body code
}

- except in this case a named property of the global object is created during variable instantiation for the global execution context but the function object is not created, and a reference to it assigned to the named property of the global object, until the assignment expression is evaluated. But the creation of the function object still happens in the global execution context so the [[scope]] property of the created function object still only contains the global object in the assigned scope chain.

Inner function declarations and expressions result in function objects being created within the execution context of a function so they get more elaborate scope chains. Consider the following code, which defines a function with an inner function declaration and then executes the outer function:-

function exampleOuterFunction(formalParameter){
    function exampleInnerFuncitonDec(){
        ... // inner function body
    }
    ...  // the rest of the outer function body.
}

exampleOuterFunction( 5 );

The function object corresponding with the outer function declaration is created during variable instantiation in the global execution context so its [[scope]] property contains the one item scope chain with only the global object in it.

When the global code executes the call to the exampleOuterFunction a new execution context is created for that function call and an Activation/Variable object along with it. The scope of that new execution context becomes the chain consisting of the new Activation object followed by the chain refereed to by the outer function object's [[scope]] property (just the global object). Variable instantiation for that new execution context results in the creation of a function object that corresponds with the inner function definition and the [[scope]] property of that function object is assigned the value of the scope from the execution context in which it was created. A scope chain that contains the Activation object followed by the global object.

So far this is all automatic and controlled by the structure and execution of the source code. The scope chain of the execution context defines the [[scope]] properties of the function objects created and the [[scope]] properties of the function objects define the scope for their execution contexts (along with the corresponding Activation object). But ECMAScript provides the with statement as a means of modifying the scope chain.

The with statement evaluates an expression and if that expression is an object it is added to the scope chain of the current execution context (in front of the Activation/Variable object). The with statement then executes another statement (that may itself be a block statement) and then restores the execution context's scope chainto what it was before.

A function declaration could not be affected by a with statement as they result in the creation of function objects during variable instantiation, but a function expression can be evaluated inside a with statement:-

/* create a global variable - y - that refers to an object:- */
var y = {x:5}; // object literal with an - x - property
function exampleFuncWith(){
    var z;
    /* Add the object referred to by the global variable - y - to the
       front of he scope chain:-
    */
    with(y){
        /* evaluate a function expression to create a function object
           and assign a reference to that function object to the local
           variable - z - :-
        */
        z = function(){
            ... // inner function expression body;
        }
    }
    ... 
}

/* execute the - exampleFuncWith - function:- */
exampleFuncWith();

When the exampleFuncWith function is called the resulting execution context has a scope chain consisting of its Activation object followed by the global object. The execution of the with statement adds the object referred to by the global variable y to the front of that scope chain during the evaluation of the function expression. The function object created by the evaluation of the function expression is assigned a [[scope]] property that corresponds with the scope of the execution context in which it is created. A scope chain consisting of object y followed by the Activation object from the execution context of the outer function call, followed by the global object.

When the block statement associated with the with statement terminates the scope of the execution context is restored (the y object is removed), but the function object has been created at that point and its [[scope]] property assigned a reference to a scope chain with the y object at its head.

Identifier Resolution

Identifiers are resolved against the scope chain. ECMA 262 categorises this as a keyword rather than an identifier, which is not unreasonable as it is always resolved dependent on the this value in the execution context in which it is used, without reference to the scope chain.

Identifier resolution starts with the first object in the scope chain. It is checked to see if it has a property with a name that corresponds with the identifier. Because the scope chain is a chain of objects this checking encompasses the prototype chain of that object (if it has one). If no corresponding value can be found on the first object in the scope chain the search progresses to the next object. And so on until one of the objects in the chain (or one of its prototypes) has a property with a name that corresponds with the identifier or the scope chain is exhausted.

The operation on the identifier happens in the same way as the use of property accessors on objects described above. The object identified in the scope chain as having the corresponding property takes the place of the object in the property accessor and the identifier acts as a property name for that object. The global object is always at the end of the scope chain.

As execution contexts associated with function calls will have the Activation/Variable object at the front of the chain, identifiers used in function bodies are effectively first checked to see whether they correspond with formal parameters, inner function declaration names or local variables. Those would be resolved as named properties of the Activation/Variable object.

Closures

Automatic Garbage Collection

ECMAScript uses automatic garbage collection. The specification does not define the details, leaving that to the implementers to sort out, and some implementations are known to give a very low priority to their garbage collection operations. But the general idea is that if an object becomes un-referable (by having no remaining references to it left accessible to executing code) it becomes available for garbage collection and will at some future point be destroyed and any resources it is consuming freed and returned to the system for re-use.

This would normally be the case upon exiting an execution context. The scope chain structure, the Activation/Variable object and any objects created within the execution context, including function objects, would no longer be accessible and so would become available for garbage collection.

Forming Closures

A closure is formed by returning a function object that was created within an execution context of a function call from that function call and assigning a reference to that inner function to a property of another object. Or by directly assigning a reference to such a function object to, for example, a global variable, a property of a globally accessible object or an object passed by reference as an argument to the outer function call. e.g:-

function exampleClosureForm(arg1, arg2){
    var localVar = 8;
    function exampleReturned(innerArg){
        return ((arg1 + arg2)/(innerArg + localVar));
    }
    /* return a reference to the inner function defined as -
       exampleReturned -:-
    */
    return exampleReturned;
}

var globalVar = exampleClosureForm(2, 4);

Now the function object created within the execution context of the call to exampleClosureForm cannot be garbage collected because it is referred to by a global variable and is still accessible, it can even be executed with globalVar(n).

But something a little more complicated has happened because the function object now referred to by globalVar was created with a [[scope]] property referring to a scope chain containing the Activation/Variable object belonging to the execution context in which it was created (and the global object). Now the Activation/Variable object cannot be garbage collected either as the execution of the function object referred to by globalVar will need to add the whole scope chain from its [[scope]] property to the scope of the execution context created for each call to it.

A closure is formed. The inner function object has the free variables and the Activation/Variable object on the function's scope chain is the environment that binds them.

The Activation/Variable object is trapped by being referred to in the scope chain assigned to the internal [[scope]] property of the function object now referred to by the globalVar variable. The Activation/Variable object is preserved along with its state; the values of its properties. Scope resolution in the execution context of calls to the inner function will resolve identifiers that correspond with named properties of that Activation/Variable object as properties of that object. The value of those properties can still be read and set even though the execution context for which it was created has exited.

In the example above that Activation/Variable object has a state that represents the values of formal parameters, inner function definitions and local variables, at the time when the outer function returned (exited its execution context). The arg1 property has the value 2,the arg2 property the value 4, localVar the value 8 and an exampleReturned property that is a reference to the inner function object that was returned form the outer function. (We will be referring to this Activation/Variable object as "ActOuter1" in later discussion, for convenience.)

If the exampleClosureForm function was called again as:-

var secondGlobalVar = exampleClosureForm(12, 3);

- a new execution context would be created, along with a new Activation object. And a new function object would be returned, with its own distinct [[scope]] property referring to a scope chain containing the Activation object form this second execution context, with arg1 being 12 and arg2 being 3. (We will be referring to this Activation/Variable object as "ActOuter2" in later discussion, for convenience.)

A second and distinct closure has been formed by the second execution of exampleClosureForm.

The two function objects created by the execution of exampleClosureForm to which references have been assigned to the global variable globalVar and secondGlobalVar respectively, return the expression ((arg1 + arg2)/(innerArg + localVar)). Which applies various operators to four identifiers. How these identifiers are resolved is critical to the use and value of closures.

Consider the execution of the function object referred to by globalVar, as globalVar(2). A new execution context is created and an Activation object (we will call it "ActInner1"), which is added to the head of the scope chain referred to the [[scope]] property of the executed function object. ActInner1 is given a property named innerArg, after its formal parameter and the argument value 2 assigned to it. The scope chain for this new execution context is: ActInner1-> ActOuter1-> global object.

Identifier resolution is done against the scope chain so in order to return the value of the expression ((arg1 + arg2)/(innerArg + localVar)) the values of the identifiers will be determined by looking for properties, with names corresponding with the identifiers, on each object in the scope chain in turn.

The first object in the chain is ActInner1 and it has a property named innerArg with the value 2. All of the other 3 identifiers correspond with named properties of ActOuter1; arg1 is 2, arg2 is 4 and localVar is 8. The function call returns ((2 + 4)/(2 + 8)).

Compare that with the execution of the otherwise identical function object referred to by secondGlobalVar, as secondGlobalVar(5). Calling the Activation object for this new execution context "ActInner2", the scope chain becomes: ActInner2-> ActOuter2-> global object. ActInner2 returns innerArg as 5 and ActOuter2 returns arg1, arg2 and localVar as 12, 3 and 8 respectively. The value returned is ((12 + 3)/(5 + 8)).

Execute secondGlobalVar again and a new Activation object will appear at the front of the scope chain but ActOuter2 will still be next object in the chain and the value of its named properties will again be used in the resolution of the identifiers arg1, arg2 and localVar.

This is how ECMAScript inner functions gain, and maintain, access to the formal parameters, declared inner functions and local variables of the execution context in which they were created. And it is how the forming of a closure allows such a function object to keep referring to those values, reading and writing to them, for as long as it continues to exist. The Activation/Variable object from the execution context in which the inner function was created remains on the scope chain referred to by the function object's [[scope]] property, until all references to the inner function are freed and the function object is made available for garbage collection (along with any now unneeded objects on its scope chain).

Inner function may themselves have inner functions, and the inner functions returned from the execution of functions to form closures may themselves return inner functions and form closures of their own. With each nesting the scope chain gains extra Activation objects originating with the execution contexts in which the inner function objects were created. The ECMAScript specification requires a scope chain to be finite, but imposes no limits on their length. Implementations probably do impose some practical limitation but no specific magnitude has yet been reported. The potential for nesting inner functions seems so far to have exceeded anyone's desire to code them.

What can be done with Closures?

Strangely the answer to that appears to be anything and everything. I am told that closures enable ECMAScript to emulate anything, so the limitation is the ability to conceive and implement the emulation. That is a bit esoteric and it is probably better to start with something a little more practical.

Example 1: setTimeout with Function References

A common use for a closure is to provide parameters for the execution of a function prior to the execution of that function. For example, when a function is to be provided as the first argument to the setTimout function that is common in web browser environments.

setTimeout schedules the execution of a function (or a string of javascript source code, but not in this context), provided as its first argument, after an interval expressed in milliseconds (as its second argument). If a piece of code wants to use setTimeout it calls the setTimeout function and passes a reference to a function object as the first argument and the millisecond interval as the second, but a reference to a function object cannot provide parameters for the scheduled execution of that function.

However, code could call another function that returned a reference to an inner function object, with that inner function object being passed by reference to the setTimeout function. The parameters to be used for the execution of the inner function are passed with the call to the function that returns it. setTimout executes the inner function without passing arguments but that inner function can still access the parameters provided by the call to the outer function that returned it:-

function callLater(paramA, paramB, paramC){
    /* Return a reference to an anonymous inner function created
       with a function expression:-
    */
    return (function(){
        /* This inner function is to be executed with - setTimeout
           - and when it is executed it can read, and act upon, the
           parameters passed to the outer function:-
        */
        paramA[paramB] = paramC;
    });
}

...

/* Call the function that will return a reference to the inner function
   object created in its execution context. Passing the parameters that
   the inner function will use when it is eventually executed as
   arguments to the outer function. The returned reference to the inner
   function object is assigned to a local variable:-
*/
var functRef = callLater(elStyle, "display", "none");
/* Call the setTimeout function, passing the reference to the inner
   function assigned to the - functRef - variable as the first argument:-
*/
hideMenu=setTimeout(functRef, 500);

Example 2: Associating Functions with Object Instance Methods

There are many other circumstances when a reference to a function object is assigned so that it would be executed at some future time where it is useful to provide parameters for the execution of that function that would not be easily available at the time of execution but cannot be known until the moment of assignment.

One example might be a javascript object that is designed to encapsulate the interactions with a particular DOM element. It has doOnClick, doMouseOver and doMouseOut methods and wants to execute those methods when the corresponding events are triggered on the DOM element, but there may be any number of instances of the javascript object created associated with different DOM elements and the individual object instances do not know how they will be employed by the code that instantiated them. The object instances do not know how to reference themselves globally because they do not know which global variables (if any) will be assigned references to their instances.

So the problem is to execute an event handling function that has an association with a particular instance of the javascript object, and knows which method of that object to call.

The following example uses a small generalised closure based function that associates object instances with element event handlers. Arranging that the execution of the event handler calls the specified method of the object instance, passing the event object and a reference to the associated element on to the object method and returning the method's return value.

/* A general function that associates an object instance with an event
   handler. The returned inner function is used as the event handler.
   The object instance is passed as the - obj - parameter and the name
   of the method that is to be called on that object is passed as the -
   methodName - (string) parameter.
*/
function associateObjWithEvent(obj, methodName){
    /* The returned inner function is intended to act as an event
       handler for a DOM element:-
    */
    return (function(e){
        /* The event object that will have been parsed as the - e -
           parameter on DOM standard browsers is normalised to the IE
           event object if it has not been passed as an argument to the
           event handling inner function:-
        */
        e = e||window.event;
        /* The event handler calls a method of the object - obj - with
           the name held in the string - methodName - passing the now
           normalised event object and a reference to the element to
           which the event handler has been assigned using the - this -
           (which works because the inner function is executed as a
           method of that element because it has been assigned as an
           event handler):-
        */
        return obj[methodName](e, this);
    });
}

/* This constructor function creates objects that associates themselves
   with DOM elements whose IDs are passed to the constructor as a
   string. The object instances want to arrange than when the
   corresponding element triggers onclick, onmouseover and onmouseout
   events corresponding methods are called on their object instance.
*/
function DhtmlObject(elementId){
    /* A function is called that retrieves a reference to the DOM
       element (or null if it cannot be found) with the ID of the
       required element passed as its argument. The returned value
       is assigned to the local variable - el -:-
    */
    var el = getElementWithId(elementId);
    /* The value of - el - is internally type-converted to boolean for
       the - if - statement so that if it refers to an object the
       result will be true, and if it is null the result false. So that
       the following block is only executed if the - el - variable
       refers to a DOM element:-
    */
    if(el){
        /* To assign a function as the element's event handler this
           object calls the - associateObjWithEvent - function
           specifying itself (with the - this - keyword) as the object
           on which a method is to be called and providing the name of
           the method that is to be called. The - associateObjWithEvent
           - function will return a reference to an inner function that
           is assigned to the event handler of the DOM element. That
           inner function will call the required method on the
           javascript object when it is executed in response to
           events:-
        */
        el.onclick = associateObjWithEvent(this, "doOnClick");
        el.onmouseover = associateObjWithEvent(this, "doMouseOver");
        el.onmouseout = associateObjWithEvent(this, "doMouseOut");
        ...
    }
}
DhtmlObject.prototype.doOnClick = function(event, element){
    ... // doOnClick method body.
}
DhtmlObject.prototype.doMouseOver = function(event, element){
    ... // doMouseOver method body.
}
DhtmlObject.prototype.doMouseOut = function(event, element){
    ... // doMouseOut method body.
}

And so any instances of the DhtmlObject can associate themselves with the DOM element that they are interested in without any need to know anything about how they are being employed by other code, impacting on the global namespace or risking clashes with other instances of the DhtmlObject.

Example 3: Encapsulating Related Functionality

Closures can be used to create additional scopes that can be used to group interrelated and dependent code in a way that minimises the risk of accidental interaction. Suppose a function is to build a string and to avoid the repeated concatenation operations (and the creation of numerous intermediate strings) the desire is to use an array to store the parts of the string in sequence and then output the results using the Array.prototype.join method (with an empty string as its argument). The array is going to act as a buffer for the output, but defining it locally to the function will result in its re-creation on each execution of the function, which may not be necessary if the only variable content of that array will be re-assigned on each function call.

One approach might make the array a global variable so that it can be re-used without being re-created. But the consequences of that will be that, in addition to the global variable that refers to the function that will use the buffer array, there will be a second global property that refers to the array itself. The effect is to render the code less manageable, as, if it is to be used elsewhere, its author has to remember to include both the function definition and the array definition. It also makes the code less easy to integrate with other code because instead of just ensuring that the function name is unique within the global namespace it is necessary to ensure that the Array on which it is dependent is using a name that is unique within the global namespace.

A Closure allows the buffer array to be associated (and neatly packaged) with the function that is dependent upon it and simultaneously keep the property name to which the buffer array as assigned out of the global namespace and free of the risk of name conflicts and accidental interactions.

The trick here is to create one additional execution context by executing a function expression in-line and have that function expression return an inner function that will be the function that is used by external code. The buffer array is then defined as a local variable of the function expression that is executed in-line. That only happens once so the Array is only created once, but is available to the function that depends on it for repeated use.

The following code creates a function that will return a string of HTML, much of which is constant, but those constant character sequences need to be interspersed with variable information provided as parameter to the function call.

A reference to an inner function object is returned from the in-line execution of a function expression and assigned to a global variable so that it can be called as a global function. The buffer array is defined as a local variable in the outer function expression. It is not exposed in the global namespace and does not need to be re-created whenever the function that uses it is called.

/* A global variable - getImgInPositionedDivHtml - is declared and
   assigned the value of an inner function expression returned from
   a one-time call to an outer function expression.

   That inner function returns a string of HTML that represents an
   absolutely positioned DIV wrapped round an IMG element, such that
   all of the variable attribute values are provided as parameters
   to the function call:-
*/
var getImgInPositionedDivHtml = (function(){
    /* The - buffAr - Array is assigned to a local variable of the
       outer function expression. It is only created once and that one
       instance of the array is available to the inner function so that
       it can be used on each execution of that inner function.

       Empty strings are used as placeholders for the date that is to
       be inserted into the Array by the inner function:-
    */
    var buffAr = [
        '<div id="',
        '',   //index 1, DIV ID attribute
        '" style="position:absolute;top:',
        '',   //index 3, DIV top position
        'px;left:',
        '',   //index 5, DIV left position
        'px;width:',
        '',   //index 7, DIV width
        'px;height:',
        '',   //index 9, DIV height
        'px;overflow:hidden;\"><img src=\"',
        '',   //index 11, IMG URL
        '\" width=\"',
        '',   //index 13, IMG width
        '\" height=\"',
        '',   //index 15, IMG height
        '\" alt=\"',
        '',   //index 17, IMG alt text
        '\"><\/div>'
    ];
    /* Return the inner function object that is the result of the
       evaluation of a function expression. It is this inner function
       object that will be executed on each call to -
       getImgInPositionedDivHtml( ... ) -:-
    */
    return (function(url, id, width, height, top, left, altText){
        /* Assign the various parameters to the corresponding
           locations in the buffer array:-
        */
        buffAr[1] = id;
        buffAr[3] = top;
        buffAr[5] = left;
        buffAr[13] = (buffAr[7] = width);
        buffAr[15] = (buffAr[9] = height);
        buffAr[11] = url;
        buffAr[17] = altText;
        /* Return the string created by joining each element in the
           array using an empty string (which is the same as just
           joining the elements together):-
        */
        return buffAr.join('');
    }); //:End of inner function expression.
})();
/*^^- :The inline execution of the outer function expression. */

If one function was dependent on one (or several) other functions, but those other functions were not expected to be directly employed by any other code, then the same technique could be used to group those functions with the one that was to be publicly exposed. Making a complex multi-function process into an easily portable and encapsulated unit of code.

Other Examples

Probably one of the best known applications of closures is Douglas Crockford's technique for the emulation of private instance variables in ECMAScript objects. Which can be extended to all sorts of structures of scope contained nested accessibility/visibility, including the emulation of private static members for ECMAScript objects.

The possible application of closures are endless, understanding how they work is probably the best guide to realising how they can be used.

Accidental Closures

Rendering any inner function accessible outside of the body of the function in which it was created will form a closure. That makes closures very easy to create and one of the consequences is that javascript authors who do not appreciate closures as a language feature can observe the use of inner functions for various tasks and employ inner functions, with no apparent consequences, not realising that closures are being created or what the implications of doing that are.

Accidentally creating closures can have harmful side effects as the following section on the IE memory leak problem describes, but they can also impact of the efficiency of code. It is not the closures themselves, indeed carefully used they can contribute significantly towards the creation of efficient code. It is the use of inner functions that can impact on efficiency.

A common situation is where inner functions are used is as event handlers for DOM elements. For example the following code might be used to add an onclick handler to a link element:-

/* Define the global variable that is to have its value added to the
   - href - of a link as a query string by the following function:-
*/
var quantaty = 5;
/* When a link passed to this function (as the argument to the function
   call - linkRef -) an onclick event handler is added to the link that
   will add the value of a global variable - quantaty - to the - href -
   of that link as a query string, then return true so that the link
   will navigate to the resource specified by the - href - which will
   by then include the assigned query string:-
*/
function addGlobalQueryOnClick(linkRef){
    /* If the - linkRef - parameter can be type converted to true
       (which it will if it refers to an object):-
    */
    if(linkRef){
        /* Evaluate a function expression and assign a reference to the
           function object that is created by the evaluation of the
           function expression to the onclick handler of the link
           element:-
        */
        linkRef.onclick = function(){
            /* This inner function expression adds the query string to
               the - href - of the element to which it is attached as
               an event handler:-
            */
            this.href += ('?quantaty='+escape(quantaty));
            return true;
        };
    }
}

Whenever the addGlobalQueryOnClick function is called a new inner function is created (and a closure formed by its assignment). From the efficiency point of view that would not be significant if the addGlobalQueryOnClick function was only called once or twice, but if the function was heavily employed many distinct function objects would be created (one for each evaluation of the inner function expression).

The above code is not taking advantage of the fact that inner functions are becoming accessible outside of the function in which they are being created (or the resulting closures). As a result exactly the same effect could be achieved by defining the function that is to be used as the event handler separately and then assigning a reference to that function to the event handling property. Only one function object would be created and all of the elements that use that event handler would share a reference to that one function:-

/* Define the global variable that is to have its value added to the
   - href - of a link as a query string by the following function:-
*/
var quantaty = 5;

/* When a link passed to this function (as the argument to the function
   call - linkRef -) an onclick event handler is added to the link that
   will add the value of a global variable - quantaty - to the - href -
   of that link as a query string, then return true so that the link
   will navigate to the resource specified by the - href - which will
   by then include the assigned query string:-
*/
function addGlobalQueryOnClick(linkRef){
    /* If the - linkRef - parameter can be type converted to true
       (which it will if it refers to an object):-
    */
    if(linkRef){
        /* Assign a reference to a global function to the event
           handling property of the link so that it becomes the
           element's event handler:-
        */
        linkRef.onclick = forAddQueryOnClick;
    }
}
/* A global function declaration for a function that is intended to act
   as an event handler for a link element, adding the value of a global
   variable to the - href - of an element as an event handler:-
*/
function forAddQueryOnClick(){
    this.href += ('?quantaty='+escape(quantaty));
    return true;
}

As the inner function in the first version is not being used to exploit the closures produced by its use, it would be more efficient not to use an inner function, and thus not repeat the process of creating many essentially identical function objects.

A similar consideration applies to object constructor functions. It is not uncommon to see code similar to the following skeleton constructor:-

function ExampleConst(param){
    /* Create methods of the object by evaluating function expressions
       and assigning references to the resulting function objects
       to the properties of the object being created:-
    */
    this.method1 = function(){
        ... // method body.
    };
    this.method2 = function(){
        ... // method body.
    };
    this.method3 = function(){
        ... // method body.
    };
    /* Assign the constructor's parameter to a property of the object:-
    */
    this.publicProp = param;
}

Each time the constructor is used to create an object, with new ExampleConst(n), a new set of function objects are created to act as its methods. So the more object instances that are created the more function objects are created to go with them.

Douglas Crockford's technique for emulating private members on javascript objects exploits the closure resulting form assigning references to inner function objects to the public properties of a constructed object from within its constructor. But if the methods of an object are not taking advantage of the closure that they will form within the constructor the creation of multiple function objects for each object instantiation will make the instantiation process slower and more resources will be consumed to accommodate the extra function objects created.

In that case it would be more efficient to create the function object once and assign references to them to the corresponding properties of the constructor's prototype so they may be shared by all of the objects created with that constructor:-

function ExampleConst(param){
    /* Assign the constructor's parameter to a property of the object:-
    */
    this.publicProp = param;
}
/* Create methods for the objects by evaluating function expressions
   and assigning references to the resulting function objects to the
   properties of the constructor's prototype:-
*/
ExampleConst.prototype.method1 = function(){
    ... // method body.
};
ExampleConst.prototype.method2 = function(){
    ... // method body.
};
ExampleConst.prototype.method3 = function(){
    ... // method body.
};

The Internet Explorer Memory Leak Problem

The Internet Explorer web browser (verified on versions 4 to 6 (6 is current at the time of writing)) has a fault in its garbage collection system that prevents it from garbage collecting ECMAScript and some host objects if those host objects form part of a "circular" reference. The host objects in question are any DOM Nodes (including the document object and its descendants) and ActiveX objects. If a circular reference is formed including one or more of them, then none of the objects involved will be freed until the browser is closed down, and the memory that they consume will be unavailable to the system until that happens.

A circular reference is when two or more objects refer to each other in a way that can be followed and lead back to the starting point. Such as object 1 has a property that refers to object 2, object 2 has a property that refers to object 3 and object 3 has a property that refers back to object 1. With pure ECMAScript objects as soon as no other objects refer to any of objects 1, 2 or 3 the fact that they only refer to each other is recognised and they are made available for garbage collection. But on Internet Explorer, if any of those objects happen to be a DOM Node or ActiveX object, the garbage collection cannot see that the circular relationship between them is isolated from the rest of the system and free them. Instead they all stay in memory until the browser is closed.

Closures are extremely good at forming circular references. If a function object that forms a closure is assigned as, for example, and event handler on a DOM Node, and a reference to that Node is assigned to one of the Activation/Variable objects in its scope chain then a circular reference exists. DOM_Node.onevent -> function_object.[[scope]] -> scope_chain -> Activation_object.nodeRef -> DOM_Node. It is very easy to do, and a bit of browsing around a site that forms such a reference in a piece of code common to each page can consume most of the systems memory (possibly all).

Care can be taken to avoid forming circular references and remedial action can be taken when they cannot otherwise be avoided, such as using IE's onunload event to null event handling function references. Recognising the problem and understanding closures (and their mechanism) is the key to avoiding this problem with IE.

comp.lang.javascript FAQ notes T.O.C.

  • Written by Richard Cornford. March 2004.
  • With corrections and suggestions by:-
    • Martin Honnen.
    • Yann-Erwan Perio (Yep).
    • Lasse Reichstein Nielsen. (definition of closure)
    • Mike Scirocco.
    • Dr John Stockton.
    • Garrett Smith.

 

Posted by 1010
반응형

자바스크립트의 메모리관리

개요

C 언어같은 저급 언어는 메모리 관리를 위해 malloc() 과 free()를 사용한다. 반면, 자바스크립트는 무언가가 생성되었을 때(오브젝트나 문자열 등) 메모리를 할당하고 쓸모 없어졌을 때 '자동으로' free 한다. '자동으로' 라는 말에는 혼란의 여지가 있다. 이는 자바스크립트를 포함한 여러 고급 언어 개발자들에게 메모리 관리가 불가능하다는 인상을 준다. 하지만 실상은 그렇지 않다.  

메모리 생존주기

메모리 생존주기는 프로그래밍 언어와 관계없이 비슷하다.

  1. 필요할때 할당한다.
  2. 사용한다. (읽기, 쓰기)
  3. 필요없어지면 해제한다. 

첫 번째 부분과 두 번째 부분은 모든 언어에서 분명하게 기술되지만 마지막 부분은 조금 다르다. 저급 언어에서는 분명히 기술되지만 자바스크립트 같은 고급 언어에서는 분명하게 기술되지 않는다(역자: 명시적으로 free를 하지 않는다는 의미). 

자바스크립트에서 메모리 할당

값 초기화

자바스크립트에서는 프로그래머들이 일일히 메모리 할당을 하는 수고를 덜어주기위해 값을 선언할 때 메모리를 할당한다. 

var n = 123; // 정수를 담기 위한 메모리 할당
var s = "azerty"; // 문자열을 담기 위한 메모리 할당

var o = {
  a: 1,
  b: null
}; // 오브젝트와 그 오브젝트에 포함된 값들을 담기 위한 메모리 할당

var a = [1, null, "abra"]; // (오브젝트 처럼) 배열과 배열에 담긴 값들을 위한 메모리 할당

function f(a){
  return a + 2;
} // 함수를 위한 할당(함수는 '호출가능한' 오브젝트이다)

// 함수식 또한 오브젝트를 담기위한 메모리를 할당한다. 
someElement.addEventListener('click', function(){
  someElement.style.backgroundColor = 'blue';
}, false);

함수 호출을 통한 할당

몇 가지 함수에서도 메모리 할당이 일어난다. 

var d = new Date();
var e = document.createElement('div'); // DOM 엘리먼트를 위해 메모리를 할당한다.

몇 가지 메쏘드도 새로운 값이나 오브젝트를 담기 위해 메모리 할당이 일어난다.

var s = "azerty";
var s2 = s.substr(0, 3); // s2는 새로운 문자열
// 자바스크립트에서 문자열은 immutable 값이기 때문에 메모리를 새로 할당하지 않고 단순히 [0, 3] 이라는 범위만 저장한다. 

var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2); // 4개의 원소를 가진 새로운 배열

값 사용

값 사용이란 기본적으로는 할당된 메모리를 읽고 쓰는 것을 의미한다. 변수나 오브젝트 속성 값을 읽고 쓸때 값 사용이 일어난다. 또 함수 호출시 함수에 인수를 넘길때도 일어난다. 

할당된 메모리가 더 이상 필요없을 때 해제하기

이 단계에서 대부분의 문제가 발생한다. "할당된 메모리가 더 이상 필요없을 때"를 알아내기가 힘들기 때문이다. 이제까지는 개발자들이 메모리가 필요없어질 때를 정하고 free하곤 했다. 

고급 언어 인터프리터는 "가비지 콜렉터"라는 소프트웨어를 가지고 있다. 가비지 콜렉터란 메모리 할당을 추적하고 할당된 메모리가 더 이상 필요 없어졌을 때 해제하는 작업을 한다. 이 작업은 근사적인 작업이다. 왜냐하면 일반적인 경우에 어떤 메모리가 필요없는지 알아내는 것은 알고리즘으로 풀 수 없는 비결정적인 문제이기 때문이다. (역자: 세상에 존재하는 모든 가비지 콜렉터는 안전하지만 완전하지 않다. 가비지 콜렉터는 항상 필요없어진 메모리만을 해제하지만 모든 필요없어진 메모리를 해제하는건 아니다)

가비지 콜렉션

위에서 언급한 것처럼 "더 이상 필요없는" 모든 메모리를 찾는건 비결정적이다. 따라서 몇 가지 제한을 두어 "더 이상 필요없는 모든 메모리"가 아니라 "더 이상 필요없는 몇몇 메모리"를 찾아보자. 몇 개의 가비지 콜렉션 알고리즘을 소개하고 한계점을 알아볼 것이다.

참조

가비지 콜렉션 알고리즘의 핵심 개념은 참조이다. A라는 메모리를 통해 (명시적이든 암시적이든) B라는 메모리에 접근할 수 있다면 "B는 A에 참조된다" 라고 한다. 예를 들어 모든 자바스크립트 오브젝트는 prototype 을 암시적으로 참조하고 그 오브젝트의 속성을 명시적으로 참조한다.

앞으로 "오브젝트"라는 어휘의 의미를 넓혀서 기존의 자바스크립트 오브젝트뿐만 아니라 함수 스코프도 포괄하자.

참조-세기(Reference-counting) 가비지 콜렉션

참조-세기 알고리즘은 가장 무난한 알고리즘이다. 이 알고리즘은 "더 이상 필요없는 오브젝트"를 "어떤 다른 오브젝트도 참조하지 않는 오브젝트"라고 정의한다. 어떤 오브젝트를 참조하는 다른 오브젝트가 하나도 없다면 그 오브젝트에 대해 가비지 콜렉션을 수행한다.

예제

var o = { 
  a: {
    b:2
  }
}; // 2개의 오브젝트가 생성되었다. 하나의 오브젝트는 다른 오브젝트의 속성으로 참조된다.
// 나머지 하나는 'o' 변수에 할당되었다.
// 명백하게 가비지 콜렉션 수행될 메모리는 하나도 없다.


var o2 = o; // 'o2' 변수는 위의 오브젝트를 참조하는 두 번째 변수이다.
o = 1; // 이제 'o2' 변수가 위의 오브젝트를 참조하는 유일한 변수가 되었다.

var oa = o2.a; // 위의 오브젝트의 'a' 속성을 참조했다.
// 이제 'o2.a'는 두 개의 참조를 가진다. 'o2'가 속성으로 참조하고 'oa'라는 변수가 참조한다.

o2 = "yo"; // 이제 맨 처음 'o' 변수가 참조했던 오브젝트를 참조하는 오브젝트는 없다(역자: 참조하는 유일한 변수였던 o2에 다른 값을 대입했다)
// 이제 오브젝트에 가비지 콜렉션이 수행될 수 있을까?
// 아니다. 오브젝트의 'a' 속성이 여전히 'oa' 변수에 의해 참조되므로 메모리를 해제할 수 없다.

oa = null; // 'oa' 변수에 다른 값을 할당했다. 이제 맨 처음 'o' 변수가 참조했던 오브젝트를 참조하는 다른 변수는 없으므로 가비지 콜렉션이 수행된다.

한계: 순환

이 알고리즘은 두 오브젝트가 서로를 참조하면 문제가 발생한다. 두 오브젝트 모두 필요 없어졌더라도 가비지 콜렉션을 수행할 수 없다.

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o는 o2를 참조한다.
  o2.a = o; // o2는 o를 참조한다.

  return "azerty";
}

f();
// 두 오브젝트가 만들어지고 서로를 참조해서 순환이 일어났다.
// 함수가 종료되고 나면 사실상 두 오브젝트는 의미가 없어지므로 가비지 콜렉션이 수행되어야 한다.
// 그러나 위의 참조-세기 알고리즘에서는 두 오브젝트 모두 참조를 가지고 있기 때문에 둘 다 가비지 콜렉션이 일어나지 않는다.

실제 예제

인터넷 익스플로러 6, 7 은 DOM 오브젝트에 대해 참조-세기 알고리즘으로 가비지 콜렉션을 수행한다. 흔히, 이 두 브라우저에서는 다음과 같은 패턴의 메모리 누수가 발생한다. 

var div = document.createElement("div");
div.onclick = function(){
  doSomething();
}; // div 오브젝트는 이벤트 핸들러를 'onclick' 속성을 통해 참조한다.
// 이벤트 핸들러의 스코프에도 div 오브젝트가 있으므로 div 오브젝트에 접근할 수 있다. 따라서 이벤트 핸들러도 div 오브젝트를 참조한다.
// 순환이 발생했고 메모리 누수가 일어난다.

표시하고-쓸기(Mark-and-sweep) 알고리즘

이 알고리즘은 "더 이상 필요없는 오브젝트"를 "닿을 수 없는 오브젝트"로 정의한다.

이 알고리즘은 roots 라는 오브젝트의 집합을 가지고 있다(자바스크립트에서는 전역 변수들을 의미한다). 주기적으로 가비지 콜렉터는 roots로 부터 시작하여 roots가 참조하는 오브젝트들, roots가 참조하는 오브젝트가 참조하는 오브젝트들... 을 닿을 수 있는 오브젝트라고 표시한다. 그리고 닿을 수 있는 오브젝트가 아닌 닿을 수 없는 오브젝트에 대해 가비지 콜렉션을 수행한다.

이 알고리즘은 위에서 설명한 참조-세기 알고리즘보다 효율적이다. 왜냐하면 "참조되지 않는 오브젝트"는 모두 "닿을 수 없는 오브젝트" 이지만 역은 성립하지 않기 때문이다. 위에서 반례인 순환 참조하는 오브젝트들을 설명했다.

2012년 기준으로 모든 최신 브라우저들은 가비지 콜렉션에서 표시하고-쓸기 알고리즘을 사용한다. 지난 몇 년간 연구된 자바스크립트 가비지 콜렉션 알고리즘의 개선들은 모두 이 알고리즘에 대한 것이다. 개선된 알고리즘도 여전히 "더 이상 필요없는 오브젝트"를 "닿을 수 없는 오브젝트"로 정의하고 있다.

순환 참조는 이제 문제가 되지 않는다.

첫 번째 예제에서 함수가 리턴되고 나서 두 오브젝트는 닿을 수 없다. 따라서 가비지 콜렉션이 일어난다.

두 번째 예제에서도 마찬가지다. div 변수와 이벤트 핸들러가 roots로 부터 닿을 수 없어지면 순환 참조가 일어났음에도 불구하고 가비지 콜렉션이 일어난다.

한계: 오브젝트들은 명시적으로 닿을 수 없어져야 한다.

이 한계가 지적되었지만 실제로는 사람들은 이 문제를 비롯한 가비지 콜렉션에 별 관심이 없다.

더 보기

출처 : https://developer.mozilla.org/ko/docs/JavaScript/Memory_Management

Posted by 1010
반응형

출처 : http://kwonnam.pe.kr/wiki/java/tomcat/contextreload

Tomcat Context Reloader

Tomcat의 컨텍스트를 reloadable=“false”인 상태에서, Tomcat Manager를 설치하지 않은 상태에서 수동 Reload 할 수 있는 Valve를 만들어 보았다. Reload Tomcat Context manually(without manager or reloadable=“true” option).

설정

  • Tomcat Reload Valve 소스와 Jar 파일에서 tomcatreloadvalve.jar 파일을 $CATALINA_HOME/lib 로 복사한다.
  • server.xml 혹은 context.xml의 <Context> 항목에 Valve를 추가한다. 항상 <Context> 항목에만 추가해야 한다.
    <Context docBase="some" path="/some" reloadable="false" >
        <Valve className="kr.pe.kwonnam.tomcat.reloader.TomcatReloadValve"/>
    </Context>
  • reloadable=“false”로 둔다. 원래 이 Valve의 목적은 자동 Reloading을 끄고, 항상 수동으로 원하는 경우에만 Reloading하는 것이다.

실행

  • 웹브라우저 혹은 wget 등으로 http://localhost:8080/reloadContext 를 호출한다.
  • 실제 URL의 도메인네임 부분은 자신의 톰캣 설정을 따른다.
  • “Context Reloaded!!” 메시지가 나오면서 Reloading이 완료된다.

소스

package kr.pe.kwonnam.tomcat.reloader;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
 
/**
 * Reload Tomcat Context by requesting URL
 *
 * Context의 reloadable="false"인 상태에서도 /reloadContext URL을 호출하면 해당 컨텍스트가 Reloading 된다.
 *
 * @author 손권남 kwon37xi@gmail.com
 *
 */
public class TomcatReloadValve extends ValveBase {
 
	private static final String RELOAD_CONTEXT_URI = "/reloadContext";
 
	@Override
	public void invoke(Request request, Response response) throws IOException,
			ServletException {
 
		Container container = getContainer();
 
		String requestUri = request.getRequestURI();
 
		String reloadUri = request.getContextPath() + RELOAD_CONTEXT_URI;
 
		if (requestUri.startsWith(reloadUri)
				&& container instanceof Context) {
			reloadContext(response, container);
			return;
		}
 
		getNext().invoke(request, response);
	}
 
	private void reloadContext(Response response, Container container)
			throws IOException {
		((Context) container).reload();
		HttpServletResponse httpResponse = response.getResponse();
 
		httpResponse.setContentType("text/plain;charset=utf-8");
		httpResponse.getWriter().write("Context Reloaded!!");
		httpResponse.getWriter().close();
 
		return;
	}
 
}

 

Posted by 1010
반응형

While on Tomcat 6 and Eclipse Ganymede I found out the following chain to work like charm:

1 stop server

2 project -> clean

3 project build (I had automatic build disabled)

4 delete server

5 delete Servers folder

6 restart Eclipse

7 create new server, add project and start :)

Posted by 1010
반응형

eclipse에서 maven 최신 버전 (0.12.xx)를 사용할 경우 maven build시 아래와 같은 에러 메세지를 확인 할 수 있습니다.


Missing artifact javax.activation:activation:jar:1.0.2:provided 
Missing artifact javax.mail:mail:jar:1.3.1:provided 
Missing artifact javax.xml:namespace:jar:1.1.0:provided


이 메세지가 발생 할 경우 eclipse의 maven intergration을 uninstall 후 하위 버전(0.10.xx)으로 설치해주면 해결 됩니다.

0.12.xx에서 새로 생긴 HttpClient 관련 인증 문제 때문에 발생하는 문제인데 아직까지 해결을 안해주고 있네요 -_-;;

# Appendix

- 위의 방식으로도 계속 Missing Artifact ... 가 발생한다면 아래를 참고하여 해결하시길 바랍니다.

1. right click on your project > Maven > Update Dependencies

then

2. right click on your project > Maven > Update Project configuration

Assuming you M2Eclipse plugin is installed correctly this should solve the issue. Also, check if there is an option right click project > Maven > 3. Enable dependency Management select that

참고 URL : http://stackoverflow.com/questions/5500534/m2eclipse-says-missing-artifact-but-i-can-build-from-cmdline



출처 : http://blog.daum.net/gildongmoo2/15971083

Posted by 1010
반응형

Scheduling 서비스

개요

Scheduling 서비스는 어플리케이션 서버 내에서 주기적으로 발생하거나 반복적으로 발생하는 작업을 지원하는 기능으로서 유닉스의 크론(Cron) 명령어와 유사한 기능을 제공한다.
실행환경 Scheduling 서비스는 오픈소스 소프트웨어로 Quartz 스케쥴러를 사용한다. 본 장에서는 Quartz 스케쥴러의 기본 개념을 살펴본 후, IoC 서비스를 제공하는 Spring과 Quartz 스케쥴러를 통합하여 사용하는 방법을 살펴본다.

설명

Quartz 스케쥴러

Quartz 스케쥴러 실행과 관계된 주요 요소는 Scheduler, Job, JobDetail, Trigger 가 있다.

  • Scheduler 는 Quartz 실행 환경을 관리하는 핵심 개체이다.
  • Job 은 사용자가 수행할 작업을 정의하는 인터페이스로서 Trigger 개체를 이용하여 스케쥴할 수 있다.
  • JobDetail 는 작업명과 작업그룹과 같은 수행할 Job에 대한 상세 정보를 정의하는 개체이다.
  • Trigger 는 정의한 Job 개체의 실행 스케쥴을 정의하는 개체로서 Scheduler 개체에게 Job 수행시점을 알려주는 개체이다.

Quartz 스케쥴러는 수행 작업을 정의하는 Job과 실행 스케쥴을 정의하는 Trigger를 분리함으로써 유연성을 제공한다. Job 과 실행 스케쥴을 정의한 경우, Job은 그대로 두고 실행 스케쥴만을 변경할 수 있다. 또한 하나의 Job에 여러 개의 실행 스케쥴을 정의할 수 있다.

Quartz 스케쥴러 사용 예제

Quartz 스케쥴러의 이해를 돕기 위해 간단한 예제를 살펴본다. 다음 예는 Quartz 매뉴얼에서 참조한 것으로 Quartz를 사용하는 방법과 사용자 Job을 설정하는 방법을 보여준다.

사용자 정의 Job

사용자는 Job 개체를 생성하기 위해 org.quartz.Job 인터페이스를 구현하고 심각한 오류가 발생한 경우 JobExecutionException 예외를 던질 수 있다. Job 인터페이스는 단일 메소드로 execute()을 정의한다.

 public class DumbJob implements Job {
    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      System.out.println("DumbJob is executing.");
    }
  }
  • DumbJob은 Job 인터페이스의 execute() 메소드를 구현한다.
  • execute() 메소드는 단순히 Job이 수행됨을 표시하는 메시지를 출력한다.
Quartz 사용 코드
  JobDetail jobDetail = 
            new JobDetail("myJob",// Job 명
              sched.DEFAULT_GROUP,  // Job 그룹명('null' 값인 경우 DEFAULT_GROUP 으로 정의됨)
              DumbJob.class);       // 실행할 Job 클래스
 
  Trigger trigger = TriggerUtils.makeDailyTrigger(8, 30);  // 매일 08시 30분 실행
  trigger.setStartTime(new Date()); // 즉시 시작
  trigger.setName("myTrigger");
 
  sched.scheduleJob(jobDetail, trigger);
  • 우선 Job 설정을 위해 JobDetail 클래스를 정의한다.
  • TriggerUtils을 이용하여 매일 8시30분 실행하는 Trigger를 생성한다.
  • 마지막으로, Scheduler에 JobDetail과 Trigger를 등록한다.

Spring 과 Quartz 통합

Spring은 Scheduling 지원을 위한 통합 클래스를 제공한다. Spring 2.5 는 JDK 1.3 버전부터 포함된 Timer 와 오픈소스 소프트웨어인 Quartz 스케쥴러를 지원한다. 여기서는 Quartz 스케쥴러와 Spring을 통합하여 사용하는 방법을 살펴본다. 
Quartz 스케쥴러와의 통합을 위해 Spring은 Spring 컨텍스트 내에서 Quart Scheduler와 JobDetail, Trigger 를 빈으로 설정할 수 있도록 지원한다. 다음은 예제를 중심으로 Quartz 작업 생성과 작업 스케쥴링, 작업 시작 방법을 살펴본다.

작업 생성

Spring은 작업 생성을 위한 방법으로 다음 두 가지 방식을 제공한다.

  • JobDetailBean을 이용한 방법으로, QuartzJobBean을 상속받아 Job 클래스를 생성하는 방법
  • MethodInvokingJobDetailFactoryBean을 이용하여 Bean 객체의 메소드를 직접 호출하는 방법
JobDetailBean을 이용한 작업 생성

JobDetail는 작업 실행에 필요한 정보를 담고 있는 객체이다. Spring은 JobDetail 빈 생성을 위해 JobDetailBean을 제공한다. 예를 들면 다음과 같다.

JobDetailBean 소스 코드

package egovframework.rte.fdl.scheduling.sample;
 
public class SayHelloJob extends QuartzJobBean {
 
	private String name;
 
	public void setName (String name) {
		this.name = name;	
	}
 
	@Override
	protected void executeInternal (JobExecutionContext ctx) throws JobExecutionException {
		System.out.println("Hello, " + name);
	}
}
  • SayHelloJob 클래스는 작업 생성을 위해 QuartzJobBean의 executeInternal(..) 함수를 오버라이드한다.

JobDetailBean 설정

 <bean id="jobDetailBean"
	class="org.springframework.scheduling.quartz.JobDetailBean">
	<property name="jobClass" value="egovframework.rte.fdl.scheduling.sample.SayHelloJob" />
	<property name="jobDataAsMap">
		<map>
			<entry key="name" value="JobDetail"/>
		</map>
	</property>
  </bean>
  • jobDataAsMap 개체를 이용하여 JobDetail 개체에 Job 설정에 필요한 속성 정보를 전달한다.
MethodInvokingJobDetailFactoryBean을 이용한 작업 생성

소스 코드

package egovframework.rte.fdl.scheduling.sample;
 
public class SayHelloService {
 
	private String name;
 
	public void setName (String name) {
		this.name = name;	
	}
 
	public void sayHello () {
		System.out.println("Hello, " + this.name);
	}
}
  • 작업 수행을 할 Bean 클래스를 정의한다.

설정

<bean id="sayHelloService" class="egovframework.rte.fdl.scheduling.sample.SayHelloService">
	<property name="name" value="FactoryBean"/>
</bean>
 
<bean id="jobDetailFactoryBean"
	class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
	<property name="targetObject" ref="sayHelloService" />
	<property name="targetMethod" value="sayHello" />
	<property name="concurrent" value="false" />
</bean>
  • 정의한 Bean 객체의 메소드를 직접 호출하는 작업을 생성하기 위해 MethodInvokingJobDetailFactoryBean을 정의한다.

작업 스케쥴링

Spring에서 주로 사용되는 Trigger타입은 SimpleTriggerBean과 CronTriggerBean 이 있다. SimpleTrigger 는 특정 시간, 반복 회수, 대기 시간과 같은 단순 스케쥴링에 사용된다. CronTrigger 는 유닉스의 Cron 명령어와 유사하며, 복잡한 스케쥴링에 사용된다. CronTrigger 는 달력을 이용하듯 특정 시간, 요일, 월에 Job 을 수행하도록 설정할 수 있다. 다음은 SimpleTriggerBean과 CronTriggerBean을 이용하여 앞서 생성한 작업을 스케쥴링하는 방법을 살펴본다.

SimpleTriggerBean을 이용한 설정

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
	<property name="jobDetail" ref="jobDetailBean" />
        <!-- 즉시 시작 -->
	<property name="startDelay" value="0" />
   	<!-- 매 10초마다 실행 -->
	<property name="repeatInterval" value="10000" />
</bean>
  • 앞서 JobDetailBean 을 이용하여 생성한 작업을 스케쥴링을 위한 Trigger 에 등록한다. SimpleTriggerBean은 즉시 시작하고 매 10초마다 실행하도록 설정하였다.

CronTriggerBean을 이용한 설정

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
   	<property name="jobDetail" ref="jobDetailFactoryBean" />
   	<!-- 매 10초마다 실행 -->
   	<property name="cronExpression" value="*/10 * * * * ?" />
</bean>
  • 앞서 MethodInvokingJobDetailFactoryBean 을 이용하여 생성한 작업을 스케쥴링을 위한 Trigger 에 등록한다. CronTriggerBean은 매 10초마다 실행하도록 설정하였다. 크론 표현식에 대한 자세한 설명은Quartz Cron 표현식를 참조한다.

작업 시작하기

스케쥴링한 작업의 시작을 위해 Spring 은 SchedulerFactoryBean을 제공한다.

설정

<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="triggers">
		<list>
			<ref bean="simpleTrigger" />
			<ref bean="cronTrigger" />
		</list>
	</property>
</bean>
  • SchedulerFactoryBean 을 이용하여 SimpleTriggerBean 과 CronTriggerBean 기반의 각 Trigger 작업을 시작한다.

참고자료



링크 : http://javastore.tistory.com/96 

링크 : http://kamsi76.egloos.com/470846 

링크 : http://briansjavablog.blogspot.kr/2012/09/spring-quartz-tutorial.html 

Posted by 1010
반응형

The error I get is as follows:

SEVERE: StandardWrapper.Throwable
org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.springframework.scheduling.quartz.JobDetailBean] for bean with name ‘runMeJob’ defined in ServletContext resource [/WEB-INF/quartz-servlet.xml]: problem with class file or dependent class; nested exception is java.lang.IncompatibleClassChangeError: class org.springframework.scheduling.quartz.JobDetailBean has interface org.quartz.JobDetail as super class

 

이런 오류 메시지가 나올때는

 

quartz 다운로드 페이지에서 Quartz 1.8.6 버전으로 lib 교체해주면 된다.



출처 - http://jhroom.tistory.com/129



===================================================================================


WAS를 이용해서 주기적으로 특정 프로그램 실행하는 방법이 spring에서는 2가지가 있다. (뭐 더 있을수도 있다 -_-)

하나는 spring batch이고 다른 하나는 quartz를 이용하는 방법이다.

뭐가좋은지는 나도 잘 모른다. 일단 quartz가 어떤식으로 동작하는지 먼저 공부했으므로 이것부터 글 적는다.

 

1. 샘플 프로젝트 생성

STS 에서 [File] - [New] - [Spring Template Project] 메뉴를 클릭한 후 Spring MVC Project를 이용해 샘플 프로젝트를 생성한다.

 

2. pom.xml 파일 수정

quartz 관련 모듈을 이용하기 위해서 pom.xml 파일에 관련 라이브러리들 넣는다.

spring-tx, quartz, commons-collections, javax.transaction 4개 넣어줘야한다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<!-- Quartz framework and dependencies -->
<dependency>
    <groupId>opensymphony</groupId>
    <artifactId>quartz</artifactId>
    <version>1.6.3</version>
    <scope>compile</scope>
</dependency>
<!-- Quartz 1.6.0 depends on commons collections -->
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
    <scope>runtime</scope>
</dependency>
<!-- Quartz 1.6.0 requires JTA in non J2EE environments -->
<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>jta</artifactId>
    <version>1.1</version>
    <scope>runtime</scope>
</dependency>

 

3. 주기적으로 실행 될 Bean 생성

2개의 Bean을 만든다.

CronQuartz1.java

package com.mungchung.sample;
 
import java.text.SimpleDateFormat;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
 
public class CronQuartz1 extends QuartzJobBean{
    @Override
    protected void executeInternal(JobExecutionContext arg0)
            throws JobExecutionException {
        long time = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        System.out.println("Cron trigger 1 (5 second): current time = " + sdf.format(time));
    }
 
}

CronQuartz2.java

package com.mungchung.sample;
 
import java.text.SimpleDateFormat;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
 
public class CronQuartz2 extends QuartzJobBean{
    @Override
    protected void executeInternal(JobExecutionContext arg0)
            throws JobExecutionException {
        long time = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        System.out.println("Cron trigger 2 (1 minute): current time = " + sdf.format(time));
    }
}

 

4. quartz Bean과 위에서 생성한 Bean 연동

2개의 Bean이 각각 다른 시간차이로 실행되도록 설정해줄것이다.

CronQuartz1.java - 5초마다 실행

CronQuartz2.java - 1분마다 실행

 

/src/main/webapp/WEB-INF/spring/root-context.xml

<!-- 1. Cron 대상이 되는 클래스 정의 -->
<bean id="cronQuartz1" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="com.mungchung.sample.CronQuartz1"/>
</bean>
<bean id="cronQuartz2" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="com.mungchung.sample.CronQuartz2"/>
</bean>
 
<!-- 2. Cron 시간 설정 -->
<bean id="cronTrigger1" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="cronQuartz1"/>
    <property name="cronExpression" value="0/5 * * * * ?"/>
</bean>
<bean id="cronTrigger2" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="cronQuartz2"/>
    <property name="cronExpression" value="0 0/1 * * * ?"/>
</bean>
 
<!-- 3. Cron 실행 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger1"/>
            <ref bean="cronTrigger2"/>
        </list>
    </property>
    <property name="quartzProperties">
        <props>
            <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
            <prop key="org.quartz.threadPool.threadCount">3</prop>
            <prop key="org.quartz.threadPool.threadPriority">4</prop>
            <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
            <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
        </props>
    </property>
</bean>

 

 

Cron Expression

총 7개의 필드 있고 마지막 필드(년도)는 생략 가능하다

 필드이름허용 값 
초(Seconds)0 ~ 59 
분(Minutes)0 ~ 59 
시간(Hours)0 ~ 23
달의 날짜(Day-of-month)1 ~ 31
달(Month) 1 ~ 12 or JAN ~ DEC
주의 날짜(Day-of-week)1 ~ 7 or SUN-SAT
년도(Year) (선택가능) 빈값, 1970 ~ 2099

Cron Expression의 특수문자
Expression설명 예시 
    * 모든 수를 나타냄  
     -값의 사이를 의미* 10-13 * * * *     10,11,12,13분에 동작함 
     ,특정값 지칭* 10,11,13 * * * *      10,11,13분에 동작함
     /값의 증가를 표현* 0/5 * * * *       0분부터 시작해서 5분마다 동작 
     ?특별한 값이 없음을 나타냄(day-of-month, day-of-week 필드만 사용) 
     L마지막 날을 나타냄(day-of-month, day-of-week 필드만 사용) 

6. 실행결과

실행결과를 보면 하나는 5초마다, 다른 하나는 1분마다 Bean이 실행되고 있음을 알수 있다.

01.png





===================================================================================


sping mvc template project로 구성 시

quartz-1.8.6과 sprint-tx를 적용하여 문제 없이 구현 하였다.


spring-tx를 pom.xml에 추가 하지 않을 경우 다음과 같이 에러 발생

심각: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schedulerFactory' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Instantiation of bean failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/transaction/TransactionException


===================================================================================


What Is Quartz

   

원문http://www.onjava.com/lpt/a/6207

저자Chuck Cavaness

2005-09-28

   

Quartz

Quartz는 오픈 소스 작업 스케줄링 프레임워크이다. Quartz는 완전히 자바로 작성되어 있으며, J2SE와 J2EE 어플리케이션 모두에서 사용될 목적으로 설계되었다. Quartz는 매우 유연하며 단순한 구조를 제공한다. 간단한 작업은 물론 복잡한 작업 모두에 대한 스케줄링을 작성할 수 있다. Quartz는 또한 EJB, JavaMail 등을 위한 데이터베이스 지원, 클러스터링, 플러그 인, 미리 내장된 작업들을 포함하고 있으며, cron과 유사한 표현식도 지원한다.

   

매일 또는 매주 오후 11시 30분 또는 매달 말일에만 실행하는 작업을 수행하는 어플리케이션을 작성해 본 적이 있는가? 수작업 없이 자동으로 실행될 수 있는 작업이 실행되는 동안 만약 실행하는 동안 심각한 오류가 발생할 경우, 어플리케이션은 잘못되었다는 것을 스스로 알아내 알려주고 이를 다시 실행시키도록 시도해야 할 것인가? 여러분과 팀이 자바로 프로그램을 작성하고 있는가? 만약 이들 질문들에 대한 대답이 "그렇다"라면, Quartz Scheduler를 사용해 보아라.

   

Job Scheduling Made Easy

Quartz는 오픈 소스 작업 스케줄링 프레임워크로 완전히 자바로 작성되었다. 작업 스케줄링 이라는 용어에 대해 너무 겁먹지 말아라. Quartz 프레임워크는 수많은 기능들이 매우 단순한 형태로 무장되어 있으며, 놀라울 정도로 매우 사용하기 쉽게 되어 있다.

   

org.quartz.Job 인터페이스를 구현하는 자바 클래스를 작성하기만 하면 된다. Job 인터페이스는 다음과 같은 하나의 메소드만을 포함하고 있다.

   

public void execute(JobExecutionContext context) throws JobExecutionException;

   

여러분이 작성한 Job 클래스에서 execute() 메소드에 몇 가지 로직을 추가한다. Job 클래스와 스케줄을 설정하게 되면, Quartz는 그 나머지 작업을 처리해준다. Scheduler가 Job에 알려줄 시간이라고 판단하게 되면, Quartz 프레임워크는 Job 클래스의 execute() 메소드를 실행하여 작업을 수행하도록 만들어준다. Scheduler에 어떠한 것도 보고해줄 필요가 없으며, 어떤 특별한 메소드를 호출할 필요도 없다. 단순히 Job 내에 있는 작업들만 수행해주면 끝이다. 만약 여러분이 이후에 다시 Job이 호출되도록 설정했다면, Quartz 프레임워크는 적절한 때에 다시 이를 호출하는 부분을 담당해준다.

   

만약 여러분이 Apache Struts와 같은 유명한 오픈 소스 프레임워크를 써본 경험이 있다면, Quartz의 설계 내용과 컴포넌트에도 쉽게 익숙해질 것이다. 비록 이 두 오픈 소스 프로젝트가 전혀 서로 다른 문제점들을 해결해주고 있지만, 오픈 소스 소프트웨어를 항시 사용하는 사람들이라면 편안한 느낌을 받을 것이다. Quartz는 표준 독립형 J2SE 어플리케이션 내에서라든지, 웹 어플리케이션 내부, 심지어는 J2EE 어플리케이션 서버 내에서 사용될 수 있다.

   

The History behind Quartz

Quartz가 주목을 받기 시작한 것은 올해부터지만, 나온 지는 좀 되었다. Quartz는 James House에 의해 개발되었으며, 원래는2001년 봄에 SourceForge 프로젝트에 추가되었다. 수년이 지나면서 많은 기능들이 추가되어 배포되었지만, 두각을 나타내기 시작하면서 주목을 받기 시작한 것은 OpenSymphony 프로젝트의 일부로 되면서 새로운 사이트로 옮겨지게 된 때부터였다.

   

House는 자신을 도와주는 여러 파트 타임 개발자와 함께 여전히 개발 작업의 많은 부분에 참여하고 있다. Quartz 개발 팀은 올 해 1.5 배포판을 포함해 다양한 새로운 버전들을 배포할 수 있었으며, 이는 현재 candidate 릴리즈 단계에 있다.

   

Getting Your Hands on Quartz

Quartz 프로젝트는 OpenSymphony 사이트에 호스팅 되어 있다. 이 사이트에는 JavaDocs, 튜토리얼, CVS, 사용자와 개발자 포럼 및 다운로드 링크와 같은 여러 유용한 정보들이 제공되고 있다.

   

다운로드 링크에서 배포판을 다운로드 한 후, 적절한 위치에 압축을 푼다. 이 안에 보면 어플리케이션에서 사용할 수 있는 Quartz바이너리 파일이 포함되어 있다. Quartz 프레임워크에서 요구 되는 다른 의존적인 라이브러리는 거의 없다.

   

배포판에 보면, <Quartz 설치 디렉터리>/lib/core와 <Quartz 설치 디렉터리>/lib/optional 디렉터리에 있는 다른 의존 라이브러리들을 프로젝트에 추가하면 된다. 이들 대부분은 표준 Jakarta Commons 라이브러리들로, 여러분도 잘 알고 있는 Commons Logging, Commons BeanUtils 등이다.

   

The quartz.properties File

Quartz에는 quartz.properties라는 환경 설정 파일이 포함되어 있다. 이 파일을 사용하면 Quartz 프레임워크의 런타임 환경을 수정할 수 있다. 디폴트로, Quartz 바이너리 내에 포함되어 있는 파일이 사용된다. 이 파일을 복사하여 클래스들이 있는 디렉터리에 두면 클래스 로더가 이를 참조할 수 있다. 예제 1은 quartz.properties 파일에 대한 간단한 예이다.

   

예제 1. quartz.properties 파일을 통해 Quartz 런타임을 변경할 수 있다.

#===============================================================

# Configure Main Scheduler Properties

#===============================================================

   

org.quartz.scheduler.instanceName = QuartzScheduler

org.quartz.scheduler.instanceId = AUTO

   

#===============================================================

# Configure ThreadPool

#===============================================================

   

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

org.quartz.threadPool.threadCount = 5

org.quartz.threadPool.threadPriority = 5

   

#===============================================================

# Configure JobStore

#===============================================================

   

org.quartz.jobStore.misfireThreshold = 60000

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

   

일단 Quartz 바이너리와 의존 라이브러리들을 프로젝트에 추가했고, quartz.properties 파일을 클래스패스 디렉터리에 추가했다면, 이제 몇몇 Job들을 작성해보자. 그러나, 이 작업을 해보기 전에 Quartz 아키텍처에 관해 간단하게 살펴보도록 하자.

   

Inside the Quartz Architecture

크기로 보자면, Quartz는 다른 대부분의 오픈 소스 프레임워크들과 유사하다. Quartz는 약 300 개의 자바 클래스들과 인터페이스들을 포함하고 있으며, 이들은 약 12개의 패키지로 구성되어 있다. 이는 Apache Struts에 있는 11개의 패키지에 약 325개의 클래스들과 인터페이스들에 비견될 수 있다. 비록 이 크기가 프레임워크의 질을 결정하는데 사용되는 특징이 될 수는 없겠지만, 여기에서 말하고자 하는 점은 Quartz 내에 수많은 기능들이 포함되어 있다는 사실이다. 그리고, 그러한 기능들이 바로 프레임워크의 질을 결정하는데 사용되는 하나의 요소이다. 이는 오픈 소스에만 국한되는 내용은 아니다.

   

The Quartz Scheduler

Quartz 프레임워크의 핵심은 Scheduler 이다. Scheduler는 Quartz 어플리케이션을 위한 런타임 환경을 관리하는 책임을 지니고 있다. Scheduler 그 자체가 모든 작업들 수행하는 것은 아니다. Scheduler는 프레임워크 내부에 있는 매우 중요한 몇몇 컴포넌트들에 그 작업을 의존하고 있다. 확장성을 보장하기 위해, Quartz는 멀티스레드 아키텍처를 기반으로 하고 있다. Quartz 프레임워크가 시작될 때, 프레임워크는 스케줄링 된 Job들을 실행하기 위해 Scheduler가 사용하는 "worker 스레드들"을 초기화 한다. Quartz가 동시에 수많은 Job들을 실행 가능한 이유가 바로 여기에 있다. Quartz는 스레드 환경을 관리하기 위해 ThreadPool관리 컴포넌트들에 의존하고 있는데, 그 결합도는 느슨하다. 이 글을 통해 여러 번 언급할 것이지만, 이는 Quartz에 있는 모든 것들은 환경 설정이 가능하거나 사용자가 정의해 지정할 수 있음을 의미한다. 예를 들면, 여러분이 정의한 "ThreadPool" 관리 기능을 플러그 인 형태로 끼워 넣고 싶은 경우, 이러한 작업이 가능하다.

   

Jobs, Jobs, and More Jobs

Quartz의 용어를 사용하자면, Job은 작업을 수행하는 간단한 자바 클래스이다. 이 작업은 자바에서 코드로 작성 가능한 그 어떠한 것이라도 될 수 있다. 필수적으로 요구되는 사항은, org.quartz.Job 인터페이스를 구현하고, 심각한 오류 발생 시JobExecutionException을 발생시키는 것뿐이다. 여러분은 이미 앞에서 Job 인터페이스와 포함된 execute() 메소드를 보았다.

   

Job 인터페이스와 execute() 메소드를 구현했다면, Quartz는 Job 실행 시기가 되었을 때, Job을 호출한다. execute() 메소드 안에서 무엇을 수행하는가는 전적으로 개발자에게 달려 있다. 다음은 Job 내부에서 실행할 작업들에 대한 몇몇 예이다.

   

● JavaMail이나 Commons Net과 같은 다른 메일 프레임워크를 사용하여 전자메일 전송

● EJB에 대한 원격 인터페이스를 생성한 후, 이 인터페이스의 메소드 호출

● Hibernate 세션을 얻어 관계형 데이터베이스에 있는 데이터 질의와 갱신

● OSWorkflow를 사용하여 Job으로부터 워크플로우 호출

● FTP를 사용해 파일 옮기기

● Ant 빌드 스크립트를 호출해 스케줄링 되어 있는 빌드 작업 시작

   

수많은 여러 작업들이 가능하며, 이것이 바로 Quartz 프레임워크를 매우 강력하게 만들어주는 이유이다. Quartz는 매우 일반적이고 반복적인 스케줄을 작성해주는 메커니즘을 제공하기 때문에, 개발자는 단지 실행을 위해 호출될 자바 클래스들만 작성하면 된다.

   

Job Management and Storage

Job들의 스케줄이 지정되었다면, Scheduler는 이러한 Job들을 기억하고 이들을 실행시킬 시간을 지속적으로 추적해야 한다. 만약 여러분의 Job이 30분 늦게 시작되거나 30초 일찍 시작된다면 Quartz는 그렇게 유용하지 않을 것이다. 사실, 스케줄이 지정된Job들 상에 있는 execute() 메소드를 호출하는 시간은 매우 정확해야 한다. Job 저장과 관리는 Quartz에서 JobStore로 일컬어지는 개념을 통해 이루어진다.

   

Available JobStores

Quartz 프레임워크에서는 두 가지 기본적인 JobStore 타입을 제공한다. Scheculer 정보를 유지하는데 일반적인 메모리(RAM)을 사용하는 첫 번째 타입은 RAMJobStore라 불린다. 이러한 타입의 JobStore는 설정 및 실행이 매우 간단하다. 많은 어플리케이션들에 대해 이러한 JobStore만으로도 충분할 것이다. 그러나, Scheduler 정보가 JVM에 할당되어 있는 메모리에 저장되기 때문에, 어플리케이션이 멈추게 되면, 스케줄과 관련된 모든 정보가 사라진다. 만약 어플리케이션이 재 시작하는 경우에도 이러한 스케줄 정보를 유지할 필요가 있다면, 두 번째 유형의 JobStore을 사용해야 할 것이다.

   

두 번째 타입의 JobStore는 실제로 Quartz 프레임워크에서 두 가지의 서로 다른 형태로 구현되어 제공되고 있지만, 이 둘 모두 일반적으로 JDBC JobStore로 일컬어지고 있다. 이 두 가지 모두의 JDBC JobStore는 JDBC 드라이버를 사용하며, 스케줄 정보를 유지하고 있는 관계형 데이터베이스로부터 정보를 가져온다. 이 두 가지 타입은 데이터베이스 트랜잭션을 제어하는지의 여부나 BEA의 WebLogic이나 JBoss와 같은 어플리케이션 컨테이너에 제어를 넘기는지의 여부에 그 차이점이 존재한다 (이는J2EE에서의 BMT와 CMT 사이의 차이점과 유사하다).

   

두 가지 유형의 JDBC JobStore는 다음과 같다.

   

● JobStoreTX: 트랜잭션을 제어하고 싶은 경우나, 서버 환경 없이 어플리케이션을 운영하려 할 때 사용된다.

● JobStoreCMT: 어플리케이션 서버 환경 내에서 어플리케이션이 운영되며 컨테이너가 트랜잭션을 관리하도록 하고 싶은 경우 사용된다.

   

JDBC JobStore는 어플리케이션이 중지되고 다시 시작된 후에라도 스케줄링 정보를 유지하여 Scheduler가 실행되도록 만들어야 할 경우를 위해 설계되었다.

   

Job and Triggers

Quartz 설계자들은 Job과 스케줄을 분리하였다. Quartz에서 Trigger는 Job이 트리거링 되거나 발생되어야 할 때, Scheduler에게 알려주는데 사용된다. Quartz 프레임워크에서는 간단한 Trigger 타입들을 제공하고 있는데, SimpleTrigger와 CronTrigger가 가장 일반적으로 사용된다.

   

SimpleTrigger는 스케줄을 간단히 발생시키는데 사용될 목적으로 설계되었다. 일반적으로, 주어진 시간에 Job을 발생시켜 (m)초 간격을 두고 여러 번(n) 이를 실행할 필요가 있을 경우, SimpleTrigger가 적합한 선택이 된다. 반면, Job에 요구되는 스케줄링이 복잡할 경우, CronTrigger가 적합할 것이다.

   

CronTrigger는 달력과 유사한 스케줄에 기반하고 있다. 만약 여러분의 Job이 매주 토요일과 일요일을 제외한, 매일 오전 10시30분마다 실행되어야 하는 경우에, CronTrigger가 사용된다. 이 이름이 암시하고 있듯, CronTrigger는 Unix의 cron 표현식을 기반으로 하고 있다. 예를 들면, 다음의 Quartz cron 표현식은 월요일부터 금요일에 걸쳐 매일 오전 10시 15분에 Job을 실행할 것이다.

   

0 15 10 ? * MON-FRI

   

그리고 다음 표현식은 2002, 2003, 2004, 2005년 동안 매월 마지막 금요일 오후 10시 15분에 Job을 실행할 것이다.

   

0 15 10 ? * 6L 2002-2005

   

이러한 작업은 SimpleTrigger로  수행할 수 없다. 이 둘 모두 Job에 사용될 수 있다. 어떠한 것을 사용할 지에 관한 선택은 스케줄링 될 작업 성격에 달려 있다.

   

Scheduling a Job

이제 예제 Job을 살펴보면서 실제 사용에 관한 부분에 대해 토의해보자. 클라이언트가 자신의 FTP 사이트에 파일을 저장할 때마다 부서에 전자 메일로 통지할 필요가 있는 상황을 여러분이 관리하고 있다고 가정해보자. 우리의 Job은 원격 서버에 있는 파일들을 다운로드 하는 것이 된다. 그런 후, Job은 발견된 파일들의 다운로드 횟수를 포함하고 있는 전자 메일을 전송하게 된다. 이Job은 누군가가 하루 동안 이러한 작업을 수작업으로 전송할 필요가 없도록 편리하게 만들어준다. 우리는 이러한 Job이 일주일 내내 하루 24시간 동안 매 60초마다 검사하도록 설정할 수 있다. 이는 바로 Quartz 프레임워크를 완벽하게 사용하는 예이다.

   

첫 번째 단계는 FTP와 Email 로직을 수행하는 Job 클래스를 작성하는 것이다. 다음 예제는 Quartz Job 클래스를 나타낸 것으로, org.quartz.Job 인터페이스를 구현하고 있다.

   

예제 2. FTP 사이트에서 파일들을 다운로드 받고 Email을 전송하는 Quartz Job

   

public class ScanFTPSiteJob implements Job {

    private static Log logger = LogFactory.getLog(ScanFTPSiteJob.class);

   

    /*

     * 정확한 시간에 스케줄러 프레임워크에 의해 호출된다.

    */

    public void execute(JobExecutionContext context) throws JobExecutionException {

        JobDataMap jobDataMap = context.getJobDataMap();

   

        try {

            // FTP 사이트에서 파일들 검사

            File[] files = JobUtil.checkForFiles(jobDataMap);

            JobUtil.sendEmail(jobDataMap, files);

        } catch (Exception ex) {

            throw new JobExecutionException(ex.getMessage());

        }

    }

}

   

이 글에서는 일부러 ScanFTPSiteJob을 매우 간단하게 구성하였다. 또한 이 글에서는 이 예제를 위해 JobUtil이라는 유틸리티 클래스를 작성하였다. 이 클래스는 Quartz의 부분이 아니지만, 다양한 Job들에서 재사용 할 수 있는 여러분만의 유틸리티 성격의 라이브러리를 만드는 것이 좋다. 이 글에서는 Job 클래스와 Quartz Scheduler 내부에 모든 코드들을 쉽게 둘 수도 있었지만, Quartz를 사용하는 것 때문에 재사용을 고려하지 않을 수는 없었다.

   

JobUtil.checkForFiles()와 JobUtil.sendEmail()이 사용하는 파라미터들은 JobDataMap 객체를 사용하고 있는데, 이 객체는Quartz가 생성한 객체이다. 이 인스턴스는 Job이 실행될 때마다 생성되며, 이를 사용해 Job 클래스로 환경 설정 파라미터들을 넘겨준다.

   

JobUtil의 구현 부분은 여기에 나타내지 않았지만, 우리는 FTP와 Email 기능을 모두 구현하고 있는 Jakarta의 Commons Net을 통해 매우 쉽게 사용할 수 있었다.

   

Calling Your Jobs with the Scheduler

Job을 생성하는 것이 첫 번째 작업이지만, Scheduler에 의해 Job이 호출되도록 하기 위해서는 Scheduler에게 얼마나 자주, 언제 Job이 호출되어야 하는지 알려주어야 한다. 이 작업은 Trigger를 Job에 연관시킴으로써 이루어진다. 우리는 Scheduler가 계속 매 60초마다 Job을 호출하는데 관심을 두고 있기 때문에, SimpleTrigger를 사용할 것이다.

   

Job과 Trigger의 스케줄은 Quartz Scheduler 인터페이스를 통해 이루어진다. Scheduler의 인스턴스를 얻기 위해서는 팩토리로부터 인스턴스를 얻어와야 한다. 이를 위한 가장 쉬운 방법은 StdSchedulerFactory 클래스의 static 메소드인getDefaultScheduler()를 호출하는 것이다.

   

Quartz 프레임워크를 사용할 때, 반드시 start() 메소드를 호출하여 Scheduler를 시작시켜야 한다. 예제 3에 있는 코드는 대부분의 Quartz 어플리케이션의 일반적인 패턴을 따르고 있다. 대부분의 Quartz 어플리케이션에서는, 하나 또는 그 이상의 Job들을 생성하고 Trigger들을 생성하고 설정한 후, Scheduler에 Job과 Trigger들에 대한 스케줄을 정하고 Scheduler를 시작시킨다(주: Scheduler를 먼저 시작시켜도 된다. 이는 중요하지 않다).

   

예제 3. Quartz Job들은 Quartz Scheduler를 통해 스케줄이 지정되어야 한다.

   

public class MyQuartzServer {

    public static void main(String[] args) {

        MyQuartzServer server = new MyQuartzServer();

   

        try {

            server.startScheduler();

        } catch(SchedulerException ex) {

            ex.printStackTrack();

        }

    }

   

    protected void startScheduler() throws SchedulerException {

        // 팩토리를 사용해 Scheduler 인스턴스를 생성

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

   

        // JobDetail은 Job들에 대한 정의를 포함한다.

        JobDetail jobDetail = new JobDetail("ScanFTPJob", Scheduler.DEFAULT_GROUP,

                                          ScanFTPSiteJob.class);

        // execute() 내에서 사용될 job 파라미터들을 저장

        jobDetails.getJobDataMap().put("FTP_HOST", "\home\cavaness\inbound");

   

        // 여기에 Job 파라미터들에 필요한 기타 다른 내용들이 온다.

   

        // 매 60초마다 발생하는 Trigger 인스턴스 생성

        Trigger trigger = TriggerUtils.makeSecondlyTrigger(60);

   

        // Scheduler에 Job과 Trigger를 설정

        scheduler.scheduleJob(jobDetail, trigger);

   

        // Scheduler 실행 시작

        scheduler.start();

    }

}

   

Programmatic vs. Declarative Scheduling

예제 3에서, 우리는 프로그래밍을 통해 ScanFTPSiteJob 스케줄을 작성했다. 즉, Scheduler에 Job과 Trigger를 설정하기 위해 자바 코드를 사용하였다는 의미이다. Quartz 프레임워크에서는 XML 파일들에 Job 스케줄을 선언적으로 설정할 수 있는 기능을 지원하고 있다. 선언적인 접근 방법을 통해 어떠한 Job이 언제 실행되어야 하는지를 보다 빠르게 지정할 수 있다.

   

Quartz 프레임워크에는 Quartz 어플리케이션이 시작하자마자, Job과 Trigger 정보를 포함하고 있는 XML 파일을 읽어 들이는"플러그 인"을 포함하고 있다. XML 내에 있는 모든 Job들은 이들과 관련된 Trigger들과 함께 Scheduler에 추가된다. 물론 Job클래스들을 작성해야 하지만, 그러한 Job들을 갖는 Scheduler의 환경을 설정하는 것은 매우 동적으로 이루어진다. 예제 4는 예제 3의 코드에 있는 동일한 로직을 수행하는데, 선언적인 방법으로 구성되어 있다.

   

예제 4. Quartz Job들은 XML 파일을 사용하여 스케줄링 될 수 있다.

   

<?xml version="1.0" encoding="utf-8"?>

<quartz>

    <job>

        <job-detail>

            <name>ScanFTPSiteJob</name>

            <group>DEFAULT</group>

            <description>A job that scans an ftp site for files</description>

            <job-class>ScanFTPSiteJob</job-class>

   

            <job-data-map allows-transient-data="true">

                <entry>

                    <key>FTP_HOST</key>

                    <value>homecavanessinbound</value>

                </entry>

   

                <!-- 다른 필요한 Job 파라미터들을 여기에 둔다 -->

            </job-data-map>

        </job-detail>

   

        <trigger>

            <simple>

                <name>ScanFTPSiteJobTrigger</name>

                <group>DEFAULT</group>

                <job-name>ScanFTPSiteJob</job-name>

                <job-group>DEFAULT</job-group>

                <start-time>2005-09-11 6:10:00 PM</start-time>

                <!-- 계속 60초마다 반복 실행 -->

                <repeat-count>-1</repeat-count>

                <repeat-interval>60000</repeat-interval>

            </simple>

        </trigger>

    </job>

</quartz>

   

예제 4에 있는 XML 엘리먼트들을 예제 3에 있는 자바 코드와 비교해볼 수도 있다. 이들은 개념적으로 동일하다. 예제 4에 보여지는 것과 같은 선언적인 접근 방법의 장점은 유지보수가 매우 간단해진다는 것이다. XML 파일만 변경하고 Quartz 어플리케이션을 재 시작 하기만 하면 되기 때문이다. 소스 코드를 수정하고 재 컴파일 하여 배포할 필요가 없다.

   

Stateful and Stateless Jobs

이 글에서 살펴보았던 Quartz Job 예제는 모두 상태 정보를 가지고 있지 않다. 즉, 각각의 Job이 실행에 대해, Job이 실행되는 동안 JobDataMap에 가해진 어떠한 변경 사항들도 유지되지 않는다는 것을 의미한다. 만약 JobDataMap에 값을 추가, 변경 또는 삭제하는 기능이 필요하며, 다음 실행에 이러한 변경 사항들이 Job에 반영되도록 해야 한다면, Quartz Stateful Job이 필요하다.

   

만약 여러분이 EJB 개발을 경험해 보았다면, Stateful이라는 것이 부정적인 의미를 담고 있다는 점 때문에 여러분은 지금 움찔하고 있을 것이다. 이는 주로 "Stateful EJB"가 가지고 있는 확장성 이슈로부터 기인한다. Quartz Stateful Job은org.quartz.StatefulJob 인터페이스를 통해 구현된다. Stateless Job과 Stateful Job 간의 주요 차이점은, Stateful Job은 한 번에Job을 실행하는 인스턴스가 오직 하나를 가질 수 있다는 점이다. 그러므로, 예제 3의 경우, "ScanFTPJob" Job을 실행하는 인스턴스는 한 번에 하나만 가지게 된다. 대부분의 경우, 이는 커다란 문제점을 나타내지는 않는다. 그러나, 만약 자주 실행될 필요가 있는 Job을 가지고 있다거나, 작업 완료까지 오랜 시간을 필요로 하는 Job을 가지고 있을 경우, Stateful Quartz Job은 확장성에 문제를 가져다 줄 수 있다.

   

Other Features of the Quartz Framework

Quartz 프레임워크는 매우 다양한 기능들을 가지고 있다. 사실, 한 번에 이 모든 기능들을 나열하기에는 너무나 많다. 다음 목록은 이 글에서는 자세히 언급할 시간이 없는 Quartz 내의 여러 기능들 중 몇몇을 간단하게나마 설명한 것이다.

   

Listeners and Plugins

오늘날 어떠한 오픈 소스 프레임워크라도 사용하고 있는 개념이 바로 이 둘이다.

   

Quartz Listener는 주요 이벤트가 발생할 때, 프레임워크 내부로부터 콜백을 받는 자바 클래스이다. 예를 들면, Job이 스케줄 되어 있거나 스케줄 되어 있지 않을 때, 또는 Trigger가 끝났거나, 더 이상 발생시키지 않을 때, 이러한 모든 것들은 Listener로 통지되도록 설정될 수 있다. Quartz 프레임워크는 Scheduler, Job, Trigger들을 위한 Listener들을 포함하고 있다. 또한, Job Listener와 Trigger Listener들을 특정한 한 Job이나 Trigger에 적용되도록 만들거나 전체에 걸쳐 적용되도록 설정할 수도 있다.

   

일단 Listener가 호출되면, 이 정보를 사용해 Listener 클래스 내에서 수행하려는 어떠한 로직이라도 실행시킬 수 있다. 예를 들면, 만약 Job이 완료될 때마다 전자 메일을 보내고 싶은 경우, 이를 Job에 프로그래밍 해 넣을 수 있다. 또한, JobListener를 사용할 수도 있다. 이 JobListener는 결합도를 느슨하게 만들어 보다 나은 설계를 만들어주는데 도움을 줄 수도 있다.

   

Quartz Plugin은 Quartz 소스를 수정하지 않고도 Quartz 프레임워크에 기능을 추가시켜주는 새로운 기능이다. 이것은 Quartz프레임워크를 확장해야 하는데, 변경한 기능을 Quartz 개발 팀에게 보내주고 다음 버전에 반영되기까지 기다릴 시간이 없는 개발자들을 위한 기능이다. 여러분이 Struts 플러그 인에 익숙하다면, Quartz 플러그 인을 사용하는 방법을 더 쉽게 이해할 수 있을 것이다.

   

Clustering Quartz Applications

Quartz 어플리케이션은 여러분의 요구 사항에 따라 수직/수평적으로 모두 클러스터링 될 수 있다. 클리스터링을 통해 다른 클러스터링 타입과 마찬가지의 이점들을 제공받을 수 있다.

   

● 확장성

● 높은 가용성

● 로드 밸런싱

   

현재 Quartz는 관계형 데이터베이스와 JDBC JobStore중 하나의 도움을 통해 클러스터링을 지원한다. 향후 버전에서는, 이러한 제한이 사라져, 데이터베이스 없이도 RAMJobStore에서 사용 가능해질 것이다.

   

The Quartz Web Application

Quartz 프레임워크를 2-3주나 몇 달간 사용한 후 보통 Quartz 사용자들이 가장 많이 요구하는 것들 중 하나는 Quartz를 GUI에 통합하는 것에 대한 것이다. 자바 서블릿을 사용하여 Quartz를 초기화 하고 시작시킬 수 있는 기능이 현재 프레임워크에 제공되고 있다. 일단 여러분이 Scheduler 인스턴스에 접근하게 되면, 이 인스턴스는 웹 컨테이너의 ServletContext에 저장하고Scheduler 인터페이스를 통해 스케줄링 환경을 관리할 수 있다.

   

다행히도, 몇몇 Quartz 개발자들은 Quartz Scheduler 환경을 보다 잘 관리하는데 사용될 수 있는 독립형 Quartz Web Application에 대해 작업해 오고 있다. Struts와 Spring과 같은 다수의 유명한 오픈 소스 프로젝트를 기반으로 구축되어 이 GUI는 많은 기능들을 지원하는데, 이들은 간단한 인터페이스에 랩핑 되어 있다. 그림 1은 이 GUI를 잡아낸 것이다.

   

   

그림 1. Quartz 환경을 보다 쉽게 관리해주는데 도움을 주는 Quartz Web Application

   

What's down the Road?

이미 다음 주요 릴리즈에 대한 움직임도 진행되고 있을 정도로 Quartz 프로젝트는 활발하게 진행되고 있다. OpenSymphony의wiki에서 Quartz 2.0에서 고려되고 있는 기능들과 설계에 대한 정보를 얻을 수도 있다.

   

항상 그렇듯이, 날마다 Quartz 사용자들은 프레임워크에서 고려될 수 있는 갖가지 기능들에 대한 제안이나 설계에 관해 자유롭게 제안하고 있다.

   

Find Out More about Quartz

Quartz 프레임워크의 보다 많은 기능들을 사용해 나갈수록, User and Developer Forum은 Quartz 사용자들과의 질문/답변 및 커뮤니케이션을 위한 매우 유용한 자원이 될 것이다.

   

출처 : http://blog.empas.com/kkamdung/12297998

[출처] Quartz - java 스케쥴러|작성자 순짱

 

===================================================================================

 

가끔 서버에서 주기적으로 어떠한 작업을 하고자 할때 리눅스에서는 크론탭을 사용하여 주기적으로 어떠한 작업을 처리합니다.
이런 주기적 작업을 처리하기위해 Spring에서 지원해 주는 Quartz스케쥴러를 통해 크론탭과 같은 역할을 하는 스케쥴러를 작성할  있습니다.
이번에는 Spring  Quartz 연동하여 스케줄러를 작성해 보겠습니다.

작업순서는 
스프링 기본 세팅 -> Quartz 세팅 순으로 작업하겠습니다.

1. 
스프링 기본 설정
1) springframework.org 
 이동하셔서 스프링 라이브러리를 다운 받습니다

위와 같은 페이지가 뜨면 해당사항을 입력하시고 Access Download  클릭하셔서 다운로드 페이지로 이동합니다. (귀찮으신 분들은 하단의 파란색으로 "download page" 선택하시면 입력하시지 않고도 다운로드 페이지로 이동하실수 있습니다.

많은 버전의 라이브러리  spring-framework-2.5.6.SEC02.zip  다운 받습니다다른 버전을 다운 받으셔도 상관없습니다만 버전에 따라 세팅 내용이 조금  달라지므로 같은 버전의 라이브러리로 진행하는 것이 나을 것같네요~^^.

2) 
이렇게 라이브러리까지 다운로드 받고 나면 Eclipse 같은 IDE에서 Dynamic Web Project 선택하여 Project 한개 생성합니다.
(
저는 SpringQuartz 라는 이름으로 생성했습니다.)

3) 
프로젝트가 생성되면 프로젝트 안에 /WEB-INF/lib 디렉토리에 스프링 라이브러리를 압축  곳에 있는 dist/spring.jar파일을 추가합니다.
 : 프로젝트를 진행하다 보면 위와같이 라이브러리 버전이 없는 jar파일을 그냥 추가하는 경우가 있는데 나중에 라이브러리를 업데이트 해야 할일이 생기게 되면 위와같이 spring.jar 라고 되어있으면 지금 적용되어 있는 버전이  인지 알수가 없습니다그렇기 때문에 항상 라이브러리 추가하실때는 추가하시는 라이브러리의 버전 번호를 파일이름 뒤에 추가하는 습관 들이 시는게 좋습니다
     ex) spring-2.5.6.jar

4) 
프로젝트 안에 생성된 web.xml Spring 사용하기 위한 세팅을 추가해 줍니다.
저는
 Quartz 사용하기 위한 최소한의 Spring 세팅을 해놓았기 때문에 세팅 내용이 단순합니다만약 웹프로젝트와 함께 Quartz 사용하신다면 웹에 맞게 설정하시고 사용하셔야 함을 알려드립니다.^^

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns="http://java.sun.com/xml/ns/javaee" 

xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 

http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/config/applicationContext*.xml</param-value>

</context-param>

</web-app>


5) 쿼츠 라이브러리를 다운로드 받고 라이브러리를 추가해 줍니다.
쿼츠 라이브러리 다운로드 하신다음 압축을 풀어 줍니다.
해당 라이브러리를 프로젝트의 lib 디렉토리에 복사하여 넣어줍니다.
- quartz-all-1.8.3.jar
압축푼 lib 디렉터리의 log4j-1.2.14.jar
압축푼 lib 디렉터리의 slf4j-api-1.5.10.jar
압축푼 lib 디렉터리의 slf4j-log4j12-1.5.10.jar
 추가  줍니다.
마지막으로 apache 
commons-logging-1.1.1.jar  다운로드 하셔서 위와 같이 프로젝트의 lib 추가해주시면 라이브러리 추가는 끝이 납니다.

6) Quartz
 핵심적인 기능을  /WEB-INF/config/applicationConext.xml  작성합니다.
스케쥴러의 핵심 세팅은 3가지 정도 입니다.
하나실제 주기적으로 실행될 클래스 등록
스케줄러가 동작하는 interval time 설정
실제 동작하게  설정

이런 세가지가 있겠습니다.

스케줄러 동작방식에는 두가지가 존재 합니다.
-Simple : interval time
 간단하게 동작하는 방식으로 몇초혹은 몇분몇시간 단위로 작동하고 싶을때 사용합니다
<Simple type setting>

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"

   xmlns:context="http://www.springframework.org/schema/context"

   xmlns:p="http://www.springframework.org/schema/p"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   xsi:schemaLocation="http://www.springframework.org/schema/beans   

                           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

                           http://www.springframework.org/schema/context

                           http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<!-- 하나.주기적으로 실행될 클래스 설정 -->

<!-- property name은 jobClass로 fix, value는 사용자가 작성한 class 파일 위치 -->

<bean id="simpleQuartzJob" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="jobClass" value="net.test.quartz.SimpleQuartzJob"/>

</bean>


<!-- 둘.스케줄러의 interval time 설정 -->

<!-- 쿼츠에는 아래와 같이 몇초에 한번씩 돌게 하는 Simple type 과 -->

<!-- 무슨 요일 몇시에 한번씩 돌게 하는 날짜로 지정하는 Cron type 이 있다. -->

<!-- 현재는 Simple type으로 세팅 -->

<!-- jobDetail은 위에서 설정한 실제 동작할 클래스 id를 적어준다 -->

<!-- startDelay는 서버 시작후 몇초 뒤에 시작할지 세팅(ms 단위)  -->

<!-- repeatInterval은 몇 초에 한번씩 실행될 건지 세팅(ms 단위: 현재 1초) -->

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">

<property name="jobDetail" ref="simpleQuartzJob"/>

<property name="startDelay" value="1000"/>

<property name="repeatInterval" value="1000"/>

</bean>

<!--셋. 실제 동작하게끔 설정 -->

<!--ref bean은 위에서 설정한 interval time 아이디를 넣어주면 됨  -->

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

<property name="triggers">

<list>

<ref bean="simpleTrigger"/>

</list>

</property>

<!-- Quartz 실행시 세팅 -->

<property name="quartzProperties">

<props>

<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>

     <prop key="org.quartz.threadPool.threadCount">5</prop>

     <prop key="org.quartz.threadPool.threadPriority">4</prop>

     <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>

     <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>

</props>

</property>

</bean>

</beans>



-Cron : linux  Cron tab  같은 역할을 하는 타입니다 몇월몇일 몇시에 동작하게 하고 싶으면 Cron type 사용하시면 됩니다<?xml version="1.0" encoding="UTF-8" ?>.
<Cron type setting>

<beans xmlns="http://www.springframework.org/schema/beans"

   xmlns:context="http://www.springframework.org/schema/context"

   xmlns:p="http://www.springframework.org/schema/p"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   xsi:schemaLocation="http://www.springframework.org/schema/beans   

                           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

                           http://www.springframework.org/schema/context

                           http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<!--하나. 주기적으로 실행될 클래스 설정 -->

<bean id="cronQuartzJob" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="jobClass" value="net.test.quartz.CronQuartzJob"/>

</bean>

<!--둘. 스케줄러의 interval time 설정-->

<!--cronExpression을 통해서 스캐줄러 주기를 설정한다. -->

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">

<property name="jobDetail" ref="cronQuartzJob"/>

<property name="cronExpression" value="0/1 * * * * ?"/>

</bean>

<!--셋. 실제 동작하게끔 설정 -->

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

<property name="triggers">

<list>

<ref bean="cronTrigger"/>

</list>

</property>

<property name="quartzProperties">

<props>

<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>

     <prop key="org.quartz.threadPool.threadCount">5</prop>

     <prop key="org.quartz.threadPool.threadPriority">4</prop>

     <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>

     <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>

</props>

</property>

</bean>

</beans>


Cron type 사용하려면 CronExpression 알아야 합니다.

*Cron Expression
cron expression
 각각의 필드는 다음을 나타낸다.(왼쪽 -> 오른쪽 )

 필드 이름

 허용 

 허용된 특수 문자

 Seconds

 0 ~ 59

 , - * /

 Minutes

 0 ~ 59

 , - * /

 Hours

 0 ~ 23

 , - * /

 Day-of-month

 1 ~ 31

 , - * ? / L W

 Month

 1 ~12 or JAN ~ DEC

  , - * /

 Day-Of-Week

 1 ~ 7 or SUN-SAT

 , - * ? / L #

 Year (optional)

 empty, 1970 ~ 2099

 , - * /


Cron Expression 
 특수문자
'*' : 
모든 수를 나타냄분의 위치에 * 설정하면 "  마다" 라는 .
'?' : day-of-month 
 day-of-week 필드에서만 사용가능특별한 값이 없음을 나타낸다.
'-' : "10-12" 
 같이 기간을 설정한다시간 필드에 "10-12" 이라 입력하면 "10, 11, 12시에 동작하도록 설정" 이란 .
',' : "MON,WED,FRI"
 같이 특정 시간을 설정할  사용한다. "MON,WED,FRI" 이면 " ',,에만 동작" 이란 .
'/' : 
증가를 표현합니다예를 들어  단위에 "0/15" 세팅 되어 있다면 "0 부터 시작하여 15 이후에 동작" 이란 .
'L' : day-of-month 
 day-of-week 필드에만 사용하며 마지막날을 나타냅만약 day-of-month  "L"  되어 있다면 이번달의 마지막에 실행하겠다는 것을 나타냄.
'W' : day-of-month 
필드에만 사용되며주어진 기간에 가장 가까운 평일(~) 나타낸다만약 "15W" 이고 이번 달의15일이 토요일이라면 가장가까운 14 금요일날 실행된다 15일이 일요일이라면 가장 가까운 평일인 16 월요일에실행되게 된다만약 15일이 화요일이라면 화요일인 15일에 수행된다.
"LW" : L
 W 결합하여 사용할  있으며 "LW" "이번달 마지막 평일" 나타냄
"#" : day-of-week
 사용된다. "6#3" 이면 3(3)번째  금요일(6) 이란 뜻이된다.1 일요일 ~ 7 토요일 

 Expression

 Meaning

 "0 0 12 * * ?"

 매일 12시에 실행

 "0 15 10 ? * *"

 매일 10 15분에 실행

 "0 15 10 * * ?"

 매일 10 15분에 실행

 "0 15 10 * * ? *"

 매일 10 15분에 실행

 "0 15 10 * * ?  2010" 

 2010 동안 매일 10 15분에 실행

 "0 * 14 * * ?"

 매일 14시에서 시작해서 14:59  끝남

 "0 0/5 14 * * ?"

 매일 14시에 시작하여 5 간격으로 실행되며 14:55분에끝남

 "0 0/5 14,18 * * ?"

 매일 14시에 시작하여 5 간격으로 실행되며 14:55분에끝나고매일 18시에 시작하여 5분간격으로 실행되며18:55분에 끝난다.

 "0 0-5 14 * * ?"

 매일 14시에 시작하여 14:05 분에 끝난다.


시간에 맞춰 돌아가는 스케줄러에서 다른 클래스를 사용하고 싶을 때는 다음과 같이 설정합니다.

<!-- 스프링 DI : 사용할 Service 객체를 생성 -->

<bean id="quartzJobService" class="net.test.quartz.service.impl.QuartzJobServiceImpl"/>


<bean id="simpleQuartzJob" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="jobClass" value="net.test.quartz.SimpleQuartzJob"/>

<!-- 사용하고자 하는 class의 bean id를 등록 -->

<property name="jobDataAsMap">

<map>

<entry key="quartzJobService">

<ref local="quartzJobService"/>

</entry>

</map>

</property>

</bean>



 *두가지 스케줄러를 동시에 실행 시킬때

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

<property name="triggers">

<!--트리거를 두개 생성후 아래와 같이 세팅 -->

<list>

<ref bean="simpleTrigger"/>

<ref bean="cronTrigger"/>

</list>

</property>

<property name="quartzProperties">

<props>

<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>

     <prop key="org.quartz.threadPool.threadCount">5</prop>

     <prop key="org.quartz.threadPool.threadPriority">4</prop>

     <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>

     <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>

</props>

</property>

</bean>



7) 실제 작동할 Class파일 생성

package net.test.quartz;


import net.test.quartz.service.QuartzJobService;


import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

import org.springframework.scheduling.quartz.QuartzJobBean;


public class SimpleQuartzJob extends QuartzJobBean{

//실행될 클래스는 꼭 QuartzJobBean을 상속받아야 되며 

//executeInternal method를 override 하면 자동으로 이 메소드가 실행


//Spring의 DI를 사용하여 Service객체를 setting

//DI를 사용하지 않는다면 필요 없는 부분

private QuartzJobService quartzJobService;

public void setQuartzJobService(QuartzJobService quartzJobService) {

this.quartzJobService = quartzJobService;

}



@Override

protected void executeInternal(JobExecutionContext ex)throws JobExecutionException {

quartzJobService.printLog();

}

}


위와 같은 방식으로 Spring Quartz 사용하여 스케줄러를 사용할  있습니다.

함께 업로드 하는 파일은 제가 직접 작업한 프로젝트이구요함께 확인  보시면 쉽게 쿼츠를 사용하실수 있으실 겁니다.~^^

 SpringQuartz.war

 

출처 - http://javastore.tistory.com/96


===================================================================================


매번 cronTab 으로 배치작업을 실행시키며, 이게 아직 쓰는 놈인지, 언제 만들었는지,
수정하기 번거롭네 등 에로사항이 많았으.. 기억도 안나고.. 그때그때 만든 것들은 버전관리도 안되고,,
해서. 스프링 프로젝트이니, 스프링에서 동작하는 배치를 쓰는게 낫겠다는 생각이 들어 이참에, 배치를 몽땅 quartz 
로 만들어서 정리를 해야겠다는 생각이 들었심.

스프링 프로젝트 환경이 세팅이 되어있다고 치고,

 

*필자의 경우는 
Dynamic Web Project 로 qbatch 라는 프로젝트를 만들었고,


디렉토리 구조는
qbatch
  |_src
  |_WebContent
          |_index.jsp
          |_META-INF
          |_WEB-INF
              |_classes
              |_lib
              |_views
              |_action-servlet.xml
              |_web.xml
              
이렇게 되어있고, 
src 폴더에는 비지니스를 구현한 java 파일들(cotroller/service/dao 등),
classes 에는 src 의 컴파일된 class 파일들 과 applicationContext.xml 및 각종 properties 파일들,
lib 폴더에는 spring.jar, activation.jar, log4j.jar, mysql-connector-java-xxx.jar 등이,
views 에는 각종 jsp 파일들이 위치해있다.

 

암튼 시작해보면,

 

1. 일단 quartz 라이브러리를 다운받는다.
http://terracotta.org/downloads/open-source/destination?name=quartz-1.8.5.tar.gz&bucket=tcdistributions&file=quartz-1.8.5.tar.gz
압축을 풀고 quartz-all-1.8.5.jar 파일을 찾아서 
WEB-INF/lib 폴더 밑에 복사해 넣으면 준비끝.

 

2. 실제 비지니스를 실행할 java 파일을 만든다.            
나의 비지니스 로직은 System.out.println("음. 배치가 실행되었구만~!!!!"); 이다.
이 로직만 빼고, 나머지는 코드는 동일하게 쓰면되심.

src/com/batch/service/SystemOutTestService.java 를 아래와같이 만든다.

 

 

package com.batch.service;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class SystemOutTestService extends QuartzJobBean {
 
 
 @Override
 protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
  try {
   
   System.out.println("음. 배치가 실행되었구만~!!!!");
  } catch (Exception e) {
   e.printStackTrace();
  }
  
 }

}

 


3. 이제 저 SystemOutTestService.java 가 실행스케줄링 되도록 applicationContext.xml 을 만져주자.
1. 실행할 비지니스로직 클래스를 빈으로 등록.
2. 해당빈이 스케줄을 만들 트리거 설정
3. 실제 동작되도록 설정.

이렇게 3가지만 세팅하면 되심. 아래와같이.

<beans 어쩌구저쩌구.... >

<!-- 1. 실행할 비지니스로직 클래스를 빈으로 등록. -->
<bean id="SystemOutTest" class="org.springframework.scheduling.quartz.JobDetailBean">
 <property name="jobClass" value="com.batch.service.SystemOutTestService"/>
 </bean>

<!-- 2. 해당빈이 스케줄을 만들 트리거 설정 -->
 <bean id="SystemOutTestTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
  <property name="jobDetail" ref="SystemOutTest"/>
  <property name="repeatInterval" value="3000"/> <!--  every 1000(1초)  -->
  <property name="startDelay" value="2000" /><!--  at first execution 2000(2초) 후에 실행  -->
 </bean>
 
<!-- 3. 실제 동작되도록 설정. -->
 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" autowire="no">
  <property name="triggers">
   <list>
    <ref bean="SystemOutTestTrigger"/>
   </list>
  </property>
  <property name="quartzProperties">
   <props>
    <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
    <prop key="org.quartz.threadPool.threadCount">5</prop>
    <prop key="org.quartz.threadPool.threadPriority">4</prop>
    <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
    <prop key="org.quartz.jobStore.misfireThreshold">600000</prop>
   </props>
  </property>
 </bean>
 
 
이제 톰캣 재시작 하면 3초마다 "음. 배치가 실행되었구만~!!!!" 이라고 찍힐거임.
비지니스 로직이 넘 초라하니, 클래스 추가하여 실제 필요한 동작을 하게끔 비지니스 로직에
시간을 들이심이 좋을듯 해서 초간단으로 적었심. 더 세심한 설정은 구글형이나 네이년에
물어보시면 찾으실 수 있으실거임.


출처 - http://jejoong76.blog.me/70125014485


===================================================================================


Spring에서 Quartz JOB 설정 예제

  • 한 시간에 한번 keyGeneratorJob을 실행 시키는 예제 입니다.

applicationContext.xml 설정

<bean id="keyPool" class="com.oracleclub.web.support.KeyPool" init-method="init" lazy-init="true">
  <property name="keySize" value="1000"/>		
</bean>

<!-- jobClass 설정 -->
<bean id="keyGeneratorJob" class="org.springframework.scheduling.quartz.JobDetailBean">
  <property name="jobClass" value="com.oracleclub.web.job.KeyGeneratorJob"/>
  <property name="jobDataAsMap">
	<map>
      <entry key="keyPool">
   	    <ref bean="keyPool"/>
  	  </entry>
 	</map>
  </property>
</bean>

<!-- jobTrigger 설정 -->
<bean id="keyGeneratorTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
  <property name="jobDetail" ref="keyGeneratorJob" />
  <!--  1000 == 1 second, 60minuts=3600000, 1day=86400000-->
  <property name="startDelay" value="3600000" />
  <property name="repeatInterval" value="3600000" />
</bean>

<bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" >		
  <property name="triggers">
    <list>
      <ref bean="keyGeneratorTrigger"/>
    </list>
  </property>
</bean>

JAVA 예제 소스

  • 위에서 JobClass로 지정한 com.oracleclub.web.job.KeyGeneratorJob 소스 입니다.
  • 아래 소스는 한 시간에 한번씩 암호화 키를 다시 생성하는 예제 입니다.
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

/**
 * Key Generator Job
 * 
 * @author : oramaster
 * 
 */
public class KeyGeneratorJob extends QuartzJobBean {
  private KeyPool keyPool;
  private final static Log LOG = LogFactory.getLog(KeyGeneratorJob.class);


  @Override
  protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

    List<KeyGenerator> newList = new ArrayList<KeyGenerator>();
    KeyGenerator keyGenerator;

    try {
      //배치때 실행되는 비지니스 로직
      for (int i = 0; i < keyPool.getKeySize(); i++) {
        keyGenerator = new KeyGenerator();
        byte[] originKey = XORMask.getGeneratorKey();

        keyGenerator.setBlowFishKey(BlowFish.encrypt(originKey, BlowFish.getKey()));
        keyGenerator.setOriginKey(originKey);

        newList.add(keyGenerator);
      }

      keyPool.setKeyList(newList);

    } catch (Exception e) {
      LOG.error(e.getMessage());
      throw new JobExecutionException(e.getMessage());
    }
  }

  public void setKeyPool(KeyPool keyPool) {
    this.keyPool = keyPool;
  }
}

문서정보


===================================================================================


Chapter 19. Quartz 혹은 Timer 를 사용한 스케쥴링

19.1. 소개

Spring은 스케쥴링을 지원하는 통합 클래스들을 제공한다. 현재적으로, Spring은 1.3 이후버전 JDK의 일부분인 Timer와 Quartz 스케쥴러 (http://www.quartzscheduler.org)를 지원하고 있다. 이 두개의 스케쥴러들은 각각 Timer 혹은 Triggers에 대한 선택적 참조를 가지는 FactoryBean을 사용하여 세팅된다. 게다가 당신이 타겟 object의 메써드를 편리하게 호출할 수 있도록 도와주는 Quartz 스케쥴러와 Timer에 대한 편의 클래스를 제공한다.(이것은 일반적인 MethodInvokingFactoryBeans와 비슷하다.)

19.2. OpenSymphony Quartz 스케쥴러 사용하기

Quartz는 TriggersJobs 그리고 모든 종류의 jobs를 인식하고 있는 JobDetail를 사용한다. Quartz에 깔려 있는 기본적인 개념을 알고 싶다면, http://www.opensymphony.com/quartz를 찾아보길 바란다. 편리한 사용을 위해서, Spring은 Spring 기반 어플리케이션 내에서 Quartz의 사용을 손쉽게 만들어주는 두 개의 클래스들을 제공한다.

19.2.1. JobDetailBean 사용하기

JobDetail 객체는 job을 실행하기 위해 필요한 모든 정보를 가지고 있다. Spring은 소위 JobDetailBean이라고 불리는 클래스를 제공하는데, 이것은 JobDetail을 합리적인 디폴트값을 가진 실질적인 JavaBean 객체로 만들어준다. 다음의 예제를 보도록 하자.

<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
  <property name="jobClass">
    <value>example.ExampleJob</value>
  </property>
  <property name="jobDataAsMap">
    <map>
      <entry key="timeout"><value>5</value></entry>
    </map>
  </property>
</bean>
			

위의 job detail bean은 job(ExampleJob)을 실행하기 위한 모든 정보를 가지고 있다. 타임아웃은 job data map으로 기술되었다. job data map은 (실행시 넘겨지는) JobExecutionContext를 통해 이용할 수 있지만, JobDetailBean 역시 job data map으로부터 실질적인 job의 프라퍼티들을 매핑할 수 있다. 때문에 이러한 경우, 만약 ExampleJob이 timeout이라는 프라퍼티를 가지고 있다면, JobDetailBean은 그것을 자동으로 적용할 것이다.

package example;

public class ExampleJob extends QuartzJobBean {

  private int timeout;
  
  /**
   * Setter called after the ExampleJob is instantiated
   * with the value from the JobDetailBean (5)
   */ 
  public void setTimeout(int timeout) {
    this.timeout = timeout;
  }
  
  protected void executeInternal(JobExecutionContext ctx)
  throws JobExecutionException {
      // do the actual work
  }
}
			

당신은 job detail bean의 모든 부가적인 세팅들 역시 마찬가지로 이용할 수 있다.

주의: name과 group 프라퍼티를 사용함으로써, 당신은 job의 name과 group을 변경할 수 있다. default로 job의 이름은 job detail bean의 이름과 동일하다.(위의 예에서는 exampleJob이 된다.)

19.2.2. MethodInvokingJobDetailFactoryBean 사용하기

종종 당신은 특정한 객체의 메써드를 호출할 필요가 있을 것이다. 당신은 MethodInvokingJobDetailFactoryBean을 사용하여 다음과 같이 할 수 있다.

<bean id="methodInvokingJobDetail" 
  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject"><ref bean="exampleBusinessObject"/></property>
    <property name="targetMethod"><value>doIt</value></property>
</bean>

위의 예는 (아래에 있는) exampleBusinessObject의 doIt을 호출하는 것을 의미한다.

public class BusinessObject {
  
  // properties and collaborators
  
  public void doIt() {
    // do the actual work
  }
}
			

<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
			

MethodInvokingJobDetailFactoryBean을 사용할 때, 메써드를 호출할 한줄짜리 jobs를 생성할 필요가 없으며, 당신은 단지 실질적인 비지니스 객체를 생성해서 그것을 묶기만 하면된다.

default로는 Quartz Jobs는 비상태이며, 상호 작용하는 jobs의 가능성을 가진다. 만약 당신이 동일한 JobDetail에 대해 두 개의 triggers를 명시한다면, 첫번째 job이 끝나기 이전에 두번째가 시작할지도 모른다. 만약 JobDetail 객체가 상태 인터페이스를 구현한다면, 이런 일은 발생하지 않을 것이다. 두번째 job은 첫번째가 끝나기 전에는 시작하지 않을 것이다. MethodInvokingJobDetailFactoryBean를 사용한 jobs가 동시작용하지 않도록 만들기 위해서는, concurrent 플래그를 false로 세팅해주어야 한다.

<bean id="methodInvokingJobDetail"
  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject"><ref bean="exampleBusinessObject"/></property>
    <property name="targetMethod"><value>doIt</value></property>
    <property name="concurrent"><value>false</value></property>
</bean>
			

주의: 기본적으로 jobs는 concurrent 옵션에 따라 실행될 것이다.

19.2.3. triggers 와 SchedulerFactoryBean을 사용하여 jobs를 묶기

우리는 job details과 jobs를 생성했고, 당신이 특정 객체의 메써드를 호출할 수 있도록 하는 편의클래스 bean을 살펴보았다. 물론, 우리는 여전히 jobs를 그자체로 스케쥴할 필요가 있다. 이것은 triggers와 SchedulerFactoryBean을 사용하여 이루어진다. 여러가지 triggers는 Quartz 내에서 이용할 수 있다. Spring은 편의를 위해 2개의 상속받은 triggers를 기본적으로 제공한다.:CronTriggerBean과 SimpleTriggerBean이 그것이다.

Triggers는 스케쥴될 필요가 있다. Spring은 triggers를 세팅하기 위한 프라퍼티들을 드러내는 SchedulerFactoryBean을 제공하고 있다. SchedulerFactoryBean은 그 triggers와 함께 실질적인 jobs를 스케쥴한다.

다음 두가지 예를 보자.

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
  <property name="jobDetail">
    <!-- see the example of method invoking job above -->    
    <ref bean="methodInvokingJobDetail"/>
  </property>
  <property name="startDelay">
    <!-- 10 seconds -->
    <value>10000</value>
  </property>
  <property name="repeatInterval">
    <!-- repeat every 50 seconds -->
    <value>50000</value>
  </property>
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
  <property name="jobDetail">
    <ref bean="exampleJob"/>
  </property>
  <property name="cronExpression">
    <!-- run every morning at 6 AM -->
    <value>0 0 6 * * ?</value>
  </property>
</bean>
			

OK, 이제 우리는 두 개의 triggers를 세팅했다. 하나는 10초 늦게 실행해서 매 50초마다 실행될 것이고, 다른 하나는 매일 아침 6시에 실행될 것이다. 모든 것을 완료하기 위해서, 우리는 SchedulerFactoryBean을 세팅해야 한다.

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
    <list>
      <ref local="cronTrigger"/>
      <ref local="simpleTrigger"/>
    </list>
  </property>
</bean>
			

당신이 세팅할 수 있는 더욱 많은 속성들이 SchedulerFactoryBean에 있다. 이를테면, job details에 의해 사용되는 calendars라던가, Quartz를 커스터마이징할 수 있게 하는 프라퍼티같은 것들이 말이다. 더 많은 정보를 위해서는 JavaDoc(http://www.springframework.org/docs/api/org/springframework/scheduling/quartz/SchedulerFactoryBean.html)을 참조하도록 해라.

19.3. JDK Timer support 사용하기

Spring에서 스케쥴링 업무를 처리하는 또다른 방법은 JDK Timer 객체들을 사용하는 것이다. Timers 자체에 대한 더 많은 정보는http://java.sun.com/docs/books/tutorial/essential/threads/timer.html에서 찾아볼 수 있다. 위에서 살펴 본 기본개념들은 Timer support에도 마찬가지로 적용된다. 당신은 임의의 timers를 생성하고 메써드들을 호출하기 위해 timer를 사용한다. TimerFactoryBean을 사용하여 timers를 묶는다.

19.3.1. 임의의 timers 생성하기

당신은 TimerTask를 사용하여 임의의 timer tasks를 생성할 수 있다. 이것은 Quartz jobs와 유사하다

public class CheckEmailAddresses extends TimerTask {

  private List emailAddresses;
  
  public void setEmailAddresses(List emailAddresses) {
    this.emailAddresses = emailAddresses;
  }
  
  public void run() {
    // iterate over all email addresses and archive them
  }
}
			

이것을 묶는 것 역시 간단하다:

<bean id="checkEmail" class="examples.CheckEmailAddress">
  <property name="emailAddresses">
    <list>
      <value>test@springframework.org</value>
      <value>foo@bar.com</value>
      <value>john@doe.net</value>
    </list>
  </property>
</bean>

<bean id="scheduledTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
  <!-- wait 10 seconds before starting repeated execution -->
  <property name="delay">
    <value>10000</value>
  </property>
  <!-- run every 50 seconds -->
  <property name="period">
    <value>50000</value>
  </property>
  <property name="timerTask">
    <ref local="checkEmail"/>
  </property>
</bean>
			

task를 단지 한번만 실행하고자 한다면, period 속성을 -1(혹은 다른 음수값으)로 바꿔주면 된다.

19.3.2. MethodInvokingTimerTaskFactoryBean 사용하기

Quartz support와 비슷하게, Timer 역시 당신이 주기적으로 메써드를 호출할 수 있도록 하는 요소들을 기술한다.

<bean id="methodInvokingTask" 
  class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
    <property name="targetObject"><ref bean="exampleBusinessObject"/></property>
    <property name="targetMethod"><value>doIt</value></property>
</bean>

위의 예제는 (아래와 같은) exampleBusinessObject에서 호출되는 doIt에서 끝날 것이다

public class BusinessObject {
  
  // properties and collaborators
  
  public void doIt() {
    // do the actual work
  }
}
			

ScheduledTimerTask가 언급된 위의 예제의 참조값을 methodInvokingTask로 변경하면 이 task가 실행될 것이다.

19.3.3. 감싸기 : TimerFactoryBean을 사용하여 tasks를 세팅하기

TimerFactoryBean은 실질적인 스케쥴링을 세팅한다는 같은 목적을 제공한다는 점에서 Quartz의 SchedulerFactoryBean과 비슷하다. TimerFactoryBean는 실질적인 Timer를 세팅하고 그것이 참조하고 있는 tasks를 스케쥴한다. 당신은 대몬 쓰레드를 사용할 것인지 말것인지를 기술할 수 있다.

<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
  <property name="scheduledTimerTasks">
    <list>
      <!-- see the example above -->
      <ref local="scheduledTask"/>
    </list>
  </property>
</bean>
			

끝!


출처 - http://openframework.or.kr/framework_reference/spring/ver1.2.2/html/scheduling.html

Posted by 1010
56. Eclipse Etc.../Eclipse2014. 1. 22. 14:42
반응형

[Eclipse]Courier New 폰트

 

 

증상 : Eclipse Indigo> Window-Preference-General-Appearance-Colors and Font 에서 Courier New 폰트가 존재하지 않는 현상

 

해결 방법 : C:\Windows\Fonts 에서 Courier New 폰트를 우클릭 - 표시 하여 숨김 해제 한다. 

 

Posted by 1010
반응형

1. 개요


나는 원래 Maven 을 사용해 본 적이 없다.

기존의 시스템을 운영하고 유지보수하는 일이 주 업무였기때문에 새로운 기술을  접할 기회가 없었다.


하지만 최근 새 시스템을 운영하기 위해 인수인계를 받고 있는데, 이쪽은 Maven 으로 프로젝트를 관리하고 있었다.

이번 기회에 Maven 을 접해보고 장단점을 실제로 느껴보자.

  

메이븐은 이클립스의 플러그인들 중 하나다.

메이븐의 주요 기능은 개발자가 정의한 자바 프로젝트에서 필요로 하는 각종 라이브러리들과 이들의 종속성을 자동으로 관리해주는 것이다.

 

출처 : http://likebnb.tistory.com/128 

 

Maven 으로 신규 프로젝트를 생성하는 방법은 http://likebnb.tistory.com/128 에 잘 설명되어 있다.

 

내가 필요한건 기존에 Maven 으로 관리하던 프로젝트를 이식받는 방법이다.

아래의 과정을 따라가 보자

 

1. Import - Maven - Check out Maven Projects from SCM 선택

2. 다음 화면에서 SCM URL 을 선택할 수 없음

3. Find more SCM connector 라는 메세지를 클릭

4. Marketplace 에서 SCM 으로 Maven 용 connector 검색

5. 관련 플러그인 설치

6. Eclipse restart 후 다시 Import - Check out Maven Projects from SCM 하면 Svn URL 을 입력해줄 수 있다.

7. Svn URL 입력 후 Checking Out

 

 

그런데 Checking Out 이 진행되다가 한순간 사라져버린다..

왜 그럴까? 




 

2. 문제(1) - 네트웍 프락시 설정


지금 내 PC 에서 인터넷에 접근하기 위해서는 사내 proxy 를 무조건 타게 되어 있다.

maven 은 관련 lib update 등을 위해서는 인터넷의 maven repository 에 접근해야하고, 마찬가지로 proxy 설정이 필요하다.

maven 설치 후 C:\Documents and Settings\사용자\.m2 에 settings.xml 이 없다면 아래와 같이 만들어 주자

(이전 글에서 이클립스에 네트웍 설정으로 세팅한 proxy ip 와 동일한 값이다)

 

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
   <proxies>
    <proxy>
       <active>true</active>
       <protocol>http</protocol>
       <host>70.10.15.10</host>
       <port>8080</port>
     </proxy>
 </proxies>
</settings>

 





2. 문제(2) - Library Jars 인식 불가


maven project 를 check out 후 maven 을 실행해보니 에러가 난다.

자세히 보니 porm.xml 도 에러파일 표시가 나타나고,  buildpath 에서 확인해보면 maven repository 에서 일부 libs 들이 missing 상태로 되어 있다.

물론 관련 jars 들은 프로젝트의 /libs 에 별도로 저장되어 있고, check out 받았다.

 

이러때는 어떻게 해야 할까?

수동으로 찾아서 C:\Users\sds\.m2\repository 아래 디렉토리를 만들어주고 하나하나 챙겨줘야 하나?

위 방법처럼 관리할 바엔 maven을 사용하지 않는게 낫겠다.


인터넷으로는 해결 방법을 찾을 수 없어 http://socurites.com 주인장께 직접 가름침 받았다.


원인은 Maven에서 네트웍 오류 등으로 LastUpdated 실패 내역이 남아있으면 네트웍 문제가 해결된 뒤에도 정상 작동하지 않기 때문이었다.

에러로그를 확인하고 관련된 lattUpdated 파일을 찾아서 지워주자.


[에러메세지 예시]

ArtifactDescriptorException: Failed to read artifact descriptor for asm:asm:jar:3.3: ArtifactResolutionException: Failure to transfer asm:asm:pom:3.3 from http://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced. Original error: Could not transfer artifact asm:asm:pom:3.3 from/to central (http://repo.maven.apache.org/maven2): C:\Users\사용자\.m2\repository\asm\asm\3.3\asm-3.3.pom.ahc761bb4996fb34ed7 (지정된 파일을 찾을 수 없습니다) 


[C:\Users\사용자\.m2\repository\asm\asm\3.3\asm-3.3.pom.lastUpdated 파일 내용 ]

#NOTE: This is an internal implementation file, its format can be changed without prior notice.

#Tue Jul 09 18:43:57 KST 2013

http\://www.datanucleus.org/downloads/maven2/.error=

legacy-file\:../../local.repository/trunk/.lastUpdated=1373362730367

https\://repository.jboss.org/nexus/content/repositories/thirdparty-uploads/.lastUpdated=1373363027302

http\://maven.springframework.org/milestone/.lastUpdated=1373363037968

http\://repo.maven.apache.org/maven2/.error=Could not transfer artifact asm\:asm\:pom\:3.3 from/to central (http\://repo.maven.apache.org/maven2)\: C\:\\Users\\사용자\\.m2\\repository\\asm\\asm\\3.3\\asm-3.3.pom.ahc761bb4996fb34ed7 (\uC9C0\uC815\uB41C \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4)

file\:../../local.repository/trunk/.error=Could not transfer artifact asm\:asm\:pom\:3.3 from/to local.repository (file\:../../local.repository/trunk)\: No connector available to access repository local.repository (file\:../../local.repository/trunk) of type legacy using the available factories AsyncRepositoryConnectorFactory, WagonRepositoryConnectorFactory

http\://www.datanucleus.org/downloads/maven2/.lastUpdated=1373362730738

http\://maven.springframework.org/milestone/.error=

da39a3ee5e6b4b0d3255bfef95601890afd80709@70.10.15.10\:8080>default-http\://repo.maven.apache.org/maven2/.lastUpdated=1373362730361

https\://repository.jboss.org/nexus/content/repositories/thirdparty-uploads/.error=



해당 파일을 삭제해도 porm.xml 의 error 가 사라지지 않는다면 일부 .m2\repository\ 아래 문제가 되는 일부 lib 디렉토리를 지워버리고 새로 업데이트 해보자.

porm.xml 에 걸려있던 error 들이 한번에 해결되는 경우도 있다.



이제 Maven 실행에 문제가 없을 것이다.


추가로 Maven 은 기본적으로 /test 디렉토리를 가지고 있기 때문에 

소스코드 내에 /test 디렉토리가 없으면 역시 에러가 발생한다.

프로젝트 프로퍼티에서 /test (missing) 을 확인하고 remove 해주던지, /test 를 만들어주던지 하자


출처 : http://originalchoi.tistory.com/category/%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD

Posted by 1010
반응형

 인터넷이 되지 않는 내부망에서 spring project 생성시 caching에 문제가 생길수 있다.

이때 spring-context-support.jar and spring-context.jar on the classpath. 를 잡아주면 어느정도 문제점은 해결된다.

eclipse 가 제대로 인식하지 못하는 버그가 있음

element 'cache:annotation-driven' of schema namespace 'http://www.springframework.org

you need spring-context-support.jar and spring-context.jar on the classpath.

 

Posted by 1010