Xây dựng J2EE Web Container riêng sử dụng Tomcat Embbed
tomcat
3
Axis2
1
groovy
6
Java
155
Servlet
2
Male avatar

Trần Trung Đức viết ngày 15/01/2019

Xây dựng J2EE Web Container riêng sử dụng Tomcat Embbeded

Đặt vấn đề

Các bạn đã từng xây dựng một ứng dụng Web dựa trên java dựa trên Servlet sẽ không xa lạ với các Phần mềm để chạy ứng dụng Web như Tomcat, Jetty, Glassfish, Jboss, Oracle Application Server, Oracle Weblogic, IBM Websphere.

Tomcat với mình có lẽ trải nghiệm đầu tiên khi bắt đầu xây dựng các ứng dụng Web. Ở công ty tôi làm việc, khi triển khai các dự án dựa trên Tomcat một số dự án thiếu nhân sự cứng có kinh nghiệm sẽ gặp phải nhiều vấn đề về hiệu năng như: hệ thống chạy 1 thời gian thì chậm, treo; khó khăn trong việc thiết lập các tham số của Tomcat như Connection, JDBC, Timeout ...

Mình đã dựng một bộ J2EE Web Container riêng sử dụng Tomcat Embbeded và đã dùng dưới dạng Axis2 SOA Module, Web Content Management cho một số dự án.

Trong bài viết này mình xin giới thiệu chi tiết các thức xây dựng một bộ ứng dụng J2EE Web Container.

Các tính năng

  • Chạy các ứng dụng dựa trên Servlet, hỗ trợ sẵn Axis2, jsp
  • Hỗ trợ lưu log toàn bộ request/response tới ứng dụng theo cơ chế Async
  • Hỗ trợ cấu hình tiến trình sử dụng CronTab + Groovy
  • Hỗ trợ sẵn kết nối tới Oracle Database
  • Tối ưu sẵn một số tham số của Tomcat theo kinh nghiệm của mình

Tổ chức ứng dụng

Tổ chức ứng dụng theo mô hình dưới đây:
alt text

Trong đó:

  • app là nơi lưu trữ các ứng dụng, ở ví dụ này có 2 ứng dụng là axis2 và web
  • conf lưu trữ các cấu hình, sẽ có các cấu hình:
    • Cấu hình ứng dụng api.properties
    • Cấu hình log log4j.properties
    • Cấu hình ngôn ngữ, gồm 1 hoặc nhiều ngôn ngữ, ví dụ: VI_VN.properties
    • Cấu hình các job: conf/job, ví dụ write_log.groovy, job định kỳ 1 phút ghi log từ cache tới hệ thống lưu trữ log
  • lib lưu trữ các thư viện jar
  • logs lưu trữ log
  • temp lưu trữ file tạm do Tomcat Embbeded tạo ra
  • Các file start (run.sh/run.cmd) và stop (stop.sh) ứng dụng

Code

Code tại địa chỉ: https://github.com/trantrungduc/npc. Ghi chú:

  • Code sử dụng Eclipse Version: Neon.2 Release (4.6.2)
  • Java 1.8.0_65 64 bit

Các cấu hình chính

module_temp_dir=temp      #Thư mục tạm của Tomcat
module_port=7001      #Port ứng dụng
module_init_connect = 20      #Số lượng connection khởi tạo đầu tiên của Tomcat
module_max_connect = 150      #Số lượng Connection tối đa
module_connection_timeout=5000      #Timeout từ Server

#Khai báo các J2EE Container
module_context.name=api
api.context_path=/api
api.location_path=/app/axis2

module_context.name=web
web.context_path=/web
web.location_path=/app/web

#Khai báo 
job.name=log
log.script=write_log.groovy
log.schedule=0/60 * * * * ?

Mô tả code

Code gồm 4 lớp là: Mnp lớp main, DumpFilter Servlet Filter để collect bản tin request/Response, GroovyJob lớp xử lý các tiến trình CronTab, SqlUtility lớp kết nối Oracle.

Ở đây mình xin đề cập chi tiết lớp Mnp, các lớp khác các bạn vui lòng xem phần code, nó cũng khá ngắn và đơn giản.

1. Đầu tiên là khai báo các biến, mình thường dùng một số biến public static để tiến kiệm bộ nhớ và cho phép truy cập được từ Groovy Context:

public static final Logger logger = Logger.getLogger("filter");
public static final Logger log = Logger.getLogger("process");
public static EvictingQueue<String[]> requests = EvictingQueue.create(400000);
public static Map<String,Object> global = new ConcurrentHashMap<String,Object>();
public static Scheduler scheduler;
public static final Gson gson = new Gson();
public static Map<String,String> runningJob = new ConcurrentHashMap<String,String>();
public static String path = "";

public static PropertiesConfiguration props = null;
public static PropertiesConfiguration i18n = null;
public static SqlUtility utility = null;

static BasicTextEncryptor textEncryptor = new BasicTextEncryptor();

Trong đó, requests để lưu trữ log Request/Response từ DumpFilter. Tiến trình write_log sẽ pull từ đây để ghi log vào hệ thống lưu trữ log. Trong ví dụ này, mình push ra stdout
scheduler và runningJob để quản lý các job nhằm kiểm soát không cho phép các job chạy nhiều Instance khi Instance trước đó khi chưa chạy xong.
textEncryptor để mã hóa mật khẩu kết nối CSDL

2. Các hàm chung

Mã hóa/giải mã mật khẩu (enc/dec), hàm gọi http (post/posts, get/gets), các hàm xử lý XML (getXmlAttr, getXmlNodes), hàm chạy script Velocity (eval), hàm chạy script Groovy (shell).

3. Hàm khởi động ứng dụng startTomcat

Khởi tạo các biến chung

path = System.getProperty("user.dir");
System.out.println("Load configuration api.properties!");
props = new PropertiesConfiguration();
props.setDelimiterParsingDisabled(true);
props.setEncoding("UTF-8");
props.load(path+"/conf/api.properties");
props.setReloadingStrategy(new FileChangedReloadingStrategy());
utility = new SqlUtility(props);
textEncryptor.setPassword("mat khau");

i18n = new PropertiesConfiguration();
i18n.setDelimiterParsingDisabled(true);
i18n.setEncoding("UTF-8");
i18n.load(path+"/conf/vi_VN.properties");
i18n.setReloadingStrategy(new FileChangedReloadingStrategy());

Tạo Tomcat, tạo Connector và add ứng dụng

Tomcat tomcat = new Tomcat();
tomcat.setBaseDir(props.getString("module_temp_dir"));

Connector connect = tomcat.getConnector();
connect.setPort(props.getInt("module_port"));
connect.setURIEncoding("UTF8");
connect.setAttribute("maxThreads", props.getInt("module_max_connect"));
connect.setAttribute("acceptCount", props.getInt("module_max_connect"));
connect.setAttribute("connectionTimeout", props.getInt("module_connection_timeout"));
connect.setProtocol("org.apache.coyote.http11.Http11NioProtocol");
connect.setAttribute("keepAliveTimeout",props.getInt("module_connection_timeout"));
connect.setAttribute("maxKeepAliveRequests",1);
connect.setAttribute("minSpareThreads",props.getInt("module_init_connect"));
connect.setAttribute("processorCache",props.getInt("module_max_connect"));

List<Object> webapp = props.getList("module_context.name");
List<Context> ctxs = new ArrayList<Context>();
for (Object name:webapp){
    Context ctx = tomcat.addWebapp(props.getString(name+".context_path"), path+props.getString(name+".location_path"));
ctxs.add(ctx);
}
if (props.containsKey("module_accept_ip")){
    RemoteAddrValve valve = new RemoteAddrValve();
    valve.setAllow(props.getString("module_accept_ip"));
    for (Context ctx:ctxs){
        ctx.getPipeline().addValve(valve);
    }
}

Khởi tạo quản lý Job

try{

    System.out.println("Load jobs!");
    scheduler = new StdSchedulerFactory().getScheduler();
    scheduler.start();

    new Timer().schedule(new TimerTask() {

        @Override
        public void run() {
            String[] jobs = props.getStringArray("job.name");
            List<String> removes = new ArrayList<String>();
            for (String jobName: runningJob.keySet()){
                try {
                    TriggerKey tk=TriggerKey.triggerKey(jobName,jobName);
                    JobKey jk = JobKey.jobKey(jobName, jobName);
                    String k = props.getString(jobName+".schedule")+"|"+props.getString(jobName+".script");

                    if (!Arrays.asList(jobs).contains(jobName) || !k.equals(runningJob.get(jobName))){
                        System.out.println("Stop job: "+jobName);
                        scheduler.unscheduleJob(tk);
                        scheduler.interrupt(jk);
                        scheduler.deleteJob(jk);
                        removes.add(jobName);
                    }
                } catch (SchedulerException e) {
                    System.out.println("Stop job: "+e.getMessage());
                }
            }
            for (String remove:removes){
                runningJob.remove(remove);
            }
            for (String jobName: jobs){
                if (!runningJob.containsKey(jobName)){
                    System.out.println("Start job: "+jobName);
                    JobDetail job = JobBuilder.newJob(GroovyJob.class).usingJobData("job", jobName).usingJobData("script", props.getString(jobName+".script")).usingJobData("scheduler", props.getString(jobName+".schedule")).withIdentity(jobName, jobName).build();
                    Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobName,jobName)
                            .withSchedule(CronScheduleBuilder.cronSchedule(props.getString(jobName+".schedule")))
                            .build();
                    try {
                        scheduler.scheduleJob(job, trigger);
                        runningJob.put(jobName,props.getString(jobName+".schedule")+"|"+props.getString(jobName+".script"));

                    } catch (SchedulerException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    },0,10000);
}catch(Exception e){
    e.printStackTrace();
}

Cuối cùng là Enable Tomcat

tomcat.enableNaming();        
tomcat.start();
tomcat.getServer().await();

Lớp main như sau

public static void main(String args[]) {
    try {
        Mnp tomcat = new Mnp();
        tomcat.startTomcat();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Khởi động ứng dụng

alt text

Kiểm tra

http://127.0.0.1:7001/web/:
alt text
http://127.0.0.1:7001/api/services/Version?method=getVersion:
alt text

[10/01/2019] Trần Trung Đức {ductt.net@gmail.com}

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

Male avatar

Trần Trung Đức

3 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


Male avatar
{{userFollowed ? 'Following' : 'Follow'}}
3 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!