<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0">
  <title type="html">Outsider&#039;s Dev Story</title>
  <id>http://blog.outsider.ne.kr/</id>
  <link rel="alternate" type="text/html" hreflang="ko" href="http://blog.outsider.ne.kr/" />
  <subtitle type="html">Stay Hungry. Stay Foolish. Don&#039;t Be Satisfied.</subtitle>
  <updated>2013-05-20T01:57:29+09:00</updated>
  <generator>Textcube 1.7.8 : Con moto</generator>
  <entry>
    <title type="html">Node.js MongoDB 드라이버에서 MapReduce 사용하기</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/942" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/942" thr:count="0"/>
    <category term="node.js" />
    <category term="Incremental MapReduce" />
    <category term="MapReduce" />
    <category term="MongoDB" />
    <category term="mongodb-native" />
    <category term="node.js" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/942</id>
    <updated>2013-05-20T01:49:21+09:00</updated>
    <published>2013-05-20T01:49:21+09:00</published>
    <summary type="html">개인 프로젝트에서는 MongoDB를 주로 사용하곤 하는데 MongoDB 라이브러리로 이것저것 쓰다가 &lt;a href=&quot;https://github.com/mongodb/node-mongodb-native&quot; target=&quot;_blank&quot;&gt;mongodb-native&lt;/a&gt;가 MongoDB의 공식 Node.js 드라이버가 된 뒤로는 mongodb-native를 주로 사용하고 있다. 난 하둡쪽은 별로 다뤄본 적이 없어서 MapReduce에 대해서는 잘 모르지만 최근에 MapReduce를 처음으로 좀 써보게 되었다. MapReduce를 많이는 모르기 때문에 MapReduce에 대한 설명이라기 보다는 mongodb-native에서의 mapReduce 사용방법에 대한 글이다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
&amp;gt; db.example.find()
{ &quot;name&quot; : &quot;outsider&quot;, &quot;classOf&quot; : &quot;A&quot;, &quot;eng&quot; : 60, &quot;kor&quot; : 70, &quot;math&quot; : 50, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000001&quot;) }
{ &quot;name&quot; : &quot;nephilim&quot;, &quot;classOf&quot; : &quot;B&quot;, &quot;eng&quot; : 95, &quot;kor&quot; : 90, &quot;math&quot; : 100, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000002&quot;) }
{ &quot;name&quot; : &quot;arawn&quot;, &quot;classOf&quot; : &quot;A&quot;, &quot;eng&quot; : 80, &quot;kor&quot; : 80, &quot;math&quot; : 67, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000003&quot;) }
{ &quot;name&quot; : &quot;zziuni&quot;, &quot;classOf&quot; : &quot;B&quot;, &quot;eng&quot; : 70, &quot;kor&quot; : 65, &quot;math&quot; : 73, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000004&quot;) }
{ &quot;name&quot; : &quot;fupfin&quot;, &quot;classOf&quot; : &quot;A&quot;, &quot;eng&quot; : 65, &quot;kor&quot; : 50, &quot;math&quot; : 85, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000005&quot;) }
{ &quot;name&quot; : &quot;tenshi&quot;, &quot;classOf&quot; : &quot;B&quot;, &quot;eng&quot; : 85, &quot;kor&quot; : 70, &quot;math&quot; : 74, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000006&quot;) }
{ &quot;name&quot; : &quot;anarcher&quot;, &quot;classOf&quot; : &quot;B&quot;, &quot;eng&quot; : 70, &quot;kor&quot; : 78, &quot;math&quot; : 87, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000007&quot;) }
{ &quot;name&quot; : &quot;nanha&quot;, &quot;classOf&quot; : &quot;A&quot;, &quot;eng&quot; : 87, &quot;kor&quot; : 84, &quot;math&quot; : 91, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000008&quot;) }
{ &quot;name&quot; : &quot;fallroot&quot;, &quot;classOf&quot; : &quot;B&quot;, &quot;eng&quot; : 93, &quot;kor&quot; : 84, &quot;math&quot; : 84, &quot;_id&quot; : ObjectId(&quot;519877aebb61820000000009&quot;) }
{ &quot;name&quot; : &quot;dani&quot;, &quot;classOf&quot; : &quot;A&quot;, &quot;eng&quot; : 88, &quot;kor&quot; : 79, &quot;math&quot; : 77, &quot;_id&quot; : ObjectId(&quot;519877aebb6182000000000a&quot;) }
&lt;/pre&gt;&lt;br&gt;예를 들어 위와 같은 데이터가 디비에 들어있다고 하자. 각 학생의 리스트고 &lt;font color=&quot;#FF7635&quot;&gt;classOf&lt;/font&gt;가 학생들이 속한 반이고 각 과목에 대한 점수가 들어있다. 각 반별로 과목에 대한 평균을 구한다고 할때 물론 그냥 쿼리로도 충분히 수행이 가능하지만 MapReduce를 사용해보자. 이 MapReduce는 다음과 같이 작성할 수 있다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
var MongoClient = require(&#039;mongodb&#039;).MongoClient;

MongoClient.connect(&#039;mongodb://localhost:27017/mrtest&#039;, function(err, db) {
&amp;nbsp; var example = db.collection(&#039;example&#039;);

&amp;nbsp; var map = function() {
&amp;nbsp; &amp;nbsp; emit(this.classOf, this);
&amp;nbsp; };

&amp;nbsp; var reduce = function(classOf, students) {
&amp;nbsp; &amp;nbsp; var engTotal = korTotal = mathTotal = 0;

&amp;nbsp; &amp;nbsp; students.forEach(function(student) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; engTotal += student.eng
&amp;nbsp; &amp;nbsp; &amp;nbsp; korTotal += student.kor
&amp;nbsp; &amp;nbsp; &amp;nbsp; mathTotal += student.math
&amp;nbsp; &amp;nbsp; });

&amp;nbsp; &amp;nbsp; return {
&amp;nbsp; &amp;nbsp; &amp;nbsp; classOf: classOf,
&amp;nbsp; &amp;nbsp; &amp;nbsp; engAvg: engTotal / students.length,
&amp;nbsp; &amp;nbsp; &amp;nbsp; korAvg: korTotal / students.length,
&amp;nbsp; &amp;nbsp; &amp;nbsp; mathAvg: mathTotal / students.length,
&amp;nbsp; &amp;nbsp; }
&amp;nbsp; };

&amp;nbsp; example.mapReduce(
&amp;nbsp; &amp;nbsp; &amp;nbsp; map,
&amp;nbsp; &amp;nbsp; &amp;nbsp; reduce,
&amp;nbsp; &amp;nbsp; &amp;nbsp; { out: &#039;mrtemp&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; function(err, coll) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; coll.find().toArray(function(err, arr) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; console.log(arr);
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; });
&amp;nbsp; &amp;nbsp; &amp;nbsp; }
&amp;nbsp; );
});
&lt;/pre&gt;&lt;br&gt;코드가 약간 길지만(?) 그리 복잡하지는 않다. 앞부분은 디비에 연결하는 부분이고 먼저 사용한 map 함수와 reduce함수를 정의해야 한다. MapReduce 개념에 대해서 여기서 다 설명하기는 좀 무리가 있는데 간단히 말하면 map함수를 이용해서 데이터를 특정 키값의 데이터로 모아줄 수 있고 이를 reduce에서 다시 재가공해서 최종 데이터를 만들게 된다. 차례차례 보자.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
var map = function() {
&amp;nbsp; emit(this.classOf, this);
};
&lt;/pre&gt;&lt;br&gt;수행한 쿼리의 모든 도큐먼트에 대해서 map함수가 실행되는데 map 함수내에서 this가 해당 도큐먼트를 가리킨다. 이 함수내에서 어떤 처리가 필요하다면 할 수 있고 최종적으로 reduce로 전달하기 위해서 &lt;font color=&quot;#FF7635&quot;&gt;emit()&lt;/font&gt;을 실행해야 하는데 파라미터로 key와 value를 전달해야 한다. 여기서는 반별로 평균을 구할 것이므로 키에 classOf의 값을 전달하고 값에는 해당 도큐먼트를 통째로 전달했다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
var reduce = function(classOf, students) {
&amp;nbsp; var engTotal = korTotal = mathTotal = 0;

&amp;nbsp; students.forEach(function(student) {
&amp;nbsp; &amp;nbsp; engTotal += student.eng
&amp;nbsp; &amp;nbsp; korTotal += student.kor
&amp;nbsp; &amp;nbsp; mathTotal += student.math
&amp;nbsp; });

&amp;nbsp; return {
&amp;nbsp; &amp;nbsp; classOf: classOf,
&amp;nbsp; &amp;nbsp; engAvg: engTotal / students.length,
&amp;nbsp; &amp;nbsp; korAvg: korTotal / students.length,
&amp;nbsp; &amp;nbsp; mathAvg: mathTotal / students.length,
&amp;nbsp; }
};
&lt;/pre&gt;&lt;br&gt;이번엔 Reduce를 위함 함수이다. map에서 전달한 것과 같이 reduce함수는 키와 배열의 두 파라미터를 받는다. 키 값은 map이 전달한 키값이고 배열에는 같은 키를 가진 값(map이 전달한)의 배열이 전달된다. 즉, 여기서는 classOf의 값이 A와 B 두가지 뿐이므로 reduce는 2번 호출되고 A 키값에 A반 학생들객체의 배열, B 키값에는 B반 학생객체의 배열이 전달되게 된다. 이 Reduce 함수내에서 필요한 작업(여기서는 평균값 계산)을 한 뒤에 최종적으로 MongoDB에 넣을 JSON 객체(MongoDB 이므로)를 만들어서 반환하면 된다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
example.mapReduce(
&amp;nbsp; &amp;nbsp; map,
&amp;nbsp; &amp;nbsp; reduce,
&amp;nbsp; &amp;nbsp; { out: &#039;mrtemp&#039;},
&amp;nbsp; &amp;nbsp; function(err, coll) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; coll.find().toArray(function(err, arr) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; console.log(arr);
&amp;nbsp; &amp;nbsp; &amp;nbsp; });
&amp;nbsp; &amp;nbsp; }
);
&lt;/pre&gt;&lt;br&gt;실제 MapReduce를 수행하는 부분이다. 컬렉션에 &lt;a href=&quot;http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce&quot; target=&quot;_blank&quot;&gt;mapRecude()함수&lt;/a&gt; &lt;a href=&#039;http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;가 존재하고 처음 두 파라미터로 Map, Reduce함수를 전달해주면 된다. 세번째 파라미터는 옵션값인데 out은 MapReduce를 수행한 결과가 들어갈 컬렉션을 지정한다. 여기서는 reduce가 2개의 문서(A, B)를 생성하므로 mrtemp라는 컬렉션이 두 문서가 들어가게 된다. 여기서는 쿼리에 어떤 조건을 주지 않았지만 쿼리조건이 필요하다면 query 옵션을 사용할 수 있다. 마지막으로 콜백으로 실행결과와 MapReduce&amp;nbsp; 결과가 들어간 컬렉션을 반환하므로 결과를 조회하려면 해당 컬렉션에서 다시 조회를 해서 데이터를 가져와야 한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;그 외 몇가지 사항들...&lt;/font&gt;&lt;/b&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;map, reduce로 전달한 함수는 MongoDB내에서 수행된다. 그러므로 일반적인 자바스크립트처럼 프로그램내의 어떤 변수를 클로저형태로 map, reduce함수로 전달할 수 없다. map, reduce함수는 각각 개별적으로 동작할 수 있어야 한다. &lt;br&gt;&lt;/li&gt;&lt;li&gt;map, reduce 과정을 디버깅하려면 MongoDB의 로그를 봐야한다. 당연히 &lt;font color=&quot;#FF7635&quot;&gt;console.log&lt;/font&gt;같은건 안 먹히고 map, reduce 함수내에서 출력해 보고 싶다면 &lt;font color=&quot;#FF7635&quot;&gt;print() &lt;/font&gt;함수를 사용하면 MongoDB의 로그에 출력되기 때문에 전달되는 값을 확인하고 싶다면 &lt;font color=&quot;#FF7635&quot;&gt;print()&lt;/font&gt;로 디버깅할 수 있다.&lt;/li&gt;&lt;li&gt;MongoDB의 내부를 다 파악해 보지는 않았지만 로그상으로 보면 MapReduce를 수행할 때마다 기존의 맵리듀스용 컬렉션(out으로 지정한)을 drop한 뒤에 다시 생성한다. &lt;br&gt;&lt;/li&gt;&lt;li&gt;MapReduce과정은 순차적으로 처리가 된다. 동시에 여러 MapReduce를 요청한다고 하더라도 한 MapReduce를 모두 처리한뒤에 나머지를 처리하게 된다. 이는 중간에 서로 간섭이 일어나지 않도록 하기 위함으로 보이므로 동시에 여러 요청이 들어가게 된다면 지연시간이 오래걸리므로 유의해서 사용해야 한다.&lt;/li&gt;&lt;li&gt;MapReduce를 잘 몰라서 잘 아는 분께 물어본 봐로는 이러한 동시성 문제나 성능향상을 위한 MapReduce를 Incremental MapReduce라고 부른다고 한다. MongoDB의 &lt;a href=&quot;http://docs.mongodb.org/manual/tutorial/perform-incremental-map-reduce/&quot; target=&quot;_blank&quot;&gt;Incremental MapReduce&lt;/a&gt; &lt;a href=&#039;http://docs.mongodb.org/manual/tutorial/perform-incremental-map-reduce/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;는 별로 좋지 않아보이고 데이터의 타임스탬프를 사용해서 직접 처리를 해주어야 하는 것으로 보인다.(사용해 보진 않았다.) 다른 디비에서는 MapReduce 명령차원에서 Incremental 사용여부를 지정할 수 있기도 한데 MongoDB는 그렇지 않아서 이부분은 신경써서 처리해야할듯 싶다.&lt;br&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">MongoDB 도큐먼트의 BSON 크기 확인하기</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/941" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/941" thr:count="0"/>
    <category term="NoSQL" />
    <category term="BSON" />
    <category term="bsonsize" />
    <category term="MongoDB" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/941</id>
    <updated>2013-05-16T23:54:02+09:00</updated>
    <published>2013-05-16T23:54:02+09:00</published>
    <summary type="html">간단한 개인 프로젝트를 하면서 MongoDB를 사용했는데 데이터를 조회하던 중 다음과 같은 connection closed due to parseError 오류가 발생한다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
Thu May 16 2013 19:53:18 GMT+0900 (KST) - error: Error: connection closed due to parseError
&amp;nbsp; at [object Object].Server.connect (/example/node_modules/mongodb/lib/mongodb/connection/server.js:645:45)
&amp;nbsp; at [object Object].EventEmitter.emit (events.js:126:20)
&amp;nbsp; at [object Object]._connect (/example/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:181:15)
&amp;nbsp; at [object Object].EventEmitter.emit (events.js:99:17)
&amp;nbsp; at Socket.exports.Connection.createDataHandler (/example/node_modules/mongodb/lib/mongodb/connection/connection.js:357:20)
&amp;nbsp; at Socket.EventEmitter.emit (events.js:96:17)
&amp;nbsp; at TCP.onread (net.js:397:14)
&lt;/pre&gt;&lt;br&gt;오류 메시지만 보면 파싱오류가 발생한 것데 왜 파싱오류가 발생했는지는 추측하기가 쉽지 않다. 이건 소스를 보면 확인할 수 있는데 해당 부분인 &lt;font color=&quot;#FF7635&quot;&gt;connection.js&lt;/font&gt;의 소스는 다음과 같다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
if(sizeOfMessage &amp;lt; 0 || sizeOfMessage &amp;gt; self.maxBsonSize) {
&amp;nbsp; var errorObject = {err:&quot;socketHandler&quot;, trace:&#039;&#039;, bin:self.buffer, parseState:{
&amp;nbsp; &amp;nbsp; sizeOfMessage: sizeOfMessage,
&amp;nbsp; &amp;nbsp; bytesRead: self.bytesRead,
&amp;nbsp; &amp;nbsp; stubBuffer: self.stubBuffer}};
&amp;nbsp; if(self.logger != null &amp;amp;&amp;amp; self.logger.doError) self.logger.error(&quot;parseError&quot;, errorObject);
&amp;nbsp; // We got a parse Error fire it off then keep going
&amp;nbsp; self.emit(&quot;parseError&quot;, errorObject, self);
&amp;nbsp; return;
}
&lt;/pre&gt;&lt;br&gt;메시지가 없거나 최대 BSON 사이즈보다 작은 경우에 오류가 발생한건데 이 경우에는 BSON 사이즈가 최대 크기를 넘어선 경우이다. MongoDB는 BSON(Binary JSON)이라는 데이터형식을 사용하는데 현재 &lt;a href=&quot;http://docs.mongodb.org/manual/reference/limits/#BSON Document Size&quot; target=&quot;_blank&quot;&gt;MongoDB의 최대 사이즈는 16MB&lt;/a&gt; &lt;a href=&#039;http://docs.mongodb.org/manual/reference/limits/#BSON Document Size&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;이다.&amp;nbsp; 과거에는 &lt;a href=&quot;https://jira.mongodb.org/browse/SERVER-431?focusedCommentId=22283&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-22283&quot; target=&quot;_blank&quot;&gt;4MB였는데 1.7.4버전부터인가 16MB로 올라갔고&lt;/a&gt; 차후에는 32MB로 올릴 예정인 것으로 알고 있다. 이 값은 참고로 수정할 수 없는 값이므로 MongoDB의 제약사항 중 하나고 MongoDB에서 하나의 도규먼트(RDB라면 하나의 Row)의 최대 크기가 16MB라는 의미이다. 임시적인 프로젝트라서 귀차니즘에 하나의 컬렉션에 데이터를 임베딩해서 넣으면서 16MB가 넘을 일은 없을꺼라고 생각했지만 아마도 넘은듯 하다.(사실 정말 16MB가 넘은지 좀 의심스럽기는 하지만 데이터가 많아서 정확히 확인하지는 못했다.)&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;BSON 사이즈 확인&lt;/font&gt;&lt;/b&gt;&lt;br&gt;MongoDB 쉘에서는 BSON의 크기를 확인할 수 있는 &lt;font color=&quot;#FF7635&quot;&gt;Object.bsonsize()&lt;/font&gt;라는 함수를 제공하고 있고 있다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
&amp;gt; Object.bsonsize({})
5
&amp;gt; Object.bsonsize({name:&#039;outsider&#039;, url:&#039;http://blog.outsider.ne.kr&#039;})
60
&lt;/pre&gt;&lt;br&gt;bsonsize()에 객체를 넘겨보면 BSON의 크기를 계산해 볼 수 있다. 위와 같이 객체를 직접 넣는대신 query를 넣어서 확인할 수도 있다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
&amp;gt; Object.bsonsize(db.test.findOne())
988923
&lt;/pre&gt;&lt;br&gt;위와 같이 &lt;font color=&quot;#FF7635&quot;&gt;findOne()&lt;/font&gt; 쿼리를 사용해서 디비에 담긴 특정 도큐먼트의 크기를 확인해 볼 수 있는데 여기서 주의할 점은 &lt;font color=&quot;#FF7635&quot;&gt;findOne()&lt;/font&gt;만 되고 &lt;font color=&quot;#FF7635&quot;&gt;find()&lt;/font&gt;로는 제대로 확인이 되지 않는다. 여기서 나오는 크기는 바이트크기이므로 988923은 988KB정도가 되는 것이다. BSON 사이즈제한이 의심되는 도큐먼트가 있다면 이 함수를 사용해서 확인해 볼 수 있다.&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">Gittip으로 오픈소스 개발자들에게 감사함 표시하기</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/940" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/940" thr:count="0"/>
    <category term="BlaBlaBla~" />
    <category term="GitHub" />
    <category term="gittip" />
    <category term="Open Source" />
    <category term="thank" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/940</id>
    <updated>2013-05-15T01:28:46+09:00</updated>
    <published>2013-05-15T01:20:43+09:00</published>
    <summary type="html">어저께 &lt;a href=&quot;https://www.gittip.com/&quot; target=&quot;_blank&quot;&gt;Gittip&lt;/a&gt;과 관련해서 재미난 사건이 있었다. 이 일은 &lt;a href=&quot;http://lqez.github.io/blog/gittipcom-and-forkorea.html&quot; target=&quot;_blank&quot;&gt;lqez님이 잘 정리&lt;/a&gt; &lt;a href=&#039;http://lqez.github.io/blog/gittipcom-and-forkorea.html&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;해 주셨고 Gittip에서는 150명이 넘어야 커뮤니티가 되는데 &lt;a href=&quot;https://www.gittip.com/for/&quot; target=&quot;_blank&quot;&gt;현재 4개밖에 커뮤니티&lt;/a&gt;가 안되었지만 첫번째이자 제일 큰 규모의 커뮤니티를 유지하고 있다. Gittip의 커뮤니티 기능은 꽤 좋은 기능 추가였다고 생각하는데 &lt;a href=&quot;https://www.gittip.com/for/korea/&quot; target=&quot;_blank&quot;&gt;Korea 커뮤니티&lt;/a&gt;만 봐도 어제 150명이 되어서 통계페이지가 열렸을때만 해도 Tip 금액이 2불정도밖에 안되었는데 지금은 60불정도나 된다.&lt;br&gt;&lt;br&gt;오픈소스활동이라는 것이 꼭 멋진 라이브러리를 만들어서 오픈소스 라이센스로 공유하는 것뿐이라고 생각하지 않는다. 누구는 만들지만 누구는 사용하고 누군가는 문서를 만들고 누군가는 지식을 나눔으로서 오픈소스에 생태계에 공헌한다. 그리고 소스코드를 만들어준 사람한테 감사하는 마음으로 자신의 위치에서 할 수 있는 일을 하는 거고 봉사하는 마음으로 한다기 보다는 그렇게 하는 것이 세상을 더 윤택하게 한다고 믿기 때문이고 IT는 실제로 그렇게 발전해 왔다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;Gittip&lt;/font&gt;&lt;/b&gt;&lt;br&gt;각자 나름대로 오픈소스에 공헌할 수 있는 방법은 많이 있지만 가장 손쉬우면서도 좋은 방법은 금액적인 방법이 아닐까 싶다. 거의 대부분의 오픈소스는 잉여에서 시작했기에 그들이 더 많은 잉여를 가질 수 있다면 훨씬 더 좋은 오픈소스들이 등장하지 않을까? 퇴근해서 오픈소스 이슈처리하고 피드백하고 소스작성하는게 얼마나 많은 노력이 들지는 쉽게 상상할 수 있고 실제로도 몇몇 회사가 적극적으로 나서서 풀타임 오픈소스 개발자로 채용해서 지원하는 오픈소스들은 훨씬 적극적으로 변해가는 모습들을 볼 수 있다.&lt;br&gt;&lt;br&gt;&lt;a href=&quot;http://lucumr.pocoo.org/2012/8/27/open-source-financing/&quot; target=&quot;_blank&quot;&gt;Armin Ronacher&lt;/a&gt; &lt;a href=&#039;http://lucumr.pocoo.org/2012/8/27/open-source-financing/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;의 글에 나온대로 &lt;a href=&quot;http://www.kickstarter.com/&quot; target=&quot;_blank&quot;&gt;Kick Starter&lt;/a&gt; &lt;a href=&#039;http://www.kickstarter.com/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;처럼 Gittip도 클라우드 펀딩의 한 형태로 볼 수 있고 Gittip은 오픈소스에 타게팅되어 있다. 거대한 비용을 누군가 지불하기는 어렵지만 수많은 사람들이 Tip처럼 원저작자들에게 감사함을 표현한다는 개념은 참 맘에 든다. 자신이 짠 소스에 또는 자신이 한 공헌에 감사함을 표할 수 있는 가장 간단하고 확실한 방법이라고 생각된다. 아직은 그정도 규모는 안되어보이지만 Gittip의 규모가 더욱 커져서 많은 오픈소스 개발자들이 상황에 따라 Gittip으로 생계를 유지할 수 있다면 얼마나 더 좋아질까 하는 상상을 해본다.&lt;br&gt;&lt;br&gt;&lt;div class=&quot;imageblock center&quot; style=&quot;text-align: center; clear: both;&quot;&gt;&lt;img src=&quot;http://blog.outsider.ne.kr/attach/1/1009463704.jpg&quot; alt=&quot;GITTIP 홈페이지 화면 &quot; height=&quot;575&quot; width=&quot;550&quot; /&gt;&lt;/div&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;Gittip의 방식&lt;/font&gt;&lt;/b&gt;&lt;br&gt;Gittip은 &lt;a href=&quot;https://github.com/&quot; target=&quot;_blank&quot;&gt;Github&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/&quot; target=&quot;_blank&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://bitbucket.org/&quot; target=&quot;_blank&quot;&gt;Bitbucket&lt;/a&gt;의 아이디로 로그인할 수 있고 원하는 사람을 찾을 때도 해당 아이디로 검색할 수 있다. 감사함을 표하고 싶은 개발자의 페이지에 가서(물론 그사람이 Gittip에 가입해야 가능하다) 원하는 금액을 선택하고 마스터나 비자카드 정보를 입력하면 된다. 결재는 매주 목요일에 결제가 된다고 한다. Gittip은 매주 자신이 원하는 사람들에게 Tip을 주는 형태이고 최소 금액인 25센트, 1불, 3불, 6불, 12불 24불 등등의 금액을 선택해서 기부할 수 있다. 최저금액이 정말 부담이 안될만한 25센트라는 부분이 좋은 접근이라고 생각된다.&lt;br&gt;&lt;br&gt;&amp;nbsp;&lt;font color=&quot;#FF7635&quot;&gt;Gittip의 특이한 점은 이 Tip이 일회성이 아니라는 것이다. 물론 얼마를 줄것인지는 각자 정하는 것이지만 지정한 금액이 매주 나간다는 점에 주의해야 한다.&lt;/font&gt; 난 이 사람한테 20불을 줘도 아깝지 않아!라고 생각할 수도 있지만 매주 20불씩 나간다면 달라질 수도 있다.(물론 사람다라 다르겠지만...) 물론 Tip의 금액은 자신의 페이지에서 계속 수정이 가능하므로 일회성으로 주고 싶다면 금액을 설정하고 결재가 떨어진뒤에 금액을 낮추거나 0으로 설정하면 된다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;Gittip을 알게 된지는 꽤 되었지만 그동안 쓰지는 않았다가 이번에 커뮤니티 사건(?)으로 다시 관심이 가서 쓰기로 했다. 내가 오픈소스로 받는 혜택을 생각하면 커피 한두잔 정도의 값은 아깝지도 않지만 주고 싶은데 없는 사람도 있고 해서 일단 3불 정도로 셋팅을 했다. 한번 셋팅해놓고 잊고 사는 대신 뭔가 도움을 받을 때마가 적극적으로 감사함을 표시하는 사용하는 패턴으로 가보려고 차차 늘려가면서 사용해 보고자 한다.&lt;br&gt;&lt;br&gt;덧) Gittip에서 위젯을 제공하기는 하는데 대가를 지불하는 문화에 대해서는 내가 지향하는 바와 일치하지만 그래도 돈은 좀 민감한 문제라서 위젯을 달지 말지는 좀 고민해봐야겠다.</summary>
  </entry>
  <entry>
    <title type="html">Handlebars에서 if문에 헬퍼함수 사용하기</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/939" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/939" thr:count="0"/>
    <category term="Javascript" />
    <category term="handlebars" />
    <category term="if" />
    <category term="mustache" />
    <category term="Template" />
    <category term="template language" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/939</id>
    <updated>2013-05-13T23:53:01+09:00</updated>
    <published>2013-05-13T23:53:01+09:00</published>
    <summary type="html">&lt;a href=&quot;http://handlebarsjs.com/&quot; target=&quot;_blank&quot;&gt;handlebars&lt;/a&gt; &lt;a href=&#039;http://handlebarsjs.com/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;는 템플릿 언어인 &lt;a href=&quot;http://mustache.github.io/&quot; target=&quot;_blank&quot;&gt;Mustache&lt;/a&gt; &lt;a href=&#039;http://mustache.github.io/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;를 확장한 템플릿 라이브러리이다. handlebars를 소개하는 글은 아니지만 처음 보는 사람들을 위해서 간단히 설명하자면 다음과 같이 템플릿을 정의한다.&lt;br&gt;&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;script id=&quot;example-template&quot; type=&quot;text/x-handlebars-template&quot;&amp;gt;
&amp;nbsp; &amp;lt;ul&amp;gt;
&amp;nbsp; &amp;nbsp; {{#each members}}
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;li&amp;gt;{{name}}
&amp;nbsp; &amp;nbsp; {{/each}}
&amp;nbsp; &amp;lt;/ul&amp;gt;
&amp;lt;/script&amp;gt;
&lt;/pre&gt;&lt;br&gt;꼭 &lt;font color=&quot;#FF7635&quot;&gt;&amp;lt;script&amp;gt;&lt;/font&gt; 태그를 사용해서 정의해야 하는 것은 아니고 그냥 템플릿 문자열이면 되기는 하지만 개인적으로는 관리하기도 변하고 작성도 편해서 &lt;font color=&quot;#FF7635&quot;&gt;&amp;lt;script&amp;gt;&lt;/font&gt; 태그를 이용하는 방법을 좋아한다.&lt;br&gt;&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;/components/handlebars.js/handlebars.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
&amp;nbsp; window.addEventListener(&#039;load&#039;, function(evt) {
&amp;nbsp; &amp;nbsp; var m = {
&amp;nbsp; &amp;nbsp; &amp;nbsp; members: [
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;outsider&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;zziuni&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;danni&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;fallroot&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;boxer&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;aj&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;insane&#039;},
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {name: &#039;j2p&#039;}
&amp;nbsp; &amp;nbsp; &amp;nbsp; ]
&amp;nbsp; &amp;nbsp; }
&amp;nbsp; &amp;nbsp; var source = document.getElementById(&#039;example-template&#039;).innerHTML;
&amp;nbsp; &amp;nbsp; var tmpl = Handlebars.compile(source);
&amp;nbsp; &amp;nbsp; document.getElementById(&#039;wrap&#039;).innerHTML = tmpl(m)
&amp;nbsp; }, false);
&amp;lt;/script&amp;gt;
&lt;/pre&gt;&lt;br&gt;앞에서 정의한 템플릿을 위와 같이 사용한다. &lt;font color=&quot;#FF7635&quot;&gt;&amp;lt;script&amp;gt;&lt;/font&gt; 태그로 정의한 템플릿을 가져와서 handlebars로 컴파일해서 템플릿으로 만들고 템플릿에 객체를 전달해서 HTML을 생성한다. 여기서는 &lt;font color=&quot;#FF7635&quot;&gt;wrap&lt;/font&gt;라는 &lt;font color=&quot;#FF7635&quot;&gt;&amp;lt;div&amp;gt;&lt;/font&gt;에 변환된 &lt;font color=&quot;#FF7635&quot;&gt;&amp;lt;li&amp;gt;&lt;/font&gt;를 넣도록 만들었다.&lt;br&gt;&lt;br&gt;이제 &lt;font color=&quot;#FF7635&quot;&gt;&amp;lt;li&amp;gt;&lt;/font&gt;태그를 만들때 &lt;font color=&quot;#FF7635&quot;&gt;name&lt;/font&gt;이 모음으로 시작하는 경우에는 다른 스타일을 주기 위해서 if 문을 사용한다고 해보자.&lt;br&gt;&lt;pre class=&quot;brush: html&quot;&gt;
{{#each members}}
&amp;nbsp; {{#if @index % 2 == 0}}
&amp;nbsp; &amp;nbsp; &amp;lt;li class=&quot;vowel&quot;&amp;gt;{{name}}&amp;lt;/li&amp;gt;
&amp;nbsp; {{else}}
&amp;nbsp; &amp;nbsp; &amp;lt;li class=&quot;consonant&quot;&amp;gt;{{name}}&amp;lt;/li&amp;gt;
&amp;nbsp; {{/if}}
{{/each}}
&lt;/pre&gt;&lt;br&gt;템플릿을 위와 같이 작성하기 쉽지만 handlebars는 저런식의 if문을 제공하지 않는다. if자제가 헬퍼메서드로 등록되어 있는데 if 헬퍼함수에 전달한 값을 true/false 여부만 판단할 뿐이고 일반적인 자바스크립트처럼 저런식의 표현식은 제공하지 않는다. &lt;font color=&quot;#FF7635&quot;&gt;{{#if name}}&lt;/font&gt;과 같은 정도로만 사용할 수 있고 name을 가지고 true/false 여부만 판단한다.(name은 값이 존재하므로 저렇게 사용하면 여기서는 항상 true이다.)&lt;br&gt;&lt;br&gt;하지만 당연히 간단한 if문 외에 로직이 필요한 if문이 필요하게 마련인데 이는 직접 헬퍼함수를 등록해서 해결할 수 있다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
Handlebars.registerHelper(&#039;isVowel&#039;, function(options) {
&amp;nbsp; var regexp = /^[aeiou]/;
&amp;nbsp; if (regexp.test(this.name)) {
&amp;nbsp; &amp;nbsp; return options.fn(this);
&amp;nbsp; } else {
&amp;nbsp; &amp;nbsp; return options.inverse(this);
&amp;nbsp; }
});
&lt;/pre&gt;&lt;br&gt;헬퍼함수는 &lt;font color=&quot;#FF7635&quot;&gt;Handlebars.registerHelper()&lt;/font&gt;를 사용해서 등록할 수 있고 첫 인자가 헬퍼함수의 이름이고 두번째 함수가 헬퍼함수이다. 헬퍼함수는 기본적으로 &lt;font color=&quot;#FF7635&quot;&gt;options&lt;/font&gt;라는 객체를 인자로 받는데 이는 자동으로 전달되는 인자이다. 즉 &lt;font color=&quot;#FF7635&quot;&gt;options&lt;/font&gt;만 받도록 작성했다는 것은 전달하는 인자가 없다는 것으로 인자가 있다면 자동으로 앞에 붙는다. 즉 인자가 1개 있으면 &lt;font color=&quot;#FF7635&quot;&gt;function(arg1, options) {}&lt;/font&gt; 처럼 작성해야 한다. 여기서는 &lt;font color=&quot;#FF7635&quot;&gt;isVowel&lt;/font&gt;이라는 헬퍼함수를 if문처럼 사용할 것이므로 원하는 조건(여기서는 이름이 문자로 시작하면)이 true이면 &lt;font color=&quot;#FF7635&quot;&gt;options.fn(this)&lt;/font&gt;를 반환해서 if 구문의 내용을 사용하고 false이면 &lt;font color=&quot;#FF7635&quot;&gt;options.inverse(this)&lt;/font&gt;를 반환해서 else 구문을 사용하도록 작성했다. handlebars에서는 대부분 그런데 &lt;font color=&quot;#FF7635&quot;&gt;this&lt;/font&gt;에 현재 컨텍스트의 값이 들어간다.(여기서는 each문으로 돌아가는 members의 값들..)&lt;br&gt;&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;ul&amp;gt;
&amp;nbsp; {{#each members}}
&amp;nbsp; &amp;nbsp; {{#isVowel}}
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;li class=&quot;vowel&quot;&amp;gt;{{name}}&amp;lt;/li&amp;gt;
&amp;nbsp; &amp;nbsp; {{else}}
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;li class=&quot;consonant&quot;&amp;gt;{{name}}&amp;lt;/li&amp;gt;
&amp;nbsp; &amp;nbsp; {{/isVowel}}
&amp;nbsp; {{/each}}
&amp;lt;/ul&amp;gt;
&lt;/pre&gt;&lt;br&gt;템플릿은 위와 같이 작성한다. if문대신에 &lt;font color=&quot;#FF7635&quot;&gt;isVowel&lt;/font&gt; 헬퍼함수를 사용했다.(&lt;font color=&quot;#FF7635&quot;&gt;{{/isVowel}}&lt;/font&gt;로 닫아준 것에 주의!!) else문은 그대로 사용하고 이렇게 사용하면 원하는 조건으로 if문을 사용할 수 있다. 물론 헬퍼함수에 파라미터가 필요하다면 전달해주면 된다.&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">express에서 하위 경로로 라우팅하기</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/938" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/938" thr:count="0"/>
    <category term="node.js" />
    <category term="express" />
    <category term="nginx" />
    <category term="node.js" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/938</id>
    <updated>2013-05-12T23:24:42+09:00</updated>
    <published>2013-05-12T23:24:42+09:00</published>
    <summary type="html">일반적으로 어플리케이션에서루트의 절대경로를 사용하기를 좋아하는데 &lt;a href=&quot;http://expressjs.com/&quot; target=&quot;_blank&quot;&gt;express&lt;/a&gt; &lt;a href=&#039;http://expressjs.com/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;앞에 &lt;a href=&quot;http://nginx.org/&quot; target=&quot;_blank&quot;&gt;nginx&lt;/a&gt; &lt;a href=&#039;http://nginx.org/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;같은 웹서버를 두다보니 일일이 서브도메인을 만드는것도 귀찮고 하위디렉토리에 express 앱을 연결할 일이 있었다. 예를 들면 &lt;font color=&quot;#FF7635&quot;&gt;http://example.com/app&lt;/font&gt; 을 express로 리버스 프록시하는 경우인데 이렇게 되면 당연히 앱의 URL 경로가 /가 아닌 &lt;font color=&quot;#FF7635&quot;&gt;/app&lt;/font&gt;이 되므로 라우팅을 모두 변경해 주어야 한다. &lt;br&gt;&lt;br&gt;당연한 이야기이지만 &lt;font color=&quot;#FF7635&quot;&gt;/example&lt;/font&gt;이라는 하위경로로 연결한다고 하면 &lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
app.get(&#039;/example&#039;, routes.index);
&lt;/pre&gt;&lt;br&gt;라우팅을 할 때 위처럼 경로 앞에 &lt;font color=&quot;#FF7635&quot;&gt;/example&lt;/font&gt;같은 접두사를 모두 붙혀주어야 한다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
link(rel=&#039;stylesheet&#039;, href=&#039;/example/stylesheets/style.css&#039;)
&lt;/pre&gt;&lt;br&gt;그리고 정적파일에도 절대경로를 사용한 곳이라면 당연히 모두 접두사를 붙혀주어야 한다. 정적파일의 경로를 바꾸어 주었으므로 express에서 정적파일에 대한 접두사를 지정해 주어야 한다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
app.use(express.static(path.join(__dirname, &#039;public&#039;)));
&lt;/pre&gt;&lt;br&gt;express에서는 위와 같이 정적파일 폴더를 지정하는데 이렇게 사용한 경우 public 폴더의 하위 내용이 자동으로 &lt;font color=&quot;#FF7635&quot;&gt;/&lt;/font&gt; 경로 아래로 라우팅된다. 여기서는 &lt;font color=&quot;#FF7635&quot;&gt;/example&lt;/font&gt; 경로를 사용하므로 정적파일에 대한 라우팅도 바꾸어야 한다. &lt;a href=&quot;http://www.senchalabs.org/connect/&quot; target=&quot;_blank&quot;&gt;connect&lt;/a&gt; &lt;a href=&#039;http://www.senchalabs.org/connect/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;의 &lt;font color=&quot;#FF7635&quot;&gt;app.use()&lt;/font&gt;는 첫번째 파라미터로 경로를 받기 때문에 추가로 접두사 경로를 지정해 주면 된다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
app.use(&#039;/example&#039;, express.static(path.join(__dirname, &#039;public&#039;)));
&lt;/pre&gt;&lt;br&gt;이렇게 설정하면 정상적으로 정적파일로 하위경로로 접근할 수 있다.&lt;br&gt;&lt;br&gt;일반적인 css가 아닌 &lt;a href=&quot;http://lesscss.org/&quot; target=&quot;_blank&quot;&gt;Less&lt;/a&gt; &lt;a href=&#039;http://lesscss.org/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;나 &lt;a href=&quot;http://learnboost.github.io/stylus/&quot; target=&quot;_blank&quot;&gt;Stylus&lt;/a&gt; &lt;a href=&#039;http://learnboost.github.io/stylus/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;같은 CSS&amp;nbsp; 프리컴파일러를 사용한다면 &lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
app.use(require(&#039;less-middleware&#039;)({ src: __dirname + &#039;/public&#039; }));
&lt;/pre&gt;&lt;br&gt;위과 같이 미들웨어를 설정해서 페이지 접근시 less나 stylus등이 css로 자동 컴파일되도록 해서 사용할 텐데 정적파일에 대한 미들웨어의 경로가 바뀌었기 때문에 정적파일에 접두사 경로를 추가하면 더이상 자동으로 컴파일을 수행하지 않는다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
app.use(&#039;/example&#039;, require(&#039;less-middleware&#039;)({ src: __dirname + &#039;/public&#039; }));
&lt;/pre&gt;&lt;br&gt;적정파일 미들웨어와 마찬가지로 CSS 프리 컴파일러에도 접두사 경로를 추가해 주어야 한다.&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">Jade에 HTML을 함께 사용하기</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/937" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/937" thr:count="2"/>
    <category term="node.js" />
    <category term="HTML" />
    <category term="jade" />
    <category term="view template engine" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/937</id>
    <updated>2013-05-11T00:27:47+09:00</updated>
    <published>2013-05-11T00:27:34+09:00</published>
    <summary type="html">&lt;a href=&quot;http://jade-lang.com/&quot; target=&quot;_blank&quot;&gt;Jade&lt;/a&gt; &lt;a href=&#039;http://jade-lang.com/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;는 node.js 웹프로젝트를 할 때 내가 애용하는 뷰 템플릿 엔진이기는 하지만 공부할 것도 많은 가운데 뷰 템플릿 엔진의 변화까지 추적하는 것은 쉽지 않기 때문에 큰 변화외에는 처음 알게된 부분 외에는 잘 안쓰게 되는 부분이 약간 있다. 직접 HTML을 작성할 때 Jade를 사용하면 실수도 줄일수 있고 훨씬 쉽게 작성할 수 있지만 Jade 특유의 들여쓰기를 이용한 계층구조때문에 복잡한 태그 구조는 오히려 표현하기 어려운 점도 있다.&lt;br&gt;&lt;br&gt;&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;p class=&quot;copyright&quot;&amp;gt;
&amp;nbsp; Copyright 2013 &amp;lt;a href=&quot;https://jquery.org/team/&quot;&amp;gt;The jQuery Foundation&amp;lt;/a&amp;gt;.&amp;lt;br&amp;gt;
&amp;nbsp; &amp;lt;span class=&quot;sponsor-line&quot;&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;a href=&quot;http://mediatemple.net&quot; rel=&quot;noindex,nofollow&quot; class=&quot;mt-link&quot;&amp;gt;Web hosting by Media Temple&amp;lt;/a&amp;gt; | 
&amp;nbsp; &amp;nbsp; &amp;lt;a href=&quot;http://wordpress.org/&quot; class=&quot;wp-link&quot;&amp;gt;Powered by WordPress&amp;lt;/a&amp;gt; | 
&amp;nbsp; &amp;nbsp; Thanks: &amp;lt;a href=&quot;https://jquery.org/members/&quot;&amp;gt;Members&amp;lt;/a&amp;gt;, 
&amp;nbsp; &amp;nbsp; &amp;lt;a href=&quot;https://jquery.org/sponsors/&quot;&amp;gt;Sponsors&amp;lt;/a&amp;gt;
&amp;nbsp; &amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;
&lt;/pre&gt;&lt;br&gt;예를 들어 &lt;a href=&quot;http://jquery.com/&quot; target=&quot;_blank&quot;&gt;jQuery&lt;/a&gt; &lt;a href=&#039;http://jquery.com/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;의 footer에 HTML인 위와 같은 HTML을 보자. 이를 순수 Jade의 구조에 따라 작성하면 다음과 같이 작성할 수 있다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
p.copyright
&amp;nbsp; | Copyright 2013
&amp;nbsp; a(href=&quot;https://jquery.org/team/&quot;) The jQuery Foundation
&amp;nbsp; | .
&amp;nbsp; br
&amp;nbsp; span.sponsor-line
&amp;nbsp; &amp;nbsp; a(href=&quot;http://mediatemple.net&quot;, rel=&quot;noindex,nofollow&quot;).mt-link Web hosting by Media Temple
&amp;nbsp; &amp;nbsp; |&amp;nbsp; |
&amp;nbsp; &amp;nbsp; a(href=&quot;http://wordpress.org/&quot;).wp-link Powered by Wordpress
&amp;nbsp; &amp;nbsp; |&amp;nbsp; | Thanks:
&amp;nbsp; &amp;nbsp; a(href=&quot;https://jquery.org/members/&quot;) Members
&amp;nbsp; &amp;nbsp; | ,
&amp;nbsp; &amp;nbsp; a(href=&quot;https://jquery.org/sponsor/&quot;) Sponsors
&lt;/pre&gt;&lt;br&gt;Jade에서는 HTML 태그안에 들어가는 텍스트를 여러줄로 표현할 때 파이프(|)를 사용하게 되는데 이 텍스트에 HTML이 섞여있는 경우(위의 footer처럼) Jade 구조로 표현하기가 오히려 불편해지고 가독성도 떨어지게 되고 라인이 변경될 때마다 원하는 대로 공백을 제대로 표현하려면 Jade 마크업이 오히려 이상한 모양이 된다.&lt;br&gt;&lt;br&gt;footer처럼 html태그가 잘게 쪼개지는 경우는 다음과 같이 HTML 태그를 섞어서 사용하는 것이 더 낫다. 사실 이 기능들은 0.15에서 이미 지원한것 같은데(지금은 0.30이고 2년정도 됐다..) 크게 신경안쓰고 있어서 모르고 있었따.&lt;br&gt;&lt;pre class=&quot;brush: html&quot;&gt;
p.copyright
&amp;nbsp; | Copyright 2013 &amp;lt;a href=&quot;https://jquery.org/team/&quot;&amp;gt;The jQuery Foundation&amp;lt;/a&amp;gt;.&amp;lt;br&amp;gt;
&amp;nbsp; span.sponsor-line.
&amp;nbsp; &amp;nbsp; &amp;lt;a href=&quot;http://mediatemple.net&quot; rel=&quot;noindex,nofollow&quot; class=&quot;mt-link&quot;&amp;gt;Web hosting by Media Temple&amp;lt;/a&amp;gt; |
&amp;nbsp; &amp;nbsp; &amp;lt;a href=&quot;http://wordpress.org/&quot; class=&quot;wp-link&quot;&amp;gt;Powered by WordPress&amp;lt;/a&amp;gt; |
&amp;nbsp; &amp;nbsp; Thanks: &amp;lt;a href=&quot;https://jquery.org/members/&quot;&amp;gt;Members&amp;lt;/a&amp;gt;,
&amp;nbsp; &amp;nbsp; &amp;lt;a href=&quot;https://jquery.org/sponsors/&quot;&amp;gt;Sponsors&amp;lt;/a&amp;gt;
&lt;/pre&gt;&lt;br&gt;Jade는 텍스트에 HTML을 사용해도 그대로 렌더링을 해주기 때문에 불필요하게 라인바꾸는 대신 텍스트가운데 HTML을 그대로 사용해서 작성할 수 있다. 그리고 3번 라인처럼 태그의 마지막에 마침표(.)를 찍어주면 파이프(|)가 없이도 그 하위내용은 모두 텍스트로 인식하기 때문에 그대로 HTML마크업을 작성해서 넣을 수 있다.&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">Jade에서 함수 사용하기</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/936" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/936" thr:count="0"/>
    <category term="node.js" />
    <category term="jade" />
    <category term="view template engine" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/936</id>
    <updated>2013-05-09T01:41:39+09:00</updated>
    <published>2013-05-09T01:41:39+09:00</published>
    <summary type="html">node.js에서 express를 사용할 때는 뷰 템플릿 엔진으로 &lt;a href=&quot;https://github.com/visionmedia/jade&quot; target=&quot;_blank&quot;&gt;Jade&lt;/a&gt;를 주로 사용하는 편이다. 퍼블리셔가 함께 작업하는 협업프로젝트에서는 Jade를 사용하는 것이 약간 거추장스러울수도 있지만 혼자하는 토이프로젝트에서는 이만큼 편한 뷰 템플릿 엔진도 없다고 생각한다. &lt;br&gt;&lt;br&gt;항상 쓰던대로만 쓰기에 큰 이슈가 없다가 어떤 값을 뷰 템플릿단에서 별도로 포매팅을 해주어야 할 필요가 생겼다. 이런 경우 서버에서 아예 포매팅을 해주어서 값을 전달하는 것도 방법이지만 비즈니스 로직과 프레젠테이션은 다른 영역이기 때문에 비즈니스 로직에서 전달하는 값은 좀 로우포맷으로 전달하고 프리젠테이션시에 변형하는 것이 더 좋다고 생각했다. 예를 들면 화면에 숫자를 출력하는데 천단위마다 콤마를 찍어주고 싶은 경우 비즈니스로직에서 값을 전달하면서 콤마를 찍어서 문자열로 전달하는 것보다는 숫자를 프리젠테이션단에서 콤마처리를 하는것이 맞다고 본다. 이런식으로 처리하려니 뷰템플릿엔진인 Jade에서 함수를 사용해야 했다. Jade에서는 믹스인은 제공하지만 함수를 사용하는 별도의 특수(?) 기능을 제공하지 않고 있지만 간단히 사용할 수 있다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
block content
&amp;nbsp; - var addPrefix = function(str) {
&amp;nbsp; - &amp;nbsp; return &#039;prefix : &#039; + str;
&amp;nbsp; - }
&amp;nbsp; p #{addPrefix(&#039;outsider&#039;)}
&lt;/pre&gt;&lt;br&gt;대쉬(-)를 사용하면 변수할당등에 코드를 사용할 수 있는데 여러 라인에 걸쳐서 사용할 수 있으므로 그냥 필요한 함수를 작성해 준 뒤에 값을 출력하는 부분에서 해당 함수를 자바스크립트를 쓰듯이 사용하면 된다. 위와 같이 작성한 경우 &lt;font color=&quot;#FF7635&quot;&gt;addPrefix&lt;/font&gt;라는 함수가 호출되어서 &lt;font color=&quot;#FF7635&quot;&gt;&amp;lt;p&amp;gt;&lt;/font&gt;태그안에는 &lt;font color=&quot;#FF7635&quot;&gt;prefix : outsider&lt;/font&gt;라는 문자열이 들어가게 된다. 뷰단에서 어떤 처리가 필요한 경우에는 이런 방식으로 자유롭게 함수를 작성해서 활용할 수 있다.&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">[Spring 레퍼런스] 19장 포틀릿(Portlet) MVC 프레임워크 #2</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/935" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/935" thr:count="0"/>
    <category term="Framework" />
    <category term="portlet" />
    <category term="Reference Guide" />
    <category term="Spring" />
    <category term="Spring 3.1" />
    <category term="Spring Framework" />
    <category term="spring_reference_documentation" />
    <category term="레퍼런스문서" />
    <category term="번역" />
    <category term="스프링" />
    <category term="스프링 프레임워크" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/935</id>
    <updated>2013-05-02T02:12:00+09:00</updated>
    <published>2013-05-02T02:11:51+09:00</published>
    <summary type="html">이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.6 뷰와 뷰 처리&lt;/font&gt;&lt;/b&gt;&lt;br&gt;앞에서 언급했듯이 스프링 포틀릿 MVC가 
직접 스프링 웹 MVC의 뷰 기술들을 모두 재사용한다. 이는 다양한 View 구현체들을 포함하지 않고 ViewResolver 
구현체들도 포함하고 있지 않다는 뜻이다. 자세한 내용은 Chapter 17, 뷰 기술와 Section 16.5, “뷰 처리”를 
각각 참조해 봐라.&lt;br&gt;&lt;br&gt;존재하는 View와 ViewResolver 구현체를 사용할 때 몇가지 언급해야 할 것들이 있다.&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;대
부분의 포탈(portal)은 포틀릿을 렌더링한 결과가 HTML 조각이 되기를 기대한다. 그러므로 JSP/JSTL, 
Velocity, FreeMarker, XSLT등이 모두 가능하다. 하지만 포틀릿에서는 다른 타입의 문서를 반환하는 뷰는 적당하지
 않다.&lt;/li&gt;&lt;li&gt;포틀릿내에서는 HTTP 리다이렉트 같은 것이 존재하지 않는다. (ActionResponse의 
sendRedirect(..) 메서드는 포탈내에서 존재할 수 없다.) 그러므로 포틀릿 MVC 내에서는 RedirectView와 
&#039;redirect:&#039; 접두사의 사용이 제대로 동작하지 않을 것이다.&lt;/li&gt;&lt;li&gt;포틀릿 MVC에서 &#039;forward:&#039; 접두사를 사용할 수도 있다. 하지만 포틀릿이므로 현재 URL이 무엇인지 알 수 없다. 즉, 웹 어플리케이션에서 다른 리소스에 접근하는 상대 URL을 사용할수 없고 절대 URL을 사용해야 한다.&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;또한 JSP 개발에서 새로운 스프링 Taglib와 스프링 폼 Taglib 모두 포틀릿 뷰에서도 서블릿 뷰의 동작과 완전히 똑같히 동작한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.7 멀티파트(파일 업로드) 지원&lt;/font&gt;&lt;/b&gt;&lt;br&gt;스
프링 포틀릿 MVC에는 포틀릿 어플리케이션에서 웹 MVC처럼 파일 업로드를 다루는 멀티파트 지원을 내장하고 있다. 멀티파트 지원은
 org.springframework.web.portlet.multipart 패키지에 정의된 플러그할 수 있는 
PortletMultipartResolver 객체로 설계되었다. 스프링은 &lt;a href=&quot;http://jakarta.apache.org/commons/fileupload&quot; target=&quot;_blank&quot;&gt;Commons FileUpload&lt;/a&gt; &lt;a href=&#039;http://jakarta.apache.org/commons/fileupload&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;와 함께 사용할 수 있는 PortletMultipartResolver를 제공한다. 파일 업로드를 지원하는 방법은 이 섹션의 남은 부분에서 설명할 것이다.&lt;br&gt;&lt;br&gt;일
부 개발자들은 멀티파트를 직접 다루기 원하므로 기본적으로 스프링 포틀릿 MVC는 멀티파트를 처리하지 않을 것이다. 웹 어플리케이션
 컨텍스트에 멀티파트 리졸버를 추가해서 직접 멀티파트 지원을 활성화해야 한다. 활성화하고 나면 DispatcherPortlet이 각
 요청이 멀티파트인지 검사할 것이다. 멀티파트가 아니라면 요청은 기대대로 계속 된다. 하지만 요청이 멀티파트라면 컨텍스트에 선언한
 PortletMultipartResolver를 사용할 것이다. 그 다음부터는 요청의 멀티파트 속성을 다른 속성처럼 다룰 것이다.&lt;br&gt;&lt;br&gt;&lt;div style=&quot;padding:10px; background-color:#E4E4E4&quot;&gt;&lt;font color=&quot;#000000&quot;&gt;&lt;b&gt;Note&lt;/b&gt;&lt;br&gt;설
정한 모든 PortletMultipartResolver 빈은 반드시 &quot;portletMultipartResolver&quot;라는 
아이디(또는 이름)을 가져야 한다. 다른 이름으로 PortletMultipartResolver를 정의했다면 
DispatcherPortlet가 PortletMultipartResolver를 찾지 못하므로 멀티파트 지원이 동작하지 않을 
것이다.&lt;/font&gt;&lt;/div&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.7.1 PortletMultipartResolver의 사용&lt;/font&gt;&lt;/b&gt;&lt;br&gt;다음 예제는 CommonsPortletMultipartResolver를 사용하는 방법을 보여준다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;bean id=&quot;portletMultipartResolver&quot; class=&quot;org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver&quot;&amp;gt;
&amp;nbsp; &amp;lt;!-- 사용할 수 있는 프로퍼티 중 하나; 파일의 최대 바이트 크기 --&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;maxUploadSize&quot; value=&quot;100000&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;&lt;br&gt;물
론 멀티파트 리졸버가 동작하도록 클래스 패스에 적절한 jar도 둘어야 한다. CommonsMultipartResolver의 경우 
commons-fileupload.jar를 사용해야 한다. Commons FileUpload의 과거의 버전은 JSR-168 포틀릿
 어플리케이션을 지원하지 않으므로 최소한 1.1 버전의 Commons FileUpload를 사용해야 한다.&lt;br&gt;&lt;br&gt;포틀릿 
MVC가 멀티파트 요청을 처리하도록 설정하는 방법을 보았으니 이제 실제로 어떻게 사용하는지 보자. 
DispatcherPortlet가 멀티파트 요청을 발견하면 DispatcherPortlet가 컨텍스트에 선언한 리졸버를 활성화하고
 요청을 전달한다. 그 다음 리졸버는 ActionRequest을 멀티파트 파일 업로드를 지원하는 
MultipartActionRequest로 감싼다. MultipartActionRequest를 사용해서 해당 요청의 멀티파트 
정보를 얻을 수 있고 컨트롤러에서 멀티파트 파일 자체에 실제로 접근할 수 있다.&lt;br&gt;&lt;br&gt;RenderRequest의 일부가 아니라 ActionRequest의 일부로만 멀티파트 파일을 받을 수 있다는 점을 기억해라.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.7.2 폼의 파일 업로드 처리&lt;/font&gt;&lt;/b&gt;&lt;br&gt;PortletMultipartResolver
가 자신의 작업을 완료한 후에 요청은 다름 요청과 마찬가지로 처리된다. PortletMultipartResolver를 사용하려면 
업로드 필드를 가진 폼(아래 예제 참고)을 생성하고 스프링이 파일을 폼(지원 객체, backing object)에 바인딩하도록 
한다. 실제로 사용자가 파일을 업로드하게 하려면 (JSP/HTML) 폼을 생성해야 한다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;h1&amp;gt;Please upload a file&amp;lt;/h1&amp;gt;
&amp;lt;form method=&quot;post&quot; action=&quot;&amp;lt;portlet:actionURL/&amp;gt;&quot; enctype=&quot;multipart/form-data&quot;&amp;gt;
&amp;nbsp; &amp;lt;input type=&quot;file&quot; name=&quot;file&quot;/&amp;gt;
&amp;nbsp; &amp;lt;input type=&quot;submit&quot;/&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;&lt;br&gt;여
기서 보듯이 byte[] 배열을 담고 있는 빈의 프로퍼티와 일치하는 “file”라는 이름의 필드를 생성했다. 게다가 브라우저가 
멀티파트 필드를 어떻게 인코딩해야 하는지 알 수 있도록 인코딩 속성(enctype=&quot;multipart/form-data&quot;)을 
추가했다.(이 부분을 잊지 마라!)&lt;br&gt;&lt;br&gt;순식간에 문자열이나 프리미티브 타입으로 변환할 수 없는 다른 프로퍼티와 마찬가지로
 객체에 바이너리 데이터를 보관하려면 PortletRequestDataBinder로 커스텀 에디터를 등록해야 한다. 파일을 다루고
 객체에 설정하는데 사용할 수 있는 두개의 에디터가 존재한다. 파일을 문자열(사용자가 정의한 캐릭터셋(character set)을
 사용해서)로 변환하는 StringMultipartFileEditor와 파일을 바이트 배열로 변환하는 
ByteArrayMultipartFileEditor가 있다. 이 기능들은 CustomDateEditor와 유사하다.&lt;br&gt;&lt;br&gt;그러므로 폼을 사용해서 파일을 업로드하게 하려면 리졸버, 빈을 처리할 컨트롤러에 대한 매칭과 컨트롤러를 선언한다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;bean id=&quot;portletMultipartResolver&quot;
&amp;nbsp; &amp;nbsp; class=&quot;org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver&quot;/&amp;gt;

&amp;lt;bean class=&quot;org.springframework.web.portlet.handler.PortletModeHandlerMapping&quot;&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;portletModeMap&quot;&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;view&quot; value-ref=&quot;fileUploadController&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/map&amp;gt;
&amp;nbsp; &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;fileUploadController&quot; class=&quot;examples.FileUploadController&quot;&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;commandClass&quot; value=&quot;examples.FileUploadBean&quot;/&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;formView&quot; value=&quot;fileuploadform&quot;/&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;successView&quot; value=&quot;confirmation&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;&lt;br&gt;이렇게 한 후 컨트롤러와 파일 프로퍼티를 가진 실제 클래스를 생성한다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
public class FileUploadController extends SimpleFormController {

&amp;nbsp; public void onSubmitAction(ActionRequest request, ActionResponse response,
&amp;nbsp; &amp;nbsp; Object command, BindException errors) throws Exception {

&amp;nbsp; &amp;nbsp; // 빈을 캐스팅한다
&amp;nbsp; &amp;nbsp; FileUploadBean bean = (FileUploadBean) command;

&amp;nbsp; &amp;nbsp; // 내용이 존재하는지 확인한다
&amp;nbsp; &amp;nbsp; byte[] file = bean.getFile();
&amp;nbsp; &amp;nbsp; if (file == null) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; // 흠.. 뭔가 잘못됐다. 사용자가 아무것도 업로드하지 않았다
&amp;nbsp; &amp;nbsp; }

&amp;nbsp; &amp;nbsp; // 여기서 파일로 어떤 작업을 한다
&amp;nbsp; }

&amp;nbsp; protected void initBinder(
&amp;nbsp; &amp;nbsp; &amp;nbsp; PortletRequest request, PortletRequestDataBinder binder) throws Exception {
&amp;nbsp; &amp;nbsp; // 실제로 Multipart 인스턴스를 byte[]로 변환할 수 있게 하려면
&amp;nbsp; &amp;nbsp; // 커스텀 에디터를 등록해야 한다
&amp;nbsp; &amp;nbsp; binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
&amp;nbsp; &amp;nbsp; // 이제 스프링이 멀티파트 객체를 어떻게 다루고 어떻게 변환하는지 알고 있다
&amp;nbsp; }
}

public class FileUploadBean {
&amp;nbsp; private byte[] file;

&amp;nbsp; public void setFile(byte[] file) {
&amp;nbsp; &amp;nbsp; this.file = file;
&amp;nbsp; }

&amp;nbsp; public byte[] getFile() {
&amp;nbsp; &amp;nbsp; return file;
&amp;nbsp; }
}
&lt;/pre&gt;&lt;br&gt;여
기서 보듯이 FileUploadBean은 파일을 담고 있는 byte[] 타입의 프로퍼티를 가진다. 컨트롤러는 스프링이 멀티파트 
객체를 실제로 변환하는 방법을 알게 하고 리볼버가 빈이 지정한 프로퍼티를 찾도록 하는 커스텀 에디터를 등록한다. 이 예제에서는 
빈의 byte[] 프로퍼티 자체로는 아무것도 하지 않지만 실사례에서는 원하는 무엇이든지 할 수 있다.(데이터베이스에 저장하거나 
누군가에게 메일로 보내는 등)&lt;br&gt;&lt;br&gt;폼을 지원하는 객체(form backing object)의 문자열 타입의 프로퍼티에 직접 바인딩되는 동일한 예제는 다음과 같을 것이다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
public class FileUploadController extends SimpleFormController {

&amp;nbsp; public void onSubmitAction(ActionRequest request, ActionResponse response,
&amp;nbsp; &amp;nbsp; &amp;nbsp; Object command, BindException errors) throws Exception {

&amp;nbsp; &amp;nbsp; // 빈을 캐스팅한다
&amp;nbsp; &amp;nbsp; FileUploadBean bean = (FileUploadBean) command;

&amp;nbsp; &amp;nbsp; // 내용이 존재하는지 검사한다
&amp;nbsp; &amp;nbsp; String file = bean.getFile();
&amp;nbsp; &amp;nbsp; if (file == null) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; // 흠.. 뭔가 잘못됐다. 사용자가 아무것도 업로드하지 않았다
&amp;nbsp; &amp;nbsp; }

&amp;nbsp; &amp;nbsp; // 여기서 파일로 어떤 작업을 한다
&amp;nbsp; }

&amp;nbsp; protected void initBinder(
&amp;nbsp; &amp;nbsp; PortletRequest request, PortletRequestDataBinder binder) throws Exception {

&amp;nbsp; &amp;nbsp; // 실제로 Multipart 인스턴스를 문자열로 변환할 수 있게 하려면
&amp;nbsp; &amp;nbsp; // 커스텀 에디터를 등록해야 한다
&amp;nbsp; &amp;nbsp; binder.registerCustomEditor(String.class,
&amp;nbsp; &amp;nbsp; &amp;nbsp; new StringMultipartFileEditor());
&amp;nbsp; &amp;nbsp; // 이제 스프링이 멀티파트 객체를 어떻게 다루고 어떻게 변환하는지 알고 있다
&amp;nbsp; }
}

public class FileUploadBean {

&amp;nbsp; private String file;

&amp;nbsp; public void setFile(String file) {
&amp;nbsp; &amp;nbsp; this.file = file;
&amp;nbsp; }

&amp;nbsp; public String getFile() {
&amp;nbsp; &amp;nbsp; return file;
&amp;nbsp; }
}
&lt;/pre&gt;&lt;br&gt;물론 이 마지막 예제는 평범한 텍스트 파일을 업로드하는 경우에만 (논리적으로) 말이된다. (이미지 파일을 업로드하는 경우에는 제대로 동작하지 않을 것이다.)&lt;br&gt;&lt;br&gt;세
번째(그리고 마지막) 선택사항은 (폼을 지원하는) 객체의 클래스에 선언한 MultipartFile 프로퍼티에 직접 바인딩하는 
것이다. 이 경우에는 타입 변환을 수행할 필요가 없으므로 어떤 커스텀 프로퍼티 에디터도 등록할 필요가 없다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
public class FileUploadController extends SimpleFormController {

&amp;nbsp; public void onSubmitAction(ActionRequest request, ActionResponse response,
&amp;nbsp; &amp;nbsp; &amp;nbsp; Object command, BindException errors) throws Exception {

&amp;nbsp; &amp;nbsp; // 빈을 캐스팅한다
&amp;nbsp; &amp;nbsp; FileUploadBean bean = (FileUploadBean) command;

&amp;nbsp; &amp;nbsp; // 내용이 존재하는지 검사한다
&amp;nbsp; &amp;nbsp; MultipartFile file = bean.getFile();
&amp;nbsp; &amp;nbsp; if (file == null) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; // 흠.. 뭔가 잘못됐다. 사용자가 아무것도 업로드하지 않았다
&amp;nbsp; &amp;nbsp; }

&amp;nbsp; &amp;nbsp; // 여기서 파일로 어떤 작업을 한다
&amp;nbsp; }
}

public class FileUploadBean {

&amp;nbsp; private MultipartFile file;

&amp;nbsp; public void setFile(MultipartFile file) {
&amp;nbsp; &amp;nbsp; this.file = file;
&amp;nbsp; }

&amp;nbsp; public MultipartFile getFile() {
&amp;nbsp; &amp;nbsp; return file;
&amp;nbsp; }
}
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.8 예외 처리&lt;/font&gt;&lt;/b&gt;&lt;br&gt;서
블릿 MVC와 마찬가지로 포틀릿 MVC도 요청에 적합한 핸들러가 요청을 처리하는 도중에 발생한 의도하지 않는 예외를 쉽게 
처리해주는 HandlerExceptionResolver를 제공한다. 포틀릿 MVC도 던져질 예의의 클래스명을 받아서 뷰 이름에 
매핑할 수 있도록 포틀릿에 한정된 구현체 SimpleMappingExceptionResolver를 제공한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.9 어노테이션에 기반한 컨트롤러 구성&lt;/font&gt;&lt;/b&gt;&lt;br&gt;스
프링 2.5는 @RequestMapping, @RequestParam, @ModelAttribute 등의 어노테이션을 사용해서 
MVC 컨트롤러에 어노테이션에 기반한 프로그래밍 모델을 도입했다. 이 어노테이션 지원은 서블릿 MVC와 포틀릿 MVC 모두에서 
사용할 수 있다. 이 방식으로 구현한 컨트롤러는 특정 기반 클래스를 확장하거나 특정 인터페이스를 구현하지 않는다. 게다가 원한다면
 서블릿이나 포틀릿 기능에 쉽게 접근할 수 있기는 하지만 보통은 서블릿이나 포틀릿 API에 직접 의존성을 갖지 않는다.&lt;br&gt;&lt;br&gt;다음 섹션에서는 이러한 어노테이션을 포틀릿 환경에서 보통 어떻게 사용하는지 설명한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.1 어노테이션 지원에 대한 디스패처 설정&lt;/font&gt;&lt;/b&gt;&lt;br&gt;디
스패처에 대응되는 HandlerMapping(타입 수준의 어노테이션에서)이나 HandlerAdapter(메서드 수준의 
어노테이션에서)이 존재하는 경우에만 @RequestMapping를 처리할 것이다. 이 동작이 DispatcherServlet과 
DispatcherPortlet에서 모두 기본값이다.&lt;br&gt;&lt;br&gt;하지만 커스텀 HandlerMappings이나 
HandlerAdapters를 정의했다면 대응되는 DefaultAnnotationHandlerMapping나 
AnnotationMethodHandlerAdapter도 역시 정의해야 한다. (@RequestMapping를 사용할 것이라면)&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
&amp;nbsp; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
&amp;nbsp; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans
&amp;nbsp; &amp;nbsp; http://www.springframework.org/schema/beans/spring-beans-3.0.xsd&quot;&amp;gt;

&amp;nbsp; &amp;lt;bean class=&quot;org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping&quot;/&amp;gt;

&amp;nbsp; &amp;lt;bean class=&quot;org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter&quot;/&amp;gt;

&amp;nbsp; // ... (컨트롤러 빈 정의) ...
&amp;lt;/beans&amp;gt;
&lt;/pre&gt;&lt;br&gt;매
핑 전략을 커스터마이징한다면 DefaultAnnotationHandlerMapping이나 
AnnotationMethodHandlerAdapter를 명시적으로 정의하는 것도 절적하다. 예를 들어 커스텀 
WebBindingInitializer를 지정하는 것 등이다.(아래 참조)&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.2 @Controller로 컨트롤러 정의하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;@Controller
 어노테이션은 특정 클래스가 컨트롤러의 역할을 한다는 것을 나타낸다. 여기서 어떤 기반 컨트롤러 클래스를 확장하거나 포틀릿 
API를 참조할 필요가 없다. 필요하다면 여전히 포틀릿에 특화된 기능을 참조할 수 있다.&lt;br&gt;&lt;br&gt;@Controller 
어노테이션의 기본적인 목적은 어노테이션이 붙은 클래스에 스테레오타입으로(클래스의 역할을 나타내면서) 동작하는 것이다. 디스패처는 
매핑된 메서드와 @RequestMapping 어노테이션을 참조하기 위해서 이렇게 어노테이션이 붙은 클래스를 스캔할 것이다.(다음 
섹션을 참고해라.)&lt;br&gt;&lt;br&gt;어노테이션이 붙은 컨트롤러 빈은 디스패처의 컨텍스트에서 표준 스프링 빈 정의를 사용해서 명시적으로
 정의할 것이다. 하지만 @Controller 스테레오타입도 클래스패스의 컴포넌트 클래스 탐지와 이 클래스들의 빈 정의의 자동 
등록에 대한 스프링 2.5의 일반적인 지원으로 자동탐지를 허용한다.&lt;br&gt;&lt;br&gt;이렇게 어노테이션이 붙은 컨트롤러의 자동탐지를 활성화하려면 설정에 컴포넌트 스캔을 추가해야 한다. 이는 다음 XML 코드에서 보듯이 spring-context 스키마를 사용해서 쉽게 추가할 수 있다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
&amp;nbsp; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
&amp;nbsp; xmlns:p=&quot;http://www.springframework.org/schema/p&quot;
&amp;nbsp; xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
&amp;nbsp; xsi:schemaLocation=&quot;
&amp;nbsp; &amp;nbsp; http://www.springframework.org/schema/beans
&amp;nbsp; &amp;nbsp; http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
&amp;nbsp; &amp;nbsp; http://www.springframework.org/schema/context
&amp;nbsp; &amp;nbsp; http://www.springframework.org/schema/context/spring-context-3.0.xsd&quot;&amp;gt;

&amp;nbsp; &amp;lt;context:component-scan base-package=&quot;org.springframework.samples.petportal.portlet&quot;/&amp;gt;

&amp;nbsp; // ...
&amp;lt;/beans&amp;gt;
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.3 @RequestMapping로 요청 매핑하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;@RequestMapping
 어노테이션은 전체 클래스나 특정 핸들러 메서드에 &#039;VIEW&#039;/&#039;EDIT&#039;같은 포틀릿 모드를 매핑하는데 사용한다. 보통 타입수준의
 어노테이션은 폼 컨트롤러에 특정 모드(또는 모드에 파라미터를 추가한 상황)를 매핑하고 추가적인 메서드 수준의 어노테이션은 특정 
포틀릿 요청 파라미터로 주요 매핑을 &#039;제한&#039;한다.&lt;br&gt;&lt;br&gt;&lt;div style=&quot;padding:10px; background-color:#C9EDFF&quot;&gt;&lt;font color=&quot;#000000&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br&gt;타
입 수준의 @RequestMapping은 Controller의 평범함 구현체에도 사용할 것이다. 이 경우에 요청을 처리하는 코드는
 전통적인 handle(Action|Render)Request 시그니처를 따를 것이지만 컨트롤러의 매핑은 
@RequestMapping 어노테이션으로 표현할 것이다. 이는 SimpleFormController같은 미리 
만들어진(pre-built) Controller 기반 클래스에서도 동작한다.&lt;br&gt;&lt;br&gt;&lt;/font&gt;&lt;font color=&quot;#000000&quot;&gt;다음 부분에서 어노테이션이 붙은 핸들러 메서드에 기반한 컨트롤러를 다룰 것이다.&lt;/font&gt;&lt;br&gt;&lt;/div&gt;&lt;br&gt;다음은 이 어노테이션을 사용한 PetPortal 예제 어플리케이션의 폼 컨트롤러 예제이다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
@Controller
@RequestMapping(&quot;EDIT&quot;)
@SessionAttributes(&quot;site&quot;)
public class PetSitesEditController {

&amp;nbsp; private Properties petSites;

&amp;nbsp; public void setPetSites(Properties petSites) {
&amp;nbsp; &amp;nbsp; this.petSites = petSites;
&amp;nbsp; }

&amp;nbsp; @ModelAttribute(&quot;petSites&quot;)
&amp;nbsp; public Properties getPetSites() {
&amp;nbsp; &amp;nbsp; return this.petSites;
&amp;nbsp; }

&amp;nbsp; @RequestMapping&amp;nbsp; // 기본값 (action=list)
&amp;nbsp; public String showPetSites() {
&amp;nbsp; &amp;nbsp; return &quot;petSitesEdit&quot;;
&amp;nbsp; }

&amp;nbsp; @RequestMapping(params = &quot;action=add&quot;)&amp;nbsp; // 렌더링 단계
&amp;nbsp; public String showSiteForm(Model model) {
&amp;nbsp; &amp;nbsp; // 최초의 폼과 오류를 다시 보여주는 데 사용한다
&amp;nbsp; &amp;nbsp; if (!model.containsAttribute(&quot;site&quot;)) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; model.addAttribute(&quot;site&quot;, new PetSite());
&amp;nbsp; &amp;nbsp; }
&amp;nbsp; &amp;nbsp; return &quot;petSitesAdd&quot;;
&amp;nbsp; }

&amp;nbsp; @RequestMapping(params = &quot;action=add&quot;)&amp;nbsp; // 액션 단계
&amp;nbsp; public void populateSite(
&amp;nbsp; &amp;nbsp; &amp;nbsp; @ModelAttribute(&quot;site&quot;) PetSite petSite, BindingResult result,
&amp;nbsp; &amp;nbsp; &amp;nbsp; SessionStatus status, ActionResponse response) {

&amp;nbsp; &amp;nbsp; new PetSiteValidator().validate(petSite, result);
&amp;nbsp; &amp;nbsp; if (!result.hasErrors()) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; this.petSites.put(petSite.getName(), petSite.getUrl());
&amp;nbsp; &amp;nbsp; &amp;nbsp; status.setComplete();
&amp;nbsp; &amp;nbsp; &amp;nbsp; response.setRenderParameter(&quot;action&quot;, &quot;list&quot;);
&amp;nbsp; &amp;nbsp; }
&amp;nbsp; }

&amp;nbsp; @RequestMapping(params = &quot;action=delete&quot;)
&amp;nbsp; public void removeSite(@RequestParam(&quot;site&quot;) String site, ActionResponse response) {
&amp;nbsp; &amp;nbsp; this.petSites.remove(site);
&amp;nbsp; &amp;nbsp; response.setRenderParameter(&quot;action&quot;, &quot;list&quot;);
&amp;nbsp; }
}
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.4 지원하는 핸들러 메서드 아규먼트&lt;/font&gt;&lt;/b&gt;&lt;br&gt;@RequestMapping
 어노테이션이 붙은 핸들러 메서드는 아주 유연한 시그니처를 가질 수 있게 한다. 이 메서드는 임의의 순서(원한다면 커맨드 객체뒤에
 와야 하는 대응되는 유효성 검사 결과를 제외하고)로 다음 타입의 인자를 가질 것이다.&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;요청 또는 응답
 객체(포틀릿 API). PortletRequest / ActionRequest / RenderRequest같은 특정 요청/응답 
타입을 선택할 것이다. 명시적으로 선언한 action/render 인자도 핸들러 메서드에 특정 요청 타입을 매핑하는데 
사용한다.(action과 render 요청을 구별하는 어떤 정보도 주어지지 않은 경우에)&lt;/li&gt;&lt;li&gt;PortletSession 타입의 세션 객체 (포틀릿 API). 이 타입의 인자는 대응되는 세션에 존재하게 강제할 것이다. 그 결과 이러한 인자는 null이 되지 않을 것이다.&lt;/li&gt;&lt;li&gt;org.springframework.web.context.request.WebRequest
나 org.springframework.web.context.request.NativeWebRequest. 네이티브 서블릿/포틀릿
 API에 의존하지 않고 요청/세션 속성 접근과 마찬가지로 일반적인 요청 파라미터의 접근을 허용한다.&lt;/li&gt;&lt;li&gt;현재 요청 로케일에 대한 java.util.Locale (포틀릿 환경의 포탈(portal) 로케일)&lt;/li&gt;&lt;li&gt;요청의 내용에 접근하는 java.io.InputStream / java.io.Reader. 이는 포틀릿 API가 노출하는 로우(raw) InputStream/Reader가 될 것이다.&lt;/li&gt;&lt;li&gt;응답의 내용을 생성하는 java.io.OutputStream / java.io.Writer. 이는 포틀릿 API가 노출하는 로우(raw) OutputStream/Writer가 될 것이다.&lt;/li&gt;&lt;li&gt;특정 포틀릿 요청 파라미터에 접근하는 @RequestParam 어노테이션이 붙은 파라미터들. 파라미터 값을 선언한 메서드 인자 타입으로 변환할 것이다.&lt;/li&gt;&lt;li&gt;웹 뷰에 노출될 암시적인(implicit) 모델을 풍부하게 하는 java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap.&lt;/li&gt;&lt;li&gt;@InitBinder
 메서드나 HandlerAdapter 설정에 의존하면서 커스터마이징할 수 있는 타입 변환으로 빈 프로퍼티나 필드로 파라미터에 
바인드할 Command/form 객체. (AnnotationMethodHandlerAdapter에 
&quot;webBindingInitializer&quot; 프로퍼티를 봐라.) 이러한 커맨드 객체와 그 유효성검사 결과는 기본적으로 프로퍼티 
표시법(notation)으로 정규화되지 않은 커맨드 클래스를 사용해서 모델 속성으로 노출될 것이다.(예를 들면 
&quot;mypackage.OrderAddress&quot; 타입의 &quot;orderAddress&quot;) 특정 모델 속성 이름을 선언하려면 파라미터 수준의
 ModelAttribute 어노테이션을 지정해라.&lt;/li&gt;&lt;li&gt;앞의 command/form 객체(바로 앞의 인자)에 대한 org.springframework.validation.Errors / org.springframework.validation.BindingResult 유효성검사 결과.&lt;/li&gt;&lt;li&gt;폼
 처리를 완료로 표시하는 org.springframework.web.bind.support.SessionStatus 산태 핸들. 
(핸들러 타입 수준의 @SessionAttributes 어노테이션이 가리키는 세션 속성을 정리한다.)&lt;/li&gt;&lt;/ul&gt;핸들러 메서드는 다음의 반환 타입을 지원한다.&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;커맨드객체와 @ModelAttribute 어노테이션이 붙은 참조 데이터 접근자 메서드의 결과로 암묵적으로 풍부해진 모델을 가진 ModelAndView 객체.&lt;/li&gt;&lt;li&gt;RequestToViewNameTranslator로 암묵적으로 결정된 뷰 이름과 커맨드객체와 @ModelAttribute 어노테이션이 붙은 참조 데이터 접근자 메서드의 결과로 암묵적으로 풍부해진 모델을 가진 Model 객체.&lt;/li&gt;&lt;li&gt;RequestToViewNameTranslator
로 암묵적으로 결정한 뷰이름을 가진 모델을 노출하는 Map 객체로 모델은 커맨드 객체와 @ModelAttribute 어노테이션이 
붙은 참조 데이터 접근자 메서드의 결과로 암묵적으로 풍부해진다.&lt;/li&gt;&lt;li&gt;커맨드 객체와 @ModelAttribute 
어노테이션이 붙은 참조 데이터 접근자 메서드로 암묵적으로 결정된 모델을 가진 View 객체. Model 인자를 선언해서(앞부분 
참고) 핸들러 메서드도 모델을 프로그래밍적으로 강화할 수도 있다.&lt;/li&gt;&lt;li&gt;뷰 이름으로 해석되는 String 값으로커맨드 
객체와 @ModelAttribute 어노테이션이 붙은 참조 데이터 접근자 메서드로 암묵적으로 결정된 모델을 가진다. Model 
인자를 선언해서(앞부분 참고) 핸들러 메서드도 모델을 프로그래밍적으로 강화할 수도 있다.&lt;/li&gt;&lt;li&gt;메서드가 직접 응답을 처리하면 void이다. (예를 들면 응답 내용을 직접 작성하는 등)&lt;/li&gt;&lt;li&gt;다
른 모든 반환 타입은 메서드 수준의 @ModelAttribute로 지정한 속성명을 사용해서(그렇지 않으면 방환 타입의 클래스에 
기반한 기본 속성명) 뷰로 노출하는 단일 모델 속성으로 간주할 것이다. 모델은 커맨드 객체와 @ModelAttribute 
어노테이션이 붙은 참조 데이터 접근자 메서드의 결과로 암묵적으로 풍부해진다.&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.5 @RequestParam로 요청 파라미터를 메서드 파라미터로 바인딩하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;컨트롤러에서 요청 파라미터를 메서드 파라미터로 바인딘하는데 @RequestParam 어노테이션을 사용한다.&lt;br&gt;&lt;br&gt;PetPortal 예제 어플리케이션의 다음 코드는 @RequestParam의 사용방법을 보여준다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
@Controller
@RequestMapping(&quot;EDIT&quot;)
@SessionAttributes(&quot;site&quot;)
public class PetSitesEditController {

&amp;nbsp; // ...

&amp;nbsp; public void removeSite(@RequestParam(&quot;site&quot;) String site, ActionResponse response) {
&amp;nbsp; &amp;nbsp; this.petSites.remove(site);
&amp;nbsp; &amp;nbsp; response.setRenderParameter(&quot;action&quot;, &quot;list&quot;);
&amp;nbsp; }

&amp;nbsp; // ...
}
&lt;/pre&gt;&lt;br&gt;이
 어노테이션을 사용한 파라미터들은 기본적으로 필수값이지만 @RequestParam의 required 속성을 false로 지정해서 
해당 파라미터를 선택적인 값으로 지정할 수 있다. (예시: @RequestParam(value=&quot;id&quot;, 
required=false))&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.6 @ModelAttribute이 붙은 모델에서 데이터로의 링크 제공하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;@ModelAttribute
은 컨트롤러에서 두가지 사용 시나리오를 가진다. 메서드 파라미터에 사용한 경우 모델 속성을 어노트에션이 붙은 특정 메서드 
파라미터에 매핑할 때 사용한다.(다음 예제의 populateSite() 메서드를 봐라.) 이것이 컨트롤러가 폼에서 들어온 데이터를
 가진 객체에 대한 참조를 얻는 방법이다. 게다가 파라미터를 일반적인 java.lang.Object가 아니라 폼 지원 
객체(form backing object)의 특정 타입으로 선언할 수 있으므로 타입 안정성을 높혀준다.&lt;br&gt;&lt;br&gt;@ModelAttribute
을 모델에 대한 참조 데이터(reference data)를 제공하려고 메서드 수준에서 사용할 수도 있다. (다음 예제의 
getPetSites()를 봐라.) 이 사용방법에서는 메서드 시그니처가 위의 @RequestMapping 어노테이션에서 문서화된 
것과 같은 타입을 담고 있을 수 있다.&lt;br&gt;&lt;br&gt;Note: @ModelAttribute 어노테이션이 붙은 메서드는 선택된 
@RequestMapping 어노테이션이 붙은 핸들러 메서드 이전에 실행될 것이다. 이 메서드들은 효율적으로 특성 속성(때로는 
데이터베이스에서 가져온)을 가진 암묵적인 모델을 미리 존재하게 한다. 이러한 속성은 선택된 핸들러 메서드에서 
@ModelAttribute 어노테이션이 붙은 핸들러 메서드(잠재적으로 바인딩과 유효성검사를 적용해서)를 통해서 이미 접근했을 수
 있다.&lt;br&gt;&lt;br&gt;다음 예제 코드는 이 어노테이션의 두가지 사용방법을 보여준다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
@Controller
@RequestMapping(&quot;EDIT&quot;)
@SessionAttributes(&quot;site&quot;)
public class PetSitesEditController {

&amp;nbsp; // ...

&amp;nbsp; @ModelAttribute(&quot;petSites&quot;)
&amp;nbsp; public Properties getPetSites() {
&amp;nbsp; &amp;nbsp; return this.petSites;
&amp;nbsp; }

&amp;nbsp; @RequestMapping(params = &quot;action=add&quot;)&amp;nbsp; // action 단계
&amp;nbsp; public void populateSite(
&amp;nbsp; &amp;nbsp; &amp;nbsp; @ModelAttribute(&quot;site&quot;) PetSite petSite, BindingResult result,
&amp;nbsp; &amp;nbsp; &amp;nbsp; SessionStatus status, ActionResponse response) {

&amp;nbsp; &amp;nbsp; new PetSiteValidator().validate(petSite, result);
&amp;nbsp; &amp;nbsp; if (!result.hasErrors()) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; this.petSites.put(petSite.getName(), petSite.getUrl());
&amp;nbsp; &amp;nbsp; &amp;nbsp; status.setComplete();
&amp;nbsp; &amp;nbsp; &amp;nbsp; response.setRenderParameter(&quot;action&quot;, &quot;list&quot;);
&amp;nbsp; &amp;nbsp; }
&amp;nbsp; }
}
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.7 @SessionAttributes로 세션에 저장할 속성 지정하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;최
상위 @SessionAttributes 어노테이션은 특정 핸들러가 사용하는 세션 속성을 선언한다. 이는 보통 모델 속성의 이름이나
 모델 속성의 타입의 목록이 될 것이고 이 모델 속성은 투명하게 세션이나 대화식 스토리지에에 저장해야 하고 뒤이은 요청간에 폼지원
 빈으로 제공한다.&lt;br&gt;&lt;br&gt;다음 코드는 이 어노테이션의 사용방법을 보여준다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
@Controller
@RequestMapping(&quot;EDIT&quot;)
@SessionAttributes(&quot;site&quot;)
public class PetSitesEditController {
&amp;nbsp; // ...
}
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.8 WebDataBinder 초기화 커스터마이징&lt;/font&gt;&lt;/b&gt;&lt;br&gt;스
프링의 WebDataBinder를 통해서 PropertyEditor 등으로 요청 파라미터 바인딩을 커스터마이징하려면 컨트롤러내에서
 @InitBinder 어노테이션이 붙은 메서드를 사용하거나 커스텀 WebBindingInitializer를 제공해서 설정을 
외부로 내낼 수 있다(externalize).&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.8.1 @InitBinder로 데이터 바인딩 커스터마이징하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;@InitBinder
 어노테이션이 붙은 컨트롤러 메서드로 컨트롤러 클래스에서 웹 데이터 바인딩을 직접 설정할 수 있다. @InitBinder는 
어노테이션이 붙은 핸들러 메서드의 커맨드와 폼 객체 아규먼트를 유지하는데 사용할 WebDataBinder를 초기화하는 메서드를 
식별한다.&lt;br&gt;&lt;br&gt;이러한 init-binder 메서드는 @RequestMapping이 지원하는 아규먼트 중 커맨드/폼 객체와
 이에 대응되는 유효성검사 결과 객체를 제외한 모든 아규먼트를 지원한다. init-binder 메서드는 반드시 반환값을 갖지 
않아야 한다. 그러므로 일반적으로 void로 선언한다. 전형적인 아규먼트는 컨텍스트에 특화된 에디터를 등록하는 코드를 허용하는 
WebRequest나 java.util.Locale를 조합한 WebDataBinder를 포함한다.&lt;br&gt;&lt;br&gt;다음 예제는 모든 java.util.Date 폼 프로퍼티에 대한 CustomDateEditor를 설정하는 @InitBinder의 사용방법을 보여준다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
@Controller
public class MyFormController {

&amp;nbsp; @InitBinder
&amp;nbsp; public void initBinder(WebDataBinder binder) {
&amp;nbsp; &amp;nbsp; SimpleDateFormat dateFormat = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;);
&amp;nbsp; &amp;nbsp; dateFormat.setLenient(false);
&amp;nbsp; &amp;nbsp; binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
&amp;nbsp; }

&amp;nbsp; // ...
}
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.9.8.2 커스텀 WebBindingInitializer 설정&lt;/font&gt;&lt;/b&gt;&lt;br&gt;데
이터 바인딩 초기화를 외부화하려고 WebBindingInitializer 인터페이스의 커스텀 구현체를 제공할 수 있고 
AnnotationMethodHandlerAdapter의 커스텀 빈 설정을 제공해서 활성화하므로 기본 설정을 오버라이딩한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.10 포틀릿 어플리케이션 배포&lt;/font&gt;&lt;/b&gt;&lt;br&gt;스프링 포틀릿 MVC 어플리케이션을 배포하는 과정은 JSR-168 포틀릿 어플리케이션을 배포하는 과정과 다르지 않다. 하지만 이 부분은 좀 헷갈리므로 여기서 간략히 얘기하고자 한다.&lt;br&gt;&lt;br&gt;보
통 포탈(portal)/포틀릿 컨테이너는 서블릿 컨테이터에서 웹앱(webapp)에서 실행되고 포틀릿은 서블릿 컨데이터의 다른 
웹앱에서 실행된다. 포틀릿 컨테이너 웹앱이 포틀릿 웹앱을 호출하게 하려면 portlet.xml 파일에 정의한 포틀릿 서비스에 
접근할 수 있는 잘 알려진 서블릿을 호출하는 교차컨텍스트(cross-context)를 구성해야 한다.&lt;br&gt;&lt;br&gt;JSR-168 
명세가 이 부분이 어떻게 이뤄지는지 정확히 명시하지 않으므로 각 포틀릿 컨테이너는 이 부분에 대한 자신만의 메카니즘을 가진다. 이
 메카니즘은 보통 포틀릿 웹 앱 자체를 변경하는 종류의 &quot;배포 과정&quot;을 포함하고 있고 배포 과정후에 포틀릿 컨테이너 내에 포틀릿을
 등록한다.&lt;br&gt;&lt;br&gt;포틀릿 컨테이너가 호출할 유명한 서블릿을 주입하기 위해 포틀릿 웹앱의 web.xml를 최소한으로 수정한다. 일부의 경우 단일 서블릿이 웹앱의 모든 포틀릿을 서비스하지만 각 포틀릿마다 서블릿 인스턴스가 존재하는 경우도 있다.&lt;br&gt;&lt;br&gt;일부 포틀릿 컨테이너도 웹앱에 라이브러리나 설정 파일을 주입할 것이다. 포틀릿 컨테이너도 웹앱에서 사용할 수 있는 Portlet JSP Tag Library 구현체를 구성해야 한다.&lt;br&gt;&lt;br&gt;배포(deployment)를 이해하는데 중요한 요점은 대상 포탈(porta)이 필요하고 포탈과 만나도록 해야한다는 것이다.(보통은 자동화된 배포과정으로 제공한다.) 이 과정에 대한 포탈(portal)의 문서를 자세히 읽어봐라.&lt;br&gt;&lt;br&gt;포틀릿을 배포하고 나면 최종 web.xml 파일이 정상정인지 살펴봐라. 일부 오래된 포탈은 ViewRendererServlet 정의를 잘못되게 하므로 포틀릿 렌더링을 깨뜨린다.&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">[Spring 레퍼런스] 19장 포틀릿(Portlet) MVC 프레임워크 #1</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/934" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/934" thr:count="0"/>
    <category term="Framework" />
    <category term="JSR-168" />
    <category term="MVC" />
    <category term="portlet" />
    <category term="Reference Guide" />
    <category term="Spring" />
    <category term="Spring 3.1" />
    <category term="Spring Framework" />
    <category term="spring_reference_documentation" />
    <category term="레퍼런스문서" />
    <category term="번역" />
    <category term="스프링" />
    <category term="스프링 프레임워크" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/934</id>
    <updated>2013-05-02T02:10:28+09:00</updated>
    <published>2013-05-02T02:09:33+09:00</published>
    <summary type="html">이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;19. 포틀릿(Portlet) MVC 프레임워크&lt;/font&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.1 소개&lt;/font&gt;&lt;/b&gt;&lt;br&gt;&lt;div style=&quot;padding:10px; background-color:#C9EDFF&quot;&gt;&lt;font color=&quot;#000000&quot;&gt;&lt;b&gt;JSR-168 자바 포틀릿 명세&lt;/b&gt;&lt;br&gt;포틀릿 개발과 관련한 일반적인 정보는 Sun이 작성한 &quot;JSR 168 소개&quot;와 JSR-168 명세를 보기 바란다.&lt;/font&gt;&lt;/div&gt;&lt;br&gt;전통적인 (서블릿 기반의) 웹 개발 지원에 추가적으로 스프링은 JSR-168 포틀릿 개발도 지원한다. 포틀릿 MVC 프레임워크는 웹 MVC 프레임워크의 복사판과도 같고 동일한 뷰 추상화와 통합 기술을 사용한다. 그러므로 이번 장을 읽기 전에 Chapter 16, 웹 MVC 프레임워크장과 Chapter 17, 뷰 기술 장을 읽어봐라.&lt;br&gt;&lt;br&gt;&lt;div style=&quot;padding:10px; background-color:#E4E4E4&quot;&gt;&lt;font color=&quot;#000000&quot;&gt;&lt;b&gt;Note&lt;/b&gt;&lt;br&gt;스프링 MVC의 개념이 스프링 포틀릿 MVC에서도 동일하지만 JSR-168 포틀릿의 독특한 흐름(workflow)으로 생겨난 주요한 차이점이 있다는 점을 명심해라.&lt;/font&gt;&lt;/div&gt;&lt;br&gt;서블릿의 흐름과 다른 포틀릿의 흐름은 포틀릿으로의 요청이 액션(action) 단계와 렌더링(render) 단계의 두 단계로 구분될 수 있다는 것이다. 액션 단계는 데이터베이스의 변경같은 &#039;백엔드&#039;의 변경사항이나 액션이 일어났을 때 딱 한번만 실행된다. 그 다음 렌더링 단계가 화면을 갱신할 때마다 사용자에게 보여줄 내용을 만든다. 여기서 중요한 점은 하나의 전체 요청에서 액션 단계는 딱 한번만 실행되지만 렌더링 단계는 여러번 실행될 수 있다는 것이다. 이는 시스템의 상태를 변경하는 활동과 사용자에게 보여줄 내용을 생성하는 활동을 깔끔하게 구분해준다. (구분해야 한다)&lt;br&gt;&lt;br&gt;&lt;div style=&quot;padding:10px; background-color:#C9EDFF&quot;&gt;&lt;font color=&quot;#000000&quot;&gt;&lt;b&gt;스프링 웹 플로우(Spring Web Flow)&lt;/b&gt;&lt;br&gt;스프링 웹 플로우 (SWF, Spring Web Flow)는 웹 어플리케이션 페이지 흐름을 관리하는 최상의 솔루션이다.&lt;br&gt;&lt;br&gt;SWF는 서블릿 환경과 포틀릿 환경에서 모두에서 스프링 MVC, 스트럿츠, JSF같은 프레임워크와 통합한다. 순사하게 요청 모델과는 반대로 대화식 모델의 이점을 갖는 비즈니스 처리과정을 가진다면 SWF가 해결책이 될 것이다.&lt;br&gt;&lt;br&gt;SWF는 여러 상황에서 재사용할 수 있는 내장된 모쥴처럼 논리적인 페이지 흐름을 갖을 수 있도록 하고 비즈니스 처리과정을 유도하는 제어된 네비게이션드로 사용자를 도와주는 웹 어플리케이션 모듈을 구성하는데 최적이다.&lt;br&gt;&lt;br&gt;SWF에 대한 자세한 내용은 &lt;a href=&quot;http://www.springframework.org/webflow&quot; target=&quot;_blank&quot;&gt;Spring Web Flow 웹사이트&lt;/a&gt; &lt;a href=&#039;http://www.springframework.org/webflow&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;를 참고해라.&lt;/font&gt;&lt;/div&gt;&lt;br&gt;포틀릿 요청의 이중 단계는 JSR-168 명세의 진짜 강점 중 하나이다. 예를 들어 사용자가 명시적으로 검색을 재실행하지 않아도 동적인 검색 결과의 화면을 언제나 갱신할 수 있다. 대부분의 다른 포틀릿 MVC 프레임워크는 개발자한테 이 두 단계를 완전히 감추고 가능한한 일반적인 서블릿 개발처럼 보이도록 한다. 스프링 개발팀은 이 접근이 포틀릿을 사용하는 주요한 이점을 없앤다고 생각한다. 그래서 스프링 포틀릿 MVC 프레임워크에서 두 단계의 구분은 유지된다. 이 접근방법의 주요한 영향으로 MVC 클래스의 서블릿 버전이 요청을 다루는 하나의 메서드를 가질 때 MVC 클래스의 포틋릿 버전은 요청을 다루는 두 메서드를 가질 것이다.(하나는 액션단계용이고 하나는 렌더링단계용이다.) 예를 들어 AbstractController의 서블릿 버전은 handleRequestInternal(..) 메서드를 가지지만 AbstractController의 포틀릿 버전은 handleActionRequestInternal(..)와 handleRenderRequestInternal(..) 메서드를 가진다.&lt;br&gt;&lt;br&gt;포틀릿 프레임워크는 웹 프레임워크에서 DispatcherServlet가 하듯이 요청을 핸들러로 디스패치하는 DispatcherPortlet을 기반으로 설계되었고 설정가능한 핸들러 매핑과 뷰 처리를 제공한다. 파입업로드도 같은 방법으로 지원한다.&lt;br&gt;&lt;br&gt;포틀릿 MVC에서는 로케일 처리와 테마 처리를 지원하지 않는다. 이 영역은 포탈(portal)/포틀릿 컨테이너의 범위이므로 스프링 수준에서 다루기는 적합하지 않다. 하지만 로케일에 기반한 스프링의 모든 메카니즘은(메시지의 국제화 등) DispatcherPortlet가 DispatcherServlet과 같은 방법으로 현재 로케일을 노출하므로 기능이 제대로 동작할 것이다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.1.1 컨트롤러 - MVC에서 C&lt;/font&gt;&lt;/b&gt;&lt;br&gt;기본 핸들러는 여전히 아주 간단한 Controller 인터페이스이고 두 메서드를 제공한다.&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;void handleActionRequest(request,response)&lt;/li&gt;&lt;li&gt;ModelAndView handleRenderRequest(request,response)&lt;/li&gt;&lt;/ul&gt;포틀릿 프레임워크는 AbstractController, SimpleFormController처럼 거의 동일한 컨트롤러 구현체 계층을 가진다. 데이터 바인딩, 커맨드 객체의 사용, 모델 처리, 뷰 처리는 서블릿 프레임워크와 완전히 동일하다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.1.2 뷰 - MVC에서 V&lt;/font&gt;&lt;/b&gt;&lt;br&gt;ViewRendererServlet라는 특수한 브릿지 서블릿으로 서블릿 프레임워크의 모든 뷰 렌더링 기능을 직접 사용한다. 이 서블릿을 사용해서 포틀릿 요청을 서블릿 요청으로 변환하고 일반적인 서블릿 인프라 전체를 사용해서 뷰를 렌더링할 수 있다. 이는 JSP, Velocity 등 기존에 존재하는 렌더러 모두를 포틀릿에서도 계속해서 사용할 수 있음을 의미한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.1.3 웹의 범위를 갖는(Web-scoped) 빈&lt;/font&gt;&lt;/b&gt;&lt;br&gt;스프링 포틀릿 MVC는 생명주기가 현재 HTTP 요청이나 HTTP Session(일반적인 세션이나 전역 세션 모두)의 범위를 갖는 빈을 지원한다. 이는 스프링 포틀릿 MVC 자체의 특정 기능이라기 보다는 스프링 포틀릿 MVC가 사용하는 WebApplicationContext 컨테이너의 기능이다. 이러한 빈의 범위는 Section 4.5.4, “리퀘스트, 세션, 글로벌 세션 범위”에서 설명한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.2 DispatcherPortlet&lt;/font&gt;&lt;/b&gt;&lt;br&gt;포틀릿 MVC는 요청을 컨트롤러로 디스패치하고 포틀릿 어플리케이션을 개발하는 기반 기능을 제공하는 포틀릿에 기반해서 설계된 요청주도 웹 MVC 프레임워크이다. 하지만 스프링의 DispatcherPortlet는 이 이상을 수행한다. DispatcherPortlet은 스프링 ApplicationContext와 완전히 통합되었고 스프링이 가진 모든 기능을 사용할 수 있게 한다.&lt;br&gt;&lt;br&gt;일반적인 포틀릿처럼 DispatcherPortlet는 웹 어플리케이션의 portlet.xml 파일에 선언한다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;portlet&amp;gt;
&amp;nbsp; &amp;lt;portlet-name&amp;gt;sample&amp;lt;/portlet-name&amp;gt;
&amp;nbsp; &amp;lt;portlet-class&amp;gt;org.springframework.web.portlet.DispatcherPortlet&amp;lt;/portlet-class&amp;gt;
&amp;nbsp; &amp;lt;supports&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;mime-type&amp;gt;text/html&amp;lt;/mime-type&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;portlet-mode&amp;gt;view&amp;lt;/portlet-mode&amp;gt;
&amp;nbsp; &amp;lt;/supports&amp;gt;
&amp;nbsp; &amp;lt;portlet-info&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;title&amp;gt;Sample Portlet&amp;lt;/title&amp;gt;
&amp;nbsp; &amp;lt;/portlet-info&amp;gt;
&amp;lt;/portlet&amp;gt;
&lt;/pre&gt;&lt;br&gt;이제 DispatcherPortlet를 설정해야 한다.&lt;br&gt;&lt;br&gt;포틀릿 MVC 프레임워크에서 각 DispatcherPortlet는 루트 WebApplicationContext에 이미 정의된 모든 빈을 상속받은 자신만의 WebApplicationContext를 가진다. 이 상속된 빈은 포틀릿에 한정된 범위에서 오버라이드할 수 있고 새로운 범위로 한정된 빈은 해당 포틀릿 인스턴스의 로컬로 정의할 수 있다.&lt;br&gt;&lt;br&gt;DispatcherPortlet 초기화시 포틀릿 MVC 프레임워크는 웹 어플리케이션의 WEB-INF 디렉토리에서 [portlet-name]-portlet.xml라는 파일을 검색해서 정의된 빈을 생성한다.(전역 범위에 같은 이름으로 정의된 모든 빈 정의는 오버라이드한다.)&lt;br&gt;&lt;br&gt;DispatcherPortlet가 사용하는 설정 위치는 포틀릿 초기화 파라미터로 수정할 수 있다.(자세한 내용은 뒤에 나온다.)&lt;br&gt;&lt;br&gt;스프링 DispatcherPortlet는 요청을 처리하고 적절한 뷰를 렌더링하는 데 사용하는 몇몇 특수한 빈을 가진다. 이러한 빈은 스프링 프레임워크에 포함되어 있고 설정할 다른 빈과 마찬가지로 WebApplicationContext에서 설정할 수 있다. 이 빈들은 뒤에서 각각 자세히 설명한다. 지금은 DispatcherPortlet을 계속 설명하기 위해서 이러한 빈이 존재한다는 것만 알아두면 된다. 대부분의 빈은 설정을 신경쓰지 않아도 되도록 기본값을 제공한다.&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Table 19.1. WebApplicationContext의 특수한 빈&lt;br&gt;&lt;/span&gt;
&lt;table style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;표현식&lt;/th&gt;
&lt;th style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;handler mapping(s)&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;(Section 19.5, “핸들러 매핑”) 전처리자, 후처리자, 컨트롤러의 목록으로 이들이 특정 크리테리아와 일치한다면 실행될 것이다.(예를 들어 찾아낸 포틀릿은 모드는 컨트롤러로 지정된다.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;controller(s)&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;(Section 19.4, “컨트롤러”) MVC의 일부로 실제 기능을 제공하는 빈(또는 최소한 기능에 접근하는)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;view resolver&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;(Section 19.6, “뷰와 뷰 처리”) 뷰 이름을 뷰 정의로 처리할 수 있다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;multipart resolver&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;(Section 19.7, “멀티파트(파일 업로드) 지원”) HTML 폼의 파일 업로드를 처리하는 기능을 제공한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;handler exception resolver&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;(Section 19.8, “예외 처리”) 예외를 뷰에 매핑하거나 복잡한 예외 처리코드를 구현하는 기능을 제공한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;DispatcherPortlet이 해당 DispatcherPortlet 들어오는 요청을 받고 사용할 설정이 되면 요청을 처리하기 시작한다. 아래 목록은 DispatcherPortlet의 요청을 처리하는 전체 과정을 설명한다.&lt;br&gt;&lt;br&gt;&lt;ol&gt;&lt;li&gt;요청을 처리할 때(뷰 렌더링, 데이터 준비 등) 처리중인 요청이 사용할 로케일을 처리하도록 PortletRequest.getLocale()가 반환하는 로케일을 요청에 바인딩한다.&lt;/li&gt;&lt;li&gt;멀티파트 리졸버를 지정했고 요청이 ActionRequest라면 해당 요청은 멀티파트인지 검사하고 멀티파트라면 다른 요소가 추가적인 처리를 하도록 MultipartActionRequest로 감싼다. (멀티파트 처리에 대한 자세한 내용은 Section 19.7, “멀티파트(파일 업로드) 지원”를 봐라.)&lt;/li&gt;&lt;li&gt;적합한 핸들러를 검색한다. 핸들러를 발견하면 모델을 준비하기 위해 핸들러(전처리자, 후처리자, 컨트롤러)에 연결된 실행 체인을 실행할 것이다.&lt;/li&gt;&lt;li&gt;모델이 반환되면 WebApplicationContext로 설정된 뷰 리졸버를 사용해서 뷰를 렌더링한다. 모델이 반환되지 않는다면(예를 들어 전처리자나 후처리자가 보안때문에 요청을 가로채는 등의 이유로) 요청이 이미 완료되었으므로 뷰를 렌더링하지 않는다.&lt;/li&gt;&lt;/ol&gt;&lt;br&gt;요청을 처리하는 중 던저진 예외는 WebApplicationContext에 선언된 핸들러 예외 리졸버(handler exception resolver)가 처리한다. 이러한 예외 리졸버를 사용해서 예외가 던저진 경우에 대한 커스텀 동작을 정의할 수 있다.&lt;br&gt;&lt;br&gt;portlet.xml 파일이나 포틀릿 init 파라미터에 컨텍스트 파라미터를 추가해서 스프링의 DispatcherPortlet를 커스터마이징할 수 있다. 할 수 있는 일은 다음 목록에 나와있다.&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Table 19.2. DispatcherPortlet 초기화 파라미터&lt;br&gt;&lt;/span&gt;
&lt;table style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;파라미터&lt;/th&gt;
&lt;th style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;contextClass&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;WebApplicationContext를 구현한 클래스로 해당 포틀릿이 사용하는 컨텍스트를 인스턴스화하는데 사용할 것이다. 이 파라미터를 지정하지 않으면 XmlPortletApplicationContext를 사용할 것이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;contextConfigLocation&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;컨텍스트를 찾을 수 있는 위치를 나타내려고 컨텍스트 인스턴스 (contextClass가 지정한)에 전달하는 문자열이다. 이 문자열은 여러 컨텍스트(여러 컨텍스트 위치가 있는 경우 두번 정의된 빈은 마지막에 정의된 빈을 우선시한다)를 지원하기 위해 여러개의 문자열로 나눌 수 있다.(구분자로 콤마를 사용한다)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;namespace&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;WebApplicationContext의 네임스페이스다. 기본값은 [portlet-name]-portlet이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;viewRendererUrl&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;DispatcherPortlet가 ViewRendererServlet에 접근할 수 있는 URL이다.(Section 19.3, “ViewRendererServlet” 참고)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.3 ViewRendererServlet&lt;/font&gt;&lt;/b&gt;&lt;br&gt;포틀릿 MVC에서 렌더링 과정은 웹 MVC보다 약간 더 복잡하다. 스프링 웹 MVC의 모든 뷰 기술을 재사용하려면 PortletRequest / PortletResponse를 HttpServletRequest / HttpServletResponse로 변환하고 View의 render 메서드를 호출해야 한다. 이렇게 하기 위해서 DispatcherPortlet이 이 목적때문에 존재하는 전용 서블릿 ViewRendererServlet을 사용한다.&lt;br&gt;&lt;br&gt;DispatcherPortlet 렌더링이 동작하려면 웹 어플리케이션의 web.xml 파일에 다음과 같이 ViewRendererServlet 인스턴스를 선언해야 한다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;servlet&amp;gt;
&amp;nbsp; &amp;lt;servlet-name&amp;gt;ViewRendererServlet&amp;lt;/servlet-name&amp;gt;
&amp;nbsp; &amp;lt;servlet-class&amp;gt;org.springframework.web.servlet.ViewRendererServlet&amp;lt;/servlet-class&amp;gt;
&amp;lt;/servlet&amp;gt;

&amp;lt;servlet-mapping&amp;gt;
&amp;nbsp; &amp;lt;servlet-name&amp;gt;ViewRendererServlet&amp;lt;/servlet-name&amp;gt;
&amp;nbsp; &amp;lt;url-pattern&amp;gt;/WEB-INF/servlet/view&amp;lt;/url-pattern&amp;gt;
&amp;lt;/servlet-mapping&amp;gt;
&lt;/pre&gt;&lt;br&gt;실제 렌더링을 수행하기 위해 DispatcherPortlet는 다음의 과정을 진행한다.&lt;br&gt;&lt;br&gt;&lt;ol&gt;&lt;li&gt;DispatcherServlet이 사용하는 같은 WEB_APPLICATION_CONTEXT_ATTRIBUTE 키에 속성으로 WebApplicationContext를 요청에 바인딩한다.&lt;/li&gt;&lt;li&gt;ViewRendererServlet에서 사용할 수 있도록 Model과 View 객체를 요청에 바인딩한다.&lt;/li&gt;&lt;li&gt;PortletRequestDispatcher를 생성하고 ViewRendererServlet에 매핑된 /WEB- INF/servlet/view URL을 사용해서 include를 수행한다.&lt;/li&gt;&lt;/ol&gt;그 다음 ViewRendererServlet이 View의 render 메서드를 적절한 인자로 호출할 수 있다.&lt;br&gt;&lt;br&gt;ViewRendererServlet의 실제 URL은 DispatcherPortlet의 viewRendererUrl 설정 파라미터를 사용해서 변경할 수 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.4 컨트롤러&lt;/font&gt;&lt;/b&gt;&lt;br&gt;포틀릿 MVC의 컨트롤러는 웹 MVC 컨트롤러와 아주 유사하고 서로간에 코드를 포딩하는 것은 아주 간단하다.&lt;br&gt;&lt;br&gt;포틀릿 MVC 컨트롤러 아키텍처의 기반은 다음 목록에 나온 org.springframework.web.portlet.mvc.Controller 인터페이스이다.&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
public interface Controller {

&amp;nbsp; /**
&amp;nbsp;&amp;nbsp; * 요청의 렌더링을 처리하고 DispatcherPortlet이 렌더링할
&amp;nbsp;&amp;nbsp; * ModelAndView 객체를 반환한다.
&amp;nbsp;&amp;nbsp; */
&amp;nbsp; ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response)
&amp;nbsp; &amp;nbsp; throws Exception;

&amp;nbsp; /**
&amp;nbsp;&amp;nbsp; * 액션 요청을 처리한다. 아무것도 반환하지 않는다.
&amp;nbsp;&amp;nbsp; */
&amp;nbsp; void handleActionRequest(ActionRequest request, ActionResponse response)
&amp;nbsp; &amp;nbsp; throws Exception;
}
&lt;/pre&gt;&lt;br&gt;여기서 보듯이 포틀릿 Controller 인터페이스는 포틀릿 요청의 두 단계(액션 요청과 렌더링 요청)을 다루는 두 개의 메서드를 필요로 한다. 액션 단계는 액션 요청을 처리할 수 있어야 하고 렌더링 단계는 렌더링 요청을 처리하고 적절한 모델과 뷰를 반환할 수 있어야 한다. Controller 인터페이스가 상당히 추상화되어 있으므로 스프링 포틀릿 MVC는 필요로 할 많은 기능을 담고 있는 다수의 컨트롤러를 제공한다. 이러한 대부분의 컨트롤러는 스프링 웹 MVC의 컨트롤러와 아주 유사하다. Controller 인터페이스는 모든 컨트롤러가 필요로하는 많은 공통 기능(액션 요청, 렌더링 요청 처리, 모델과 뷰 반환)을 정의하고 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.4.1 AbstractController와 PortletContentGenerator&lt;/font&gt;&lt;/b&gt;&lt;br&gt;물론 Controller 인터페이스만으로는 충분치 않다. 기본 인프라스트럭처인 AbstractController를 상속받은 스프링 포틀릿 MVC의 모든 Controller를 제공하려면 클래스가 스프링의 ApplicationContext에 접근할 수 있고 캐싱을 제어할 수 있어야 한다.&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Table 19.3. AbstractController가 제공하는 기능&lt;br&gt;&lt;/span&gt;
&lt;table style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;파라미터&lt;/th&gt;
&lt;th style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;requireSession&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;해당 Controller가 동작하는 세션을 필요로 하는지를 나타낸다. 이러한 컨트롤러가 요청을 받았을 때 세션이 제공되지 않으면 SessionRequiredException를 사용해서 사용자에게 알려준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;synchronizeSession&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;사용자 세션 동기화를 해당 컨트롤러가 제어하기를 원한다면 이 파라미터를 사용해라. 더 자세히 보자면 컨트롤러를 확장하는 것은 변수를 지정한 경우 사용자 세션의 동기화를 할 handleRenderRequestInternal(..)와 handleActionRequestInternal(..) 메서드를 오버라이드 할 것이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;renderWhenMinimized&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;포틀릿이 최소화된 상태(minimized state)일 때 컨트롤러가 뷰를 실제로 렌더링하게 하려면 이 파라미터를 true로 설정해라. 기본적으로 이는 false이므로 최소화된 상태의 포틀릿은 아무 내용도 보여주지 않을 것이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;cacheSeconds&lt;/td&gt;
&lt;td style=&quot;border-collapse: collapse;border: 1.0pt solid;&quot; align=&quot;left&quot;&gt;포틀릿에 정의된 캐시 기본 만료(expiration)를 컨트롤러가 오버라이드하려면 이 파라미터를 양수로 지정해라. 기본적으로 이 파라미터는 기본 캐싱을 바꾸지 않는 의미로 -1로 설정되어 있다. 이 파라미터를 0으로 설정하면 결과를 절대 캐싱하지 않는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;requireSession와 cacheSeconds 프로퍼티는 PortletContentGenerator (AbstractController의 수퍼클래스)에 선언하지만 자세함을 위해서 여기에 포함시켰다.&lt;br&gt;&lt;br&gt;AbstractController를 컨트롤러의 기반클래스로 사용하는 경우(이미 해당 작업을 하는 다른 클래스가 많이 있으므로 추천하는 방법은 아니다.) handleActionRequestInternal(ActionRequest, ActionResponse) 메서드나 handleRenderRequestInternal(RenderRequest, RenderResponse) 메서드(혹은 둘다)를 오버라이드하고 로직을 구현하고 ModelAndView 객체를 반환 (handleRenderRequestInternal인 경우에)해야 한다.&lt;br&gt;&lt;br&gt;handleActionRequestInternal(..)와 handleRenderRequestInternal(..)의 기본 구현체는 PortletException를 던진다. 이는 JSR-168 명세서 API의 GenericPortlet 동작과 일관성이 있다. 그러므로 컨트롤러가 다루기를 원하는 메서드만 오버라이드 해야한다.&lt;br&gt;&lt;br&gt;클래스와 웹 어플리케이션 컨텍스트의 선언으로 구성된 간단한 예제가 다음에 나와 있다.&lt;br&gt;&lt;br&gt;&lt;pre class=&quot;brush: java&quot;&gt;
package samples;

import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.springframework.web.portlet.mvc.AbstractController;
import org.springframework.web.portlet.ModelAndView;

public class SampleController extends AbstractController {

&amp;nbsp; &amp;nbsp; public ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ModelAndView mav = new ModelAndView(&quot;foo&quot;);
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; mav.addObject(&quot;message&quot;, &quot;Hello World!&quot;);
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return mav;
&amp;nbsp; &amp;nbsp; }
}
&lt;/pre&gt;&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;bean id=&quot;sampleController&quot; class=&quot;samples.SampleController&quot;&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;cacheSeconds&quot; value=&quot;120&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;&lt;br&gt;이 아주 간단한 컨트롤러가 동작하는데 위의 클래스와 웹 어플리케이션의 선언이 핸들러 매핑 구성(Section 19.5, “핸들러 매핑” 참고)외에 해야하는 전부이다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.4.2 그 외 간단한 컨트롤러&lt;/font&gt;&lt;/b&gt;&lt;br&gt;AbstractController를 확장할 수 있더라도 스프링 포틀릿 MVC는 간단한 MVC 어플리케이션에서 공통적으로 사용하는 기능을 제공하는 다수의 구현체를 제공하고 있다.&lt;br&gt;&lt;br&gt;ParameterizableViewController는 웹 어플리케이션 컨텍스트에서 반환할 뷰 이름을 지정할 수 있다는 점(뷰 이름을 하드코딩할 필요가 없다)을 제외하면 기본적으로 위의 예제와 같다.&lt;br&gt;&lt;br&gt;PortletModeNameViewController는 뷰 이름으로 포틀릿의 현재 모드를 사용한다. 그래서 포틀릿이 View 모드라면(예시: PortletMode.VIEW) 뷰 이름으로 &quot;view&quot;를 사용한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.4.3 커맨드 컨트롤러&lt;/font&gt;&lt;/b&gt;&lt;br&gt;스프링 포틀릿 MVC는 스프링 웹 MVC와 정확히 같은 계층의 커맨트 컨트롤러를 가진다. 커맨드 컨트롤러는 데이터 객체와 상호작용하고 PortletRequest의 파라미터를 지정한 데이터 객체로 동적으로 바인딩하는 방법을 제공한다. 데이터 객체는 프레임워크에 특화된 인터페이스를 구현하지 않아야 하므로 원한다면 퍼시스턴트 객체를 직접 조작할 수 있다. 커맨드 컨트롤러로 무엇을 할 수 있는지 살펴보기 위해서 어떤 커맨드 컨트롤러를 사용할 수 있는지 보자.&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;AbstractCommandController - 자신만의 커맨트 컨트롤러를 생성하는데 사용할 수 있는 커맨드 컨트롤러로 요청 파라미터를 지정한 데이터 객체에 바인딩할 수 있다. 이 클래스는 폼(form) 기능을 제공하지 않지만 유효성검사 기능은 제공하고 컨트롤러에서 요청의 파라미터로 채워진 커맨드 객체로 무엇을 할지 지정하게 한다.&lt;/li&gt;&lt;li&gt;AbstractFormController - 폼 제출을 지원하는 추상 컨트롤러다. 이 컨트롤러를 사용해서 폼을 만들 수 있고 컨트롤러에서 획득한 커맨드 객체를 사용해서 폼을 유지할 수 있다. 사용자가 폼을 채운 후에 AbstractFormController가 필드를 바인딩하고 유효성을 검사하고 객체를 적절한 액션을 취하는 컨트롤러에 다시 전달한다. 유효하지 않은 폼 제출 (중복제출), 유효성 검사, 일반적인 폼 워크플로우의 기능을 지원한다. 폼 표현과 성공에 어떤 뷰를 사용할지 결정하는 메서드를 구현한다. 폼은 필요하지만 어플리케이션 컨텍스트에서 사용자를 보여줄 뷰를 지정하기 원치 않는다면 이 컨트롤러를 사용해라.&lt;/li&gt;&lt;li&gt;SimpleFormController - AbstractFormController의 구현체로 대응되는 커맨드 객체로 폼을 생성할 때 추가적인 지원을 제공한다. SimpleFormController는 커맨드 객체, 폼에 대한 뷰이름, 폼 제출이 성공적으로 이뤄졌을 때 사용자를 보여주는 페이지의 뷰이름을 지정하게 한다.&lt;/li&gt;&lt;li&gt;AbstractWizardFormController ? 여러 화면에 걸쳐서 커맨드 객체의 내용을 수정하는 마법사방식의 인터페이스를 제공하는 AbstractFormController의 구현체이다. 종료, 취소, 페이지 변경등 여러 사용자 동작을 지원하고 이 모든 기능을 화면의 요청 파라미터로 쉽게 지정한다.&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;이러한 커맨드 컨트롤러는 아주 강력하지만 효율적으로 사용하려면 어떻게 동작하는지 자세히 이해해야 한다. 이 컨트롤러의 전체 계층에 대해서는 Javadoc을 자세히 보고 사용하기 전에 예제 구현체를 살펴봐라.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.4.4 PortletWrappingController&lt;/font&gt;&lt;/b&gt;&lt;br&gt;새로운 컨트롤러를 개발하는 대신 기존에 존재하는 포틀릿을 사용해서 DispatcherPortlet에서 요청을 컨트롤러로 매핑할 수 있다. PortletWrappingController를 사용해서 Controller로 이미 존재하는 Portlet을 다음과 같이 인스턴스화 할 수 있다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;bean id=&quot;myPortlet&quot; class=&quot;org.springframework.web.portlet.mvc.PortletWrappingController&quot;&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;portletClass&quot; value=&quot;sample.MyPortlet&quot;/&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;portletName&quot; value=&quot;my-portlet&quot;/&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;initParameters&quot;&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;value&amp;gt;config=/WEB-INF/my-portlet-config.xml&amp;lt;/value&amp;gt;
&amp;nbsp; &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;&lt;br&gt;이러한 포틀릿으로 들어오는 요청 이전과 이후의 처리를 위해 인터셉터를 사용할 수 있으므로 이는 아주 가치가 있다. JSR-168가 필터 메카니즘을 전혀 지원하지 않으므로 이 방법이 아주 편리하다. 예를 들어 이를 MyFaces JSF 포틀릿을 하이버네이트 OpenSessionInViewInterceptor로 감싸는데 사용할 수 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;4&quot;&gt;19.5 핸들러 매핑&lt;/font&gt;&lt;/b&gt;&lt;br&gt;핸들러 매핑을 사용해서 들어오는 포틀릿 요청을 적당한 핸들러로 매핑할 수 있다. 사용할 수 있는 몇몇 핸들러 매핑이 존재하지만(예를 들면 PortletModeHandlerMapping) 먼저 HandlerMapping의 일반적인 개념을 살펴보자.&lt;br&gt;&lt;br&gt;Note: 의도적으로 “Controller” 대신에 “Handler”라는 용어를 여기서 사용하고 있다. DispatcherPortlet은 스프링 포틀릿 MVC 자체의 컨트롤러와는 다른 방법으로 요청을 처리하는데 사용하도록 설계되었다. 핸들러는 포틀릿 요청을 다룰 수 있는 객체이다. 컨트롤러는 핸들러의 하나의 예시이면서 기본값이다. 다른 프레임워크와 함께 DispatcherPortlet를 사용하려면 HandlerAdapter의 대응되는 구현체가 필요한 전부이다.&lt;br&gt;&lt;br&gt;기본 HandlerMapping이 제공하는 기능은 들어오는 요청과 일치하는 핸들러를 반드시 가지고 요청에 적용되는 핸들러 인터셉터의 목록을 가질 수도 있는 HandlerExecutionChain의 전달이다. 요청이 들어올 때 DispatcherPortlet는 요청을 검사하고 적절한 HandlerExecutionChain에 접근하도록 핸들러 매핑에 전달한다. 그 다음 DispatcherPortlet은 체인에 있는(존재한다면) 핸들러와 인터셉터를 실행할 것이다. 이러한 개념은 스프링 웹 MVC와 완전히 같다.&lt;br&gt;&lt;br&gt;선택적으로 인터셉터(실제 실행되는 핸들러 이전과 이후에 실행된다)를 가질 수 있고 설정가능한 핸들러 매핑의 개념은 굉장히 강력하다. 지원하는 다수의 기능은 커스텀 HandlerMapping의 일부가 될 수 있다. 들어오는 요청의 포틀릿 모드에 기반하지 않고 요청과 연관된 세션의 특정 상태에 기반해서 핸들러를 선택하는 커스텀 핸들러 매핑을 생각해 보자.&lt;br&gt;&lt;br&gt;스프링 웹 MVC에서 핸들러 매핑은 일반적으로 URL에 기반한다. 포틀릿에서는 URL같은 것이 없기 때문에 매핑을 제어하는 다른 메카니즘을 사용해야 한다. 가장 일반적인 두가지는 포틀릿 모드와 요청 파라미터이지만 포틀릿 요청에서 사용할 수 있는 것들은 커스텀 핸들러 매핑에서도 사용할 수 있다.&lt;br&gt;&lt;br&gt;이번 섹션의 남은 부분은 스프링 포틀릿 MVC에서 가장 일반적으로 사용하는 핸들러 매핑을 설명한다. 이 핸들러 매핑은 모두 AbstractHandlerMapping을 확장하고 다음 프로퍼티를 공유한다.&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;interceptors: 사용할 인터셉터 목록. HandlerInterceptor는 Section 19.5.4, “HandlerInterceptor 추가”에서 설명했다.&lt;/li&gt;&lt;li&gt;defaultHandler: 해당 핸들러 매핑에서 일치하는 핸들러가 없는 경우 사용할 기본 핸들러.&lt;/li&gt;&lt;li&gt;order: order 프로퍼티 값에 기반해서 (org.springframework.core.Ordered 인터페이스 참고) 스프링은 컨텍스트에서 사용할 수 있는 모든 핸들러 매핑을 정렬하고 처음 일치하는 핸들러를 적용할 것이다.&lt;/li&gt;&lt;li&gt;lazyInitHandlers: 싱글톤 핸들러의 지연 초기화를 허용한다.(프로토타입 핸들러는 항상 지연 초기화를 한다.) 기본값은 false다. 이 프로퍼티는 세가지 구현체에서 직접 구현되었다.&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.5.1 PortletModeHandlerMapping&lt;/font&gt;&lt;/b&gt;&lt;br&gt;들어오는 요청을 포틀릿의 현재 모드에 기반해서(예시. ‘view’, ‘edit’, ‘help’) 매핑하는 간단한 핸들러 매핑이다. 예를 들면 다음과 같다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;bean class=&quot;org.springframework.web.portlet.handler.PortletModeHandlerMapping&quot;&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;portletModeMap&quot;&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;view&quot; value-ref=&quot;viewHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;edit&quot; value-ref=&quot;editHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;help&quot; value-ref=&quot;helpHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/map&amp;gt;
&amp;nbsp; &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.5.2 ParameterHandlerMapping&lt;/font&gt;&lt;/b&gt;&lt;br&gt;포틀릿 모드를 변경하지 않고 여러 컨트롤러를 탐색해야 한다면 매핑을 제어하는 키로 요청 파라미터를 사용하는 것이 가장 간단한 방법이다.&lt;br&gt;&lt;br&gt;ParameterHandlerMapping은 특정 요청 파라미터의 값을 매핑을 제어하는데 사용한다. 파라미터의 기본 이름은 &#039;action&#039;이지만 &#039;parameterName&#039; 프로퍼티를 사용해서 변경할 수 있다.&lt;br&gt;&lt;br&gt;이 매핑에 대한 빈(bean) 설정은 다음과 같을 것이다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;bean class=&quot;org.springframework.web.portlet.handler.ParameterHandlerMapping”&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;parameterMap&quot;&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;add&quot; value-ref=&quot;addItemHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;edit&quot; value-ref=&quot;editItemHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;delete&quot; value-ref=&quot;deleteItemHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/map&amp;gt;
&amp;nbsp; &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.5.3 PortletModeParameterHandlerMapping&lt;/font&gt;&lt;/b&gt;&lt;br&gt;가장 강력한 내장 핸들러 매핑인 PortletModeParameterHandlerMapping는 각 포틀릿 모드내에서 다른 탐색을 허용하는 앞의 두 핸들러 매핑의 기능을 합쳐놓았다.&lt;br&gt;&lt;br&gt;마찬가지로 기본 파라미터 이름은 &quot;action&quot;이지만 parameterName 프로퍼티를 사용해서 변경할 수 있다.&lt;br&gt;&lt;br&gt;기본적으로 두가지 다른 포틀릿 모드에서 같은 파라미터값을 사용하지 않는다. 이는 포탈(portal)이 직접 포틀릿 모드를 변경하면 요청이 더이상 매핑에서 유효하지 않도록 하기 위함이다. 이 동작은 allowDupParameters 프로퍼티를 true로 설정해서 변경할 수 있다. 하지만 변경하는 것을 권장하지 않는다.&lt;br&gt;&lt;br&gt;이 매핑에 대한 빈(bean) 설정은 다음과 같을 것이다.&lt;br&gt;&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;bean class=&quot;org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping&quot;&amp;gt;
&amp;nbsp; &amp;lt;property name=&quot;portletModeParameterMap&quot;&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;view&quot;&amp;gt; &amp;lt;!-- &#039;view&#039; 포틀릿 모드 --&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;add&quot; value-ref=&quot;addItemHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;edit&quot; value-ref=&quot;editItemHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;delete&quot; value-ref=&quot;deleteItemHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/entry&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;edit&quot;&amp;gt; &amp;lt;!-- &#039;edit&#039; 포틀릿 모드 --&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;prefs&quot; value-ref=&quot;prefsHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;entry key=&quot;resetPrefs&quot; value-ref=&quot;resetPrefsHandler&quot;/&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/map&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/entry&amp;gt;
&amp;nbsp; &amp;nbsp; &amp;lt;/map&amp;gt;
&amp;nbsp; &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;&lt;br&gt;이 매핑은 PortletModeHandlerMapping보다 먼저 체이닝될 수 있어서 각 모드나 전체적인 기본값을 제공할 수 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.5.4 HandlerInterceptor 추가&lt;/font&gt;&lt;/b&gt;&lt;br&gt;스프링의 핸들러 매핑 메카니즘은 특정 요청에 어떤 기능(예를 들어 중요한 검사 등)을 적용하기를 원할 때 아주 유용할 수 있는 핸들러 인터셉터의 개념을 가진다. 스프링 포틀릿 MVC는 웹 MVC와 같은 방법으로 이러한 개념을 구현한다.&lt;br&gt;&lt;br&gt;핸들러 매핑에 있는 인터셉터는 org.springframework.web.portlet 패키지의 HandlerInterceptor를 구현해야 한다. 서블릿 버전처럼 이 인터셉터는 세가지 메서드를 정의한다. 한 메서드는 실제 핸들러가 실행되기 전에 호출될 것이고(preHandle) 한 메서드는 핸들러가 실행된 후에 호출할 것이고(postHandle) 한 메서드는 요청이 완료된 후에 호출할 것이다. (afterCompletion) 이 세가지 메서드는 모든 종류의 전처리와 후처리를 하는 유연성을 충분히 제공해야 한다.&lt;br&gt;&lt;br&gt;preHandle 메서드는 불리언 값을 반환한다. 실행체인의 처리를 멈추거나 계속 진행하는데 이 메서드를 사용할 수 있다. 이 메서드가 true를 반환하면 핸들러 실행 체인을 계속 진행될 것이고 false를 반환하면 DispatcherPortlet는 인터셉터가 직접 요청을 처리한다고(예를 들어 적절한 뷰를 렌더링하는 등) 가정하고 실행 체인의 다른 인터셉터와 실제 핸들러를 계속해서 실행하지 않는다.&lt;br&gt;&lt;br&gt;RenderRequest에서만 postHandle메서드를 호출한다. preHandle와 afterCompletion 메서드는 ActionRequest와 RenderRequest에서 모두 실행된다. 딱 한가지 종류의 요청에서 이러한 메서드의 로직을 실행해야 한다면 처리하기 전에 요청의 종류를 확인해봐야 한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.5.5 HandlerInterceptorAdapter&lt;/font&gt;&lt;/b&gt;&lt;br&gt;서블릿 패키지처럼 포틀릿 패키지는 HandlerInterceptor의 구현체인 HandlerInterceptorAdapter를 가진다. 이 클래스를 상속받아서 필요한 하나나 두개의 메서드를 구현할 수 있도록 이 클래스는 구현부는 없는(empty)는 모든 메서드를 가진다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;3&quot;&gt;19.5.6 ParameterMappingInterceptor&lt;/font&gt;&lt;/b&gt;&lt;br&gt;포틀릿 패키지도 ParameterMappingInterceptor라는 인터셉터를 가지므로 직접 ParameterHandlerMapping과 PortletModeParameterHandlerMapping을 함께 사용할 수 있다. 이 인터셉터는 파라미터를 ActionRequest에서 이어진 RenderRequest으로 진행하는 매핑을 제어하는데 사용할 수 있게 한다. 이는 ActionRequest처럼 RenderRequest가 같은 핸들러로 매핑된도록 보장하는 것을 도와줄 것이다. 이는 인터셉터의 preHandle 메서드에서 이뤄지므로 RenderRequest가 매핑될 위치를 변경하기 위해 핸들러에서 파라미터 값을 수정할 수 있다.&lt;br&gt;&lt;br&gt;이 인터셉터가 ActionResponse에서 setRenderParameter를 호출하고 있음을 유의해라. 즉, 이 인터셉터를 사용할 때는 핸들러에서 sendRedirect를 호출할 수 없다. 외부로 리다이렉트해야 한다면 수동으로 파라미터를 매핑하거나 이를 처리하는 다른 인터셉터를 작성해야 할 것이다.&lt;br&gt;</summary>
  </entry>
  <entry>
    <title type="html">bower : 웹 프론트앤드 패키지 관리자</title>
    <link rel="alternate" type="text/html" href="http://blog.outsider.ne.kr/933" />
    <link rel="replies" type="application/atom+xml" href="http://blog.outsider.ne.kr/atom/response/933" thr:count="0"/>
    <category term="Javascript" />
    <category term="bower" />
    <category term="component" />
    <category term="Front-end" />
    <category term="npm" />
    <category term="package manager" />
    <category term="Twitter" />
    <author>
      <name>(Outsider)</name>
    </author>
    <id>http://blog.outsider.ne.kr/933</id>
    <updated>2013-04-30T00:03:04+09:00</updated>
    <published>2013-04-29T23:57:57+09:00</published>
    <summary type="html">&lt;a href=&quot;http://bower.io/&quot; target=&quot;_blank&quot;&gt;bower&lt;/a&gt; &lt;a href=&#039;http://bower.io/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;를 알게 된 것은 &lt;a href=&quot;http://blog.geekple.com/2012/09/10/bower-open-sourced-by-twitter/&quot; target=&quot;_blank&quot;&gt;Dani님의 포스팅&lt;/a&gt; &lt;a href=&#039;http://blog.geekple.com/2012/09/10/bower-open-sourced-by-twitter/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;을 보고 나서였지만 본격적으로 쓰기 시작한 것은 최근의 일이다. bower는 트위터에서 만든 프론트앤드용 패키지 매니저이다. node.js를 사용한다면 npm을 생각하면 이해하기 가장 쉬울것이고(실제로도 비슷하다.) 보통 언어별로 있는 의존성 라이브러리 관리자(메이븐, 루비젬 등등)이지만 bower는 프론트엔드 전용이라는 점이 다르다. &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;bower&lt;/font&gt;&lt;/b&gt;&lt;br&gt;&lt;a href=&quot;http://bower.io/&quot; target=&quot;_blank&quot;&gt;bower&lt;/a&gt; &lt;a href=&#039;http://bower.io/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;를 처음 봤을 때의 시기나 지금이나 웹 프로젝트를 할 때마다 반복적으로 해야하는 jQuery를 다운받고 Bootstrap을 다운받는 등의 일을 어떻게 줄일까 고민하고 있고 일부는 &lt;a href=&quot;https://github.com/gruntjs/grunt-init&quot; target=&quot;_blank&quot;&gt;grunt-init&lt;/a&gt;을
 사용해서 해결하고 있다. bower를 처음 봤을 때 &#039;오오~ 이거야~&#039;라는 생각을 했음에도 불구하고 사용하지 않았던 것은 실제 
필요에 비해서 좀 거추장스럽게 느껴진다는 점과(지금도 좀 그렇게 느낀다.) 그 이전에는 사용해 보지 않았던 components라는
 어색한 이름때문이었다. 이런 부분이 크게 달라지진 않았지만 프론트앤드쪽도 이젠 의존성 관리가 거추장스러워져서 쓰기 시작했다.&lt;br&gt;&lt;br&gt;&lt;div class=&quot;imageblock center&quot; style=&quot;text-align: center; clear: both;&quot;&gt;&lt;img src=&quot;http://blog.outsider.ne.kr/attach/1/1110550393.gif&quot; alt=&quot;Bower 홈페이지&quot; height=&quot;225&quot; width=&quot;550&quot; /&gt;&lt;/div&gt;&lt;br&gt;&lt;a href=&quot;http://bower.io/&quot; target=&quot;_blank&quot;&gt;bower&lt;/a&gt; &lt;a href=&#039;http://bower.io/&#039; target=&#039;_blank&#039; title=&#039;이 링크를 새창으로 엽니다.&#039; style=&#039;text-decoration: none&#039;&gt;&lt;IMG src=&#039;http://blog.outsider.ne.kr/plugins/zExternalLink/external.png&#039; align=&#039;middle&#039; alt=&#039;새창으로 열기&#039;&gt;&lt;/a&gt;는 기존에는 저장소는 twitter하단에서 사용했지만 프로젝트가 커져서 그런지 이제는 &lt;a href=&quot;https://github.com/bower/bower&quot; target=&quot;_blank&quot;&gt;bower라는 계정으로 분리&lt;/a&gt;되었다. 그래서 검색으로 찾을 경우 일부 검색결과는 현재 깨지고 있다. &lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
npm install -g bower
&lt;/pre&gt;&lt;br&gt;bower는 node.js로 만들어진 도구 이므로 &lt;a href=&quot;https://npmjs.org/&quot; target=&quot;_blank&quot;&gt;npm&lt;/a&gt;을 이용해서 설치하고 커맨드라인 명령어로 사용하기 때문에 글로벌로 설치한다. 정상적으로 설치가 되면 &lt;font color=&quot;#FF7635&quot;&gt;bower -v&lt;/font&gt;로 버전정보를 확인할 수 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;bower 사용&lt;/font&gt;&lt;/b&gt;&lt;br&gt;bower는 npm과 매우 유사하므로 npm에 익숙하다면 bower도 쉽게 사용할 수 있다. &lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower install jquery
&lt;/pre&gt;&lt;br&gt;위와 같이 실행하면 jquery를 다운받는다. bower는 기본적으로 components라는 폴더를 사용하므로 현재 위치에 components 폴더가 생성되고(npm 처럼 하위폴더에서 실행하더라도 상위의 bower 컴포넌트 위치를 찾아서 설치하는 기능은 없어보인다.)그 아래 jquery 폴더가 생성되고 이 아래 jquery관련 파일들을 다운받는다. 최초 다운로드시에는 시간이 걸리지만 로컬에 캐싱하므로(&lt;font color=&quot;#FF7635&quot;&gt;~/.bower&lt;/font&gt;) 이후부터는 빨라진다. 한번에 여러 컴포넌트를 설치하고 싶다면 &lt;font color=&quot;#FF7635&quot;&gt;bower install jquery bootstrap&lt;/font&gt;과 같이 사용하면 된다.(여전히 컴포넌트라는 이름이 어색하기는 하지만 자바스크립트와 CSS등을 한꺼번에 다루기 때문에 이런 이름을 정한 것으로 생각된다.) 다만 이때 설치는 해당 파일 하나만(jquery.js나 jquery.min.js 등) 받아오는 것이 아닌 bower 저장소에 패키징되서 저장된 내용을 모두 내려받는다. 설치한 bootstrap의 폴더 내용을 보면 다음과 같다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ ll components/bootstrap/
total 168
drwxr-xr-x&amp;nbsp; 16 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 544&amp;nbsp; 4 29 19:57 .
drwxr-xr-x &amp;nbsp; 4 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 136&amp;nbsp; 4 29 19:57 ..
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 344&amp;nbsp; 4 28 20:25 .gitignore
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp; &amp;nbsp; 34&amp;nbsp; 4 28 20:25 .travis.yml
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff&amp;nbsp; 25120&amp;nbsp; 4 28 20:25 CHANGELOG.md
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp; 3488&amp;nbsp; 4 29 19:56 CONTRIBUTING.md
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff&amp;nbsp; 10172&amp;nbsp; 4 28 20:25 LICENSE
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp; 4561&amp;nbsp; 4 28 20:25 Makefile
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp; 4599&amp;nbsp; 4 28 20:25 README.md
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp; 5308&amp;nbsp; 4 29 19:57 component.json
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 297&amp;nbsp; 4 28 20:25 composer.json
drwxr-xr-x&amp;nbsp; 14 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 476&amp;nbsp; 4 28 20:25 docs
drwxr-xr-x &amp;nbsp; 4 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 136&amp;nbsp; 4 28 20:25 img
drwxr-xr-x&amp;nbsp; 17 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 578&amp;nbsp; 4 28 20:25 js
drwxr-xr-x&amp;nbsp; 44 outsider&amp;nbsp; staff &amp;nbsp; 1496&amp;nbsp; 4 28 20:25 less
-rw-r--r-- &amp;nbsp; 1 outsider&amp;nbsp; staff &amp;nbsp;&amp;nbsp; 700&amp;nbsp; 4 28 20:25 package.json
&lt;/pre&gt;&lt;br&gt;그래서 html페이지에서 참조할 때도 &lt;font color=&quot;#FF7635&quot;&gt;components/bootstrap/docs/assets/css/bootstrap.css&lt;/font&gt;나(bootstrap은 빌드된 파일이 좀 특이한 위치에 있다.) &lt;font color=&quot;#FF7635&quot;&gt;components/jquery/jquery.js&lt;/font&gt;로 참조해야 한다. 이는 일반적으로 정적파일 폴더 아래 js, css, images 등으로 구분해 놓고 사용하던 관례에 맞지 않고 경로도 상당히 복잡해 지지만(components 하위의 경로는 바꿀 수 없다.) bower를 쓰려면 어쩔 수 없어 보인다. bower는 단순 팩키지 매니저일 뿐이므로 배포시에 이런 파일들이 함께 배포되는 것이 싫다면 별도로 배포과정에서 처리를 해주어야 할 것 같다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower install jquery#1.9.1
&lt;/pre&gt;&lt;br&gt;버전을 지정해서 설치하고 싶다면 뒤에 #과 함께 버전번호를 붙혀준다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower uninstall jquery
&lt;/pre&gt;&lt;br&gt;설치한 컴포넌트를 제거하고 싶다면 uninstall 명령어를 사용한다. 사실 이는 그냥 components 폴더 아래 해당 폴더를 삭제해 준 것과 동일하다.&lt;br&gt;&amp;nbsp;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower list
bower discover Please wait while newer package versions are being discovered
example
├─┬ bootstrap#2.3.1
│ └── jquery#1.8.3 (2.0.0 now available)
└── jquery#1.8.3 (2.0.0 now available)
&lt;/pre&gt;&lt;br&gt;&lt;font color=&quot;#FF7635&quot;&gt;bower list&lt;/font&gt; 혹은 &lt;font color=&quot;#FF7635&quot;&gt;bower ls&lt;/font&gt;로 현재 폴더 하위에 설치된 컴포넌트들의 목록을 확인할 수 있고 최신버전이 아닌 경우 옆에 최신버전이 표시된다. 계층 구조라서 보기는 쉽지만 여기서 약간 조심해야 할 점이 있는데(npm에 익숙하다면 특히) 위와 같이 계층 구조로 나오기는 하지만 최상위의 jquery와 bootstrap하위의 jquery는 사실 같은 폴더이다. 그러므로 jquery의 버전을 올리면 양쪽이 모두 올라가게 된다. 저렇게 계층으로 표시되는 이유는 bootstrap안에서 jquery에 대한 의존성이 표시되어 있기 때문인데 실제로 node.js 처럼 폴더의 계층으로 의존성을 갖지 않으므로 두 jQuery는 사실상 같다. 이는 프론트앤드에서는 각 파일이 자신만의 의존성을 갖는것이 아니라 html에서 제어를 하게 되므로 당연한 선택으로 생각된다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower search d3
Search results:

&amp;nbsp; &amp;nbsp; d3 git://github.com/mbostock/d3.git
&amp;nbsp; &amp;nbsp; nvd3 git://github.com/novus/nvd3
&amp;nbsp; &amp;nbsp; d3-plugins git://github.com/d3/d3-plugins.git
&amp;nbsp; &amp;nbsp; github-d3 git://github.com/github/d3.git
&amp;nbsp; &amp;nbsp; d3cloud git://github.com/rajenms/d3-cloud.git
&amp;nbsp; &amp;nbsp; d3-tip git://github.com/Caged/d3-tip.git
&amp;nbsp; &amp;nbsp; d3-treelayout git://github.com/MfgLabs/d3-treelayout.git
&lt;/pre&gt;&lt;br&gt;원하는 컴포넌트가 존재하는지 찾고 싶다면 &lt;font color=&quot;#FF7635&quot;&gt;search &lt;/font&gt;명령어를 사용하면 된다. 패키지명에 검색어가 들어간 패키지를 모두 찾아준고 검색어를 생략하면 모든 패키지가 다 출력된다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower lookup d3
d3 git://github.com/mbostock/d3.git
&lt;/pre&gt;&lt;br&gt;패키지명으로 검색하고 싶다면 &lt;font color=&quot;#FF7635&quot;&gt;lookup &lt;/font&gt;명령어를 사용하고 이는 일치하는 패키지명만 출력된다. 일치하는 이름이 없는 경우에는 비슷한 패키지를 추천해준다.&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower info jquery
jquery

&amp;nbsp; Versions:
&amp;nbsp; &amp;nbsp; - 2.0.0
&amp;nbsp; &amp;nbsp; - 1.9.1
&amp;nbsp; &amp;nbsp; - 1.8.3
&amp;nbsp; &amp;nbsp; - 1.8.2
&amp;nbsp; &amp;nbsp; - 1.8.1
&amp;nbsp; &amp;nbsp; - 1.8.0
&amp;nbsp; &amp;nbsp; - 1.7.2
&amp;nbsp; &amp;nbsp; - 1.7.1
&amp;nbsp; &amp;nbsp; - 1.7.0
&amp;nbsp; &amp;nbsp; - 1.6.4
&amp;nbsp; &amp;nbsp; - 1.6.3
&amp;nbsp; &amp;nbsp; - 1.6.2
&amp;nbsp; &amp;nbsp; - 1.6.1
&amp;nbsp; &amp;nbsp; - 1.6.0
&amp;nbsp; &amp;nbsp; - 1.5.2
&amp;nbsp; &amp;nbsp; - 1.5.1
&amp;nbsp; &amp;nbsp; - 1.5.0
&amp;nbsp; &amp;nbsp; - 1.4.4
&amp;nbsp; &amp;nbsp; - 1.4.3
&amp;nbsp; &amp;nbsp; - 1.4.2
&amp;nbsp; &amp;nbsp; - 1.4.1
&amp;nbsp; &amp;nbsp; - 1.4.0
&amp;nbsp; &amp;nbsp; - 1.3.2
&amp;nbsp; &amp;nbsp; - 1.3.1
&amp;nbsp; &amp;nbsp; - 1.3.0
&amp;nbsp; &amp;nbsp; - 1.2.6
&amp;nbsp; &amp;nbsp; - 1.2.3
&lt;/pre&gt;&lt;br&gt;패키지의 버전 정보를 알고 싶다면 &lt;font color=&quot;#FF7635&quot;&gt;info &lt;/font&gt;명령어를 사용한다. npm과는 달리 간단히 버전 정보의 목록만 나타난다. &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;bower.json&lt;/font&gt;&lt;/b&gt;&lt;br&gt;보통 프로젝트의 의존성은 동일하게 가져가므로 설치한 패키지에 대한 정보를 명시해야 하는데 npm에서는 &lt;font color=&quot;#FF7635&quot;&gt;package.json&lt;/font&gt;에 명시하고 Maven에서는 &lt;font color=&quot;#FF7635&quot;&gt;pom.xml&lt;/font&gt;에 명시한다. bower에서는 &lt;font color=&quot;#FF7635&quot;&gt;bower.json&lt;/font&gt;을 사용하는데 이전 버전에서는 &lt;font color=&quot;#FF7635&quot;&gt;component.json&lt;/font&gt;을 사용하다가 0.9.0 버전부터는 &lt;font color=&quot;#FF7635&quot;&gt;bower.json&lt;/font&gt;을 사용하고 있다. &lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower init
name: [example]
version: [0.0.0] 0.1.0
main file: []
add commonly ignored files to ignore list? (y/n): [y] y
&lt;/pre&gt;&lt;br&gt;bower init을 사용하면 다음과 같은 bower.json을 생성할 수 있다&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
{
&amp;nbsp; &quot;name&quot;: &quot;example&quot;,
&amp;nbsp; &quot;version&quot;: &quot;0.1.0&quot;,
&amp;nbsp; &quot;ignore&quot;: [
&amp;nbsp; &amp;nbsp; &quot;**/.*&quot;,
&amp;nbsp; &amp;nbsp; &quot;node_modules&quot;,
&amp;nbsp; &amp;nbsp; &quot;components&quot;
&amp;nbsp; ]
}
&lt;/pre&gt;&lt;br&gt;&lt;font color=&quot;#FF7635&quot;&gt;bower.json&lt;/font&gt;에서 각 값의 의미는 아래와 같다.&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;name&lt;/font&gt;: (필수값) 팩키지명&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;version&lt;/font&gt;: 버전 정보&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;main&lt;/font&gt;: 패키지의 엔드포인트&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;ignore&lt;/font&gt;: 패키지를 설치할 때 bower가 무시할 파일 목록&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;dependencies &lt;/font&gt;: 프로덕션에서 사용할 패키지 의존성 정보&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;devDependencies &lt;/font&gt;: 개발용 패키지 의존성 정보&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;작성한 프로젝트를 bower에 배포할 것이 아니라면 &lt;font color=&quot;#FF7635&quot;&gt;dependencies&lt;/font&gt;와 &lt;font color=&quot;#FF7635&quot;&gt;devDependencies&lt;/font&gt;외이는 큰 의미는 없다. &lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
{
&amp;nbsp; &quot;dependencies&quot;: {
&amp;nbsp; &amp;nbsp; &quot;jquery&quot;: &quot;~2.0.0&quot;
&amp;nbsp; },
&amp;nbsp; &quot;devDependencies&quot;: {
&amp;nbsp; &amp;nbsp; &quot;qunit&quot;: &quot;~1.11.0&quot;
&amp;nbsp; }
}
&lt;/pre&gt;&lt;br&gt;의존성은 위와 같이 작성하면 되고 버전 표기 방식은 npm과 동일해 보인다(&lt;a href=&quot;http://blog.outsider.ne.kr/665&quot; target=&quot;_blank&quot;&gt;&quot;package.json으로 npm 의존성 모듈 관리하기&quot; 참고&lt;/a&gt;) &lt;font color=&quot;#FF7635&quot;&gt;bower.json&lt;/font&gt;에 의존성이 명시되어 있다면 &lt;font color=&quot;#FF7635&quot;&gt;bower install&lt;/font&gt; 만 실행하면 자동으로 &lt;font color=&quot;#FF7635&quot;&gt;bower.json&lt;/font&gt;에 명시된 컴포넌트들을 다운받아서 설치한다. 참고로 &lt;font color=&quot;#FF7635&quot;&gt;bower update&lt;/font&gt; 명령어도 있는데 예상과 달리 새로운 버전이 있어도 &lt;font color=&quot;#FF7635&quot;&gt;update &lt;/font&gt;명령어로는 새로운 버전을 설치하지 않는다.(명령어가 좀...) 좀 테스트를 해봤는데 잘 모르겠다...(이건 나중에..)&lt;br&gt;&lt;pre class=&quot;brush: shell&quot;&gt;
$ bower install jquery --save
$ bower install qunit --save-dev
&lt;/pre&gt;&lt;br&gt;&lt;font color=&quot;#FF7635&quot;&gt;bower.json&lt;/font&gt;에 일일이 버전을 표기하는 것이 귀찮다면 &lt;font color=&quot;#FF7635&quot;&gt;--save&lt;/font&gt;나 &lt;font color=&quot;#FF7635&quot;&gt;--save-dev&lt;/font&gt; 옵션을 사용하면 설치하면서 자동으로 &lt;font color=&quot;#FF7635&quot;&gt;bower.json&lt;/font&gt; 파일의 의존성 부분에 설치버전으로 추가를 해준다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;.bowerrc&lt;/font&gt;&lt;/b&gt;&lt;br&gt;bower에 전체적으로 적용할 설정을 사용자의 홈디렉토리에 &lt;font color=&quot;#FF7635&quot;&gt;.bowerrc&lt;/font&gt; 파일을 만들어서 지정할 수 있다. &lt;font color=&quot;#FF7635&quot;&gt;.bowerrc&lt;/font&gt;파일은 다음과 같은 JSON 형식이다.&lt;br&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
{
&amp;nbsp; &quot;directory&quot;: &quot;bower_components&quot;,
&amp;nbsp; &quot;endpoint&quot;: &quot;https://bower.mycompany.com&quot;,
&amp;nbsp; &quot;json&quot;: &quot;bower.json&quot;,
&amp;nbsp; &quot;searchpath&quot;: [
&amp;nbsp; &amp;nbsp; &quot;https://bower.herokuapp.com&quot;
&amp;nbsp; ],
&amp;nbsp; &quot;shorthand_resolver&quot;: &quot;git://example.com/{{{ organization }}}/{{{ package }}}.git&quot;
}
&lt;/pre&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;directory &lt;/font&gt;: 컴포넌트를 설치할 기본 디렉토리&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;endpoint&lt;/font&gt;: 커스텀 등록 엔드포인트&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;json&lt;/font&gt;: 의존성을 처리할 때 사용할 기본 JSON 파일&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;searchpath&lt;/font&gt;: 추가적으로 검색할 읽기전용 Bower 저장소&lt;/li&gt;&lt;li&gt;&lt;font color=&quot;#FF7635&quot;&gt;shorthand_resolver&lt;/font&gt;: 간단하게 지정할 패키지명의 템플릿&lt;/li&gt;&lt;/ul&gt;&lt;font color=&quot;#FF7635&quot;&gt;shorthand_resolver&lt;/font&gt;의 경우 기본 값이 github이므로 account/repository 처럼 지정할 수도 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size=&quot;5&quot;&gt;bower의 장점&lt;/font&gt;&lt;/b&gt;&lt;br&gt;초반에 bower를 접했을 때 느꼈던 것처럼 약간 거추장스럽게 느껴질 수도 있다. &lt;br&gt;하지만..&lt;br&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;프론트앤드쪽 라이브러리도 많이 복잡해 졌기 때문에 의존성 관리가 필요하다. 경로가 좀 복잡해 지지만 부트스트랩같은 경우 안에 css, js, img등이 있으므로 일일이 나눠서 복사해주거나 어차피 전체를 하나의 폴더안에 넣어야 한다.)&lt;/li&gt;&lt;li&gt;변경사항을 추적할 필요가 없는 의존성 라이브러리를 저장소에 추가하는 것은 낭비다.(프론트앤드라서 익숙치 않아서 그렇지 개념은 서버와 동일하다. 물론 서버는 보통 바이너리라는 차이가 있지만)&lt;/li&gt;&lt;li&gt;json 파일만 공유하고 의존성 라이브러리는 각자 설치해서 사용하는 것이 더 좋은 접근으로 보인다.(물론 프론트앤드에서는 아직 익숙치 않으므로 불필요한 과정이 늘어난 것으로 보일수도 있다.)&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;</summary>
  </entry>
</feed>
