Tag Archives: 자바

Welcome to Struts 2

Struts 2와 관련된 국내 문서가 너무 없는거 같아 http://struts.apache.org 의 기본문서를 토대로 기본 프로젝트를 하나 만들어 보았습니다.

지극히 초보분들을 위한 내용이니, 고수 분들은 ‘뒤로’를 눌러주시면 되겠습니다^^;

기본적인 HelloWorld 프로젝트를 생성합니다. 스트럿츠 관련 라이브러리를 추가합니다. (WEB-INF/lib/)

미니멈하게 필요한 라이브러리 파일은 다음과 같습니다.



  • struts2-core.jar : 스트럿츠2의 코어 라이브러리입니다.
  • xwork.jar : 스트럿츠2의 새로운 점중 가장 큰 부분입니다. Webwork(= Xwork)와 통합 되었는데요. xwork가 주가 되고 struts가 뒷받침 해주는 형식으로 작동합니다.
  • ognl.jar : Object Graph Navigation Language라고 합니다. struts2를 위한 EL(Expression Language)라고 하는군요. JSP 2.1 스펙에 포함된 EL과 거진 비슷한거 같습니다. 좀더 편한거 같긴한데 struts2의 퍼포먼스를 좀먹는 녀석이라고 하네요. 이녀석이 꼭 있어야 한다니..OTL
  • freemarker.jar : UI 태그 탬플릿을 위해 있는 녀석입니다. 벨로시티를 생각하시면 되겠습니다.
  • commons-logging.jar : log4j와 같은 로깅을 위한 라이브러리입니다.


다음의 소스들을 보시면 느끼시겠지만, 예전의 서블릿 매핑이 아닌 필터 매핑을 하고 있습니다.

눈에 띄는것부터 보자면 excute함수의 인자가 하나도 없군요. 컨트롤러에서 특정 로직을 수행후에 view로 리턴해 줄때 열심히 setAttribute하던것도 없어졌습니다. 그냥 변수에 담아두면 가져다 쓸수 있군요.

SUCCESS를 많이 써서 그럴까요? 리턴할떄 SUCCESS가 기본 상수가 되어버린 모양입니다.

열심히 mapping어쩌고를 안써도 되게 되었네요.

또한 struts.properties에 기본적인 설정을 할수 있습니다. 캐릭터셋까지 기본설정 가능하군요. 디폴트는 UTF-8입니다.

struts2의 디폴트 확장자는 do가 아닌 action입니다.

/WEB-INF/web.xml
[code]<?xml version=”1.0″ encoding=”UTF-8″?>
<web-app id=”WebApp_ID” version=”2.4″ xmlns=”http://java.sun.com/xml/ns/j2ee”
 xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
 xsi:schemaLocation=”http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”>
 <display-name>HelloWorld</display-name>
 <filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
 </filter>

 <filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
</web-app>[/code]

/WEB-INF/classes/struts.xml
[code] version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE struts PUBLIC “-//Apache Software Foundation//DTD Struts Configuration 2.0//EN”
 “http://struts.apache.org/dtds/struts-2.0.dtd”>
<struts>
 <!– Configuration for the default package. –>
 <package name=”tutorial” extends=”struts-default”>
        <action name=”HelloWorld” class=”tutorial.HelloWorld”>
            <result>Helloworld.jsp</result>
        </action>
        <!– Add your actions here –>
    </package>
</struts>[/code]

/WEB-INF/classes/struts.properties
[code]### Struts default properties
###(can be overridden by a struts.properties file in the root of the classpath)
###

### Specifies the Configuration used to configure Struts
### one could extend org.apache.struts2.config.Configuration
### to build one’s customize way of getting the configurations parameters into Struts
# struts.configuration=org.apache.struts2.config.DefaultConfiguration

### This can be used to set your default locale and encoding scheme
# struts.locale=en_US
struts.i18n.encoding=UTF-8

### if specified, the default object factory can be overridden here
### Note: short-hand notation is supported in some cases, such as “spring”
### Alternatively, you can provide a com.opensymphony.xwork2.ObjectFactory subclass name here 
# struts.objectFactory = spring

### specifies the autoWiring logic when using the SpringObjectFactory.
### valid values are: name, type, auto, and constructor (name is the default)
struts.objectFactory.spring.autoWire = name

### indicates to the struts-spring integration if Class instances should be cached
### this should, until a future Spring release makes it possible, be left as true
### unless you know exactly what you are doing!
### valid values are: true, false (true is the default)
struts.objectFactory.spring.useClassCache = true

### if specified, the default object type determiner can be overridden here
### Note: short-hand notation is supported in some cases, such as “tiger” or “notiger”
### Alternatively, you can provide a com.opensymphony.xwork2.util.ObjectTypeDeterminer
### implementation name here
### Note: if you have the xwork-tiger.jar within your classpath, GenericsObjectTypeDeterminer is
### used by default
### To disable tiger support use the “notiger” property value here.
#struts.objectTypeDeterminer = tiger
#struts.objectTypeDeterminer = notiger

### Parser to handle HTTP POST requests, encoded using the MIME-type multipart/form-data
# struts.multipart.parser=cos
# struts.multipart.parser=pell
struts.multipart.parser=jakarta
# uses javax.servlet.context.tempdir by default
struts.multipart.saveDir=
struts.multipart.maxSize=2097152

### Load custom property files (does not override struts.properties!)
# struts.custom.properties=application,org/apache/struts2/extension/custom

### How request URLs are mapped to and from actions
#struts.mapper.class=org.apache.struts2.dispatcher.mapper.DefaultActionMapper

### Used by the DefaultActionMapper
### You may provide a comma separated list, e.g. struts.action.extension=action,jnlp,do
struts.action.extension=do

### Used by FilterDispatcher
### If true then Struts serves static content from inside its jar.
### If false then the static content must be available at <context_path>/struts
struts.serve.static=true

### Used by FilterDispatcher
### This is good for development where one wants changes to the static content be
### fetch on each request.
### NOTE: This will only have effect if struts.serve.static=true
### If true -> Struts will write out header for static contents such that they will
###             be cached by web browsers (using Date, Cache-Content, Pragma, Expires)
###             headers).
### If false -> Struts will write out header for static contents such that they are
###            NOT to be cached by web browser (using Cache-Content, Pragma, Expires
###            headers)
struts.serve.static.browserCache=true

### Set this to false if you wish to disable implicit dynamic method invocation
### via the URL request. This includes URLs like foo!bar.action, as well as params
### like method:bar (but not action:foo).
### An alternative to implicit dynamic method invocation is to use wildcard
### mappings, such as <action name=”*/*” method=”{2}” class=”actions.{1}”>
struts.enable.DynamicMethodInvocation = true

### Set this to true if you wish to allow slashes in your action names.  If false,
### Actions names cannot have slashes, and will be accessible via any directory
### prefix.  This is the traditional behavior expected of WebWork applications.
### Setting to true is useful when you want to use wildcards and store values
### in the URL, to be extracted by wildcard patterns, such as
### <action name=”*/*” method=”{2}” class=”actions.{1}”> to match “/foo/edit” or
### “/foo/save”.
struts.enable.SlashesInActionNames = false

### use alternative syntax that requires %{} in most places
### to evaluate expressions for String attributes for tags
struts.tag.altSyntax=true

### when set to true, Struts will act much more friendly for developers. This
### includes:
### – struts.i18n.reload = true
### – struts.configuration.xml.reload = true
### – raising various debug or ignorable problems to errors
###   For example: normally a request to foo.action?someUnknownField=true should
###                be ignored (given that any value can come from the web and it
###                should not be trusted). However, during development, it may be
###                useful to know when these errors are happening and be told of
###                them right away.
struts.devMode = false

### when set to true, resource bundles will be reloaded on _every_ request.
### this is good during development, but should never be used in production
struts.i18n.reload=false

### Standard UI theme
### Change this to reflect which path should be used for JSP control tag templates by default
struts.ui.theme=xhtml
struts.ui.templateDir=template
#sets the default template type. Either ftl, vm, or jsp
struts.ui.templateSuffix=ftl

### Configuration reloading
### This will cause the configuration to reload struts.xml when it is changed
struts.configuration.xml.reload=false

### Location of velocity.properties file.  defaults to velocity.properties
struts.velocity.configfile = velocity.properties

### Comma separated list of VelocityContext classnames to chain to the StrutsVelocityContext
struts.velocity.contexts =

### Location of the velocity toolbox
struts.velocity.toolboxlocation=

### used to build URLs, such as the UrlTag
struts.url.http.port = 80
struts.url.https.port = 443
### possible values are: none, get or all
struts.url.includeParams = get

### Load custom default resource bundles
# struts.custom.i18n.resources=testmessages,testmessages2

### workaround for some app servers that don’t handle HttpServletRequest.getParameterMap()
### often used for WebLogic, Orion, and OC4J
struts.dispatcher.parametersWorkaround = false

### configure the Freemarker Manager class to be used
### Allows user to plug-in customised Freemarker Manager if necessary
### MUST extends off org.apache.struts2.views.freemarker.FreemarkerManager
#struts.freemarker.manager.classname=org.apache.struts2.views.freemarker.FreemarkerManager

### See the StrutsBeanWrapper javadocs for more information
struts.freemarker.wrapper.altMap=true

### configure the XSLTResult class to use stylesheet caching.
### Set to true for developers and false for production.
struts.xslt.nocache=false

### A list of configuration files automatically loaded by Struts
struts.configuration.files=struts-default.xml,struts-plugin.xml,struts.xml

### Whether to always select the namespace to be everything before the last slash or not
struts.mapper.alwaysSelectFullNamespace=false[/code]

Class : tutorial.HelloWorld.java
[code]package tutorial;

import com.opensymphony.xwork2.ActionSupport;

public class HelloWorld extends ActionSupport {

 private static final long serialVersionUID = 1L;
 public static final String MESSAGE = “Hello World (Struts 2)”;

 public String execute() throws Exception {
  setMessage(MESSAGE);
  return SUCCESS;
 }

 private String message;

 public void setMessage(String message) {
  this.message = message;
 }

 public String getMessage() {
  return message;
 }
}[/code]

/Helloworld.jsp
[code]<%@ page language=”java” contentType=”text/html; charset=EUC-KR”
    pageEncoding=”EUC-KR”%>
<%@ taglib prefix=”s” uri=”/struts-tags” %>
<html>
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h2><s:property value=”message” /></h2>
    </body>
</html>[/code]

Java Lucene – 루씬을 이용한 JSP용 클래스

자바를 하면서 가장 중독성 있었던것을 꼽아보라고 하면 단연 수많은 오픈소스 프레임워크가 아닐까 생각해 본다.

검색엔진에 대한 아무런 지식이 없는 상태인 나는 검색엔진 구현을 해야 하는 상황에서 어떻게 해야 할까 고민하다가 무턱대고 Lucene in Action을 덜컥 구매해 버렸다.

그리고 보다보니깐 참 이해 안되고, 책 내용도 자꾸 웹과는 거리가 있는 콘솔상에서의 구현에 집중되어있고 인터넷을 검색해 봐도 다 이해할수 없는 예제였고, 아무튼 그렇게 루씬을 공부했다.

이제 꽤나 루씬을 이해하고 있고, 이제 내가 아는 지식을 공개해야 할때인듯 하다. 물론 내가 아는 지식도 매우 기초적인것이라 나역시 초보를 위한 강좌밖에 못할듯 하다.

현존하는 루씬의 강좌나 자료들이 2.0이전 버젼들을 대상으로 제작되어있고, 그 이전버젼들은 한글에 대한 Analyzer버그가 있다.

고로 나는 처음부터 2.0으로 해왔다. 그래서인지 Lucene in Action책을 보고 따라하기엔 다른것이 많아 지식 습득에 조금 문제가 있었다. 앞으로 쓰는 모든 글은 2.0으로 제작할것이며, 한글 문제가 없는 버젼이다.

우선 간단하게 공부해 볼수 있는 루씬 클래스입니다. 공부에 참고만 하세요^^

[CODE]package ke.pe.theeye.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

public class SearchEngine {
 private static SearchEngine instance;
 private Analyzer standardAnalyzer;
 private Analyzer whitespaceAnalyzer;
 private Analyzer simpleAnalyzer;
 private Analyzer currentAnalyzer;
 private String directory;

 private static final int STANDARD = 0;
 private static final int WHITESPACE = 1;
 private static final int SIMPLE = 2;

 public static SearchEngine getInstance() {
  if (instance == null) {
   synchronized (SearchEngine.class) {
    if (instance == null)
     instance = new SearchEngine();
   }
  }

  return instance;
 }

 private SearchEngine() {
  instance = null;
  currentAnalyzer = null;
  standardAnalyzer = new StandardAnalyzer();
  whitespaceAnalyzer = new WhitespaceAnalyzer();
  simpleAnalyzer = new SimpleAnalyzer();
 }

 public boolean setIndexDirectory(String directory) throws IOException {
  if (this.currentAnalyzer == null) {
   return false;
  }

  this.directory = directory;

  Directory fsDir = null;
  IndexWriter writer = null;
  try {
   fsDir = FSDirectory.getDirectory(directory, false);
   if (!fsDir.fileExists(“segments”)) {
    writer = new IndexWriter(this.directory, currentAnalyzer, true);
   }
  } finally {
   if (writer != null)
    try {
     writer.close();
    } catch (IOException ex) {
    };
   if (fsDir != null)
    try {
     fsDir.close();
    } catch (IOException ex) {
    }
  }

  return true;
 }

 public boolean setAnalyzer(int type) {
  if (type == STANDARD) {
   this.currentAnalyzer = this.standardAnalyzer;
  } else if (type == WHITESPACE) {
   this.currentAnalyzer = this.whitespaceAnalyzer;
  } else if (type == SIMPLE) {
   this.currentAnalyzer = this.simpleAnalyzer;
  } else {
   return false;
  }

  return true;
 }

 public boolean makeIndex(String word) throws IOException {
  if (this.currentAnalyzer == null) {
   return false;
  }

  IndexWriter writer = null;

  try {
   writer = new IndexWriter(this.directory, this.currentAnalyzer,
     false);

   Document document = new Document();
   document.add(new Field(“word”, word, Store.YES, Index.TOKENIZED));
   writer.addDocument(document);
  } finally {
   if (writer != null)
    try {
     writer.close();
    } catch (IOException ex) {
    };
  }
  return true;
 }

 public List searchIndex(String queryString) throws IOException,
   ParseException {
  Directory fsDir = null;
  IndexSearcher is = null;

  try {
   fsDir = FSDirectory.getDirectory(this.directory, false);
   is = new IndexSearcher(fsDir);

   QueryParser parser = new QueryParser(“word”, this.currentAnalyzer);

   Query query = parser.parse(queryString);
   Hits hits = is.search(query);

   List<String> list = new ArrayList<String>();

   if (hits.length() > 0) {
    for (int i = 0; i < hits.length(); i++) {
     Document doc = hits.doc(i);
     list.add(doc.get(“word”));
    }
   }

   return list;
  } finally {
   if (is != null)
    try {
     is.close();
    } catch (IOException ex) {
    }
   if (fsDir != null)
    try {
     fsDir.close();
    } catch (IOException ex) {
    }
  }
 }
}[/CODE]

사용은 다음과 같이 한다.

초기화 :
[CODE]SearchEngine engine = SearchEngine.getInstance();
// 0 : Standard Analyzer, 1 : Whitespace Analyzer, 2 : Simple Analyzer
engine.setAnalyzer(0);
setIndexDirectory(“C:\LuceneIndex”);[/CODE]

인덱스 생성 :
[CODE]engine.makeIndex(“아이군의 홈페이지 주소는 theeye.pe.kr이다”);[/CODE]

검색시 :
[CODE]// 검색결과 객체들이 list로 담겨 나옴, 알아서 재사용
List list = engine.searchIndex(“아이군”); [/CODE]

위와같다. 이 글을 보는 분들은 루씬에 대해 어느정도 사전지식이 있는 분일것이다. 이것을 이해하는것은 어렵지 않았을것이라 생각한다.

위와같은 예제로(각각의 인덱스는 String형 하나가 아닌, Beans형의 특정 데이터 객체였다) 10만건을 입력해 놓고 검색해 보니 0.1초 이상 걸리지 않았다.

검색엔진 알고리즘에 사전지식이 없는 사람도 이정도로 사용할수 있다는것에 감탄을 표하는 바이다.

각각의 Analyzer차이와 인덱스 및 검색 옵션 여러가지 Term들에 대해서는 앞으로 계속 짬짬이 글을 써보겠다.