`

基于Struts2+Spring+iBatis的web应用最佳实践系列之二(访问控制篇上)

阅读更多

访问控制对于一个web应用来说几乎是不可或缺的。当访问web应用当中的某些资源时,如果你这时还没有登录,那么就会被重定向到登录页面,只有在登录之后才会被允许访问。经常上网的朋友对这样一个场景一定不会陌生。那么,如何实现对部分访问受限的url进行保护呢?笔者在这里向大家介绍一种基于cookie的实现方法。

 

 当用户登录的时候我们就在httpResponse中写入一个cookie,这个cookie就成为了一个标志。服务端每次收到http请求的时候就会先检查所请求的资源是否受到保护,如果是的话则会检查httpRequest中的cookie是否有效,只有检查通过了的才被允许访问,如果cookie无效则会被重定向到登录页面。对于那些不受访问限制的url则一律通过。

 

既然是基于Struts2的web应用,看到这样一个需求不禁让人想起了Struts2里的拦截器,而仔细研究Struts2之后也发现在它的发行包里正好也有一个cookie拦截器。

 

Struts2的cookie拦截器

<interceptor name="cookie" class="org.apache.struts2.interceptor.CookieInterceptor"/>

 

 这个拦截器没有出现在Struts2的默认拦截器栈中,不过并不妨碍我们对其研究一番。首先这个拦截器有两个方法分别接受cookieName和cookieValue的参数。

 

    /**
     * Set the <code>cookiesName</code> which if matche will allow the cookie
     * to be injected into action, could be comma-separated string.
     *
     * @param cookiesName
     */
    public void setCookiesName(String cookiesName) {
        if (cookiesName != null)
            this.cookiesNameSet = TextParseUtil.commaDelimitedStringToSet(cookiesName);
    }

    /**
     * Set the <code>cookiesValue</code> which if matched (together with matching
     * cookiesName) will caused the cookie to be injected into action, could be
     * comma-separated string.
     *
     * @param cookiesValue
     */
    public void setCookiesValue(String cookiesValue) {
        if (cookiesValue != null)
            this.cookiesValueSet = TextParseUtil.commaDelimitedStringToSet(cookiesValue);
    }

 

cookieName和cookieValue的参数可以在Struts2的配置文件中配置。

 

 <action ... >
    <interceptor-ref name="cookie">
        <param name="cookiesName">cookie1, cookie2</param>
        <param name="cookiesValue">cookie1value, cookie2value</param>
    </interceptor-ref>
    ....
 </action>

 

 在这个拦截器的核心拦截方法中,它会从httpRequest中读取cookie,由populateCookieValueIntoStack方法将读取的cookie保存在一个map中。对于具体读取哪些cookie是由cookiesName和cookiesValue参数共同决定的,不过规则有些复杂和繁琐,这里就不具体展开讨论了。

 

    public String intercept(ActionInvocation invocation) throws Exception {

        if (LOG.isDebugEnabled())
            LOG.debug("start interception");

        final ValueStack stack = ActionContext.getContext().getValueStack();
        HttpServletRequest request = ServletActionContext.getRequest();

        // contains selected cookies
        Map cookiesMap = new LinkedHashMap();

        Cookie cookies[] = request.getCookies();
        if (cookies != null) {
            for (int a=0; a< cookies.length; a++) {
                String name = cookies[a].getName();
                String value = cookies[a].getValue();

                if (cookiesNameSet.contains("*")) {
                    if (LOG.isDebugEnabled())
                        LOG.debug("contains cookie name [*] in configured cookies name set, cookie with name ["+name+"] with value ["+value+"] will be injected");
                    populateCookieValueIntoStack(name, value, cookiesMap, stack);
                }
                else if (cookiesNameSet.contains(cookies[a].getName())) {
                    populateCookieValueIntoStack(name, value, cookiesMap, stack);
                }
            }
        }

        injectIntoCookiesAwareAction(invocation.getAction(), cookiesMap);

        return invocation.invoke();
    }

 

 最后将包含cookie的map注入到action中,当然,前提是这个action实现CookiesAware这个接口。

 

    /**
     * Hook that set the <code>cookiesMap</code> into action that implements
     * {@link CookiesAware}.
     *
     * @param action
     * @param cookiesMap
     */
    protected void injectIntoCookiesAwareAction(Object action, Map cookiesMap) {
        if (action instanceof CookiesAware) {
            if (LOG.isDebugEnabled())
                LOG.debug("action ["+action+"] implements CookiesAware, injecting cookies map ["+cookiesMap+"]");
            ((CookiesAware)action).setCookiesMap(cookiesMap);
        }
    }

 

研究完之后我们发现这个拦截器还是挺简单的,仅仅只是从httpRequest中读取cookie并注入到action中,里我们的需求还有很远的距离。一般的思路是在这个拦截器的基础上继续构造我们需要的东西。即在登录action中在登录成功之后将身份信息写入到cookie中,在拦截器中则验证cookie是否有效,如果无效则重定向到登录页面。这个实现方法看似可行,但仔细想想却存在着很多问题。

 

第一,登录action和拦截器变得过于臃肿。我们需要造登录action中不仅要加入写入cookie的代码,出于安全性的考虑,还要在写入cookie前对其进行加密处理,否则恶意用户可以伪造cookie带来安全隐患。拦截器中不仅要对cookie解密还要判断其是否有效。面向对象程序设计的原则告诉我们好的设计需要做到高聚合,也就是说一个类只做一件事情,显然用户登录属于业务层面的逻辑而处理cookie则属于比较底层的操作,将这二者放到一起肯定不是好的设计。

 

第二,降低了组件的可复用性。登录的逻辑是应用特有的,也就是说每一个应用都有它自己的登录逻辑,但是对cookie的操作或者说对于“只有在登录之后才会被允许访问某些受保护的资源,否则会被重定向到登录页面”这样的需求却是普遍存在的,既然如此我们应该可以把这样的需求抽象出来,做成可复用的组件,编入我们自己的工具库,以供不同的应用使用。

 

 下面我们就来实现一种同样基于cookie的可复用的解决方案。

 

 首先我们要设计好我们的ClientCookie类,这个ClientCookie类必须是对用户友好的,也就是说用户可以对其进行扩展但又不必关心底层的细节,我们可以通过提供一个对用户关心的ClientCookie与底层的httpCookie的映射来达到这个目的。同时我们把一些底层的数据,比如ip,时间戳等,login等封装在一个cookieMask中,从而把我们的ClientCookie类解放出来。下面是这个cookieMask类的代码,同时我们以一定的编码规则将这个cookieMask类映射到一个单独的httpCookie中。

 

public class ClientCookieMask implements Serializable{
	private static final long serialVersionUID = 1L;
	public static char SPLITER = '|';
	private long lastTime= -1;
	private long firstTime = -1;
	private String loginId;
	private String clientIp;
	
	private boolean valid = false;;
	/**
	 * mask 编码规则:
	 * loginId + '|' + IP + '|' + 'first time' +'|'+'last time'
	 */
	public ClientCookieMask(String mask){
		String tmp[] = StringUtils.split(mask, SPLITER);
		
		if(tmp!= null && tmp.length==4){
			loginId = tmp[0];
			clientIp = tmp[1];
			try{
				firstTime = Long.parseLong(tmp[2]);
			}catch(NumberFormatException e){
				firstTime = 0;
				valid = false;
				return;
			}
			try{
				lastTime = Long.parseLong(tmp[3]);
			}catch(NumberFormatException e){
				lastTime = 0;
				valid = false;
				return;
			}
			valid = true;
		}else{
			valid = false;
		}
	}
	
	public boolean isValid() {
		return valid;
	}

	public long getLastTime() {
		return lastTime;
	}
	public void setLastTime(long lastTime) {
		this.lastTime = lastTime;
	}
	public long getFirstTime() {
		return firstTime;
	}
	public void setFirstTime(long firstTime) {
		this.firstTime = firstTime;
	}
	public String getClientIp() {
		return clientIp;
	}
	public void setClientIp(String clientIp) {
		this.clientIp = clientIp;
	}
	public String getLoginId() {
		return loginId;
	}
	public void setLoginId(String loginId) {
		this.loginId = loginId;
	}
	
	public String toString(){
		StringBuffer buffer = new StringBuffer();
		buffer.append(this.getLoginId());
		buffer.append(ClientCookie.SPLITER).append(clientIp);
		buffer.append(ClientCookie.SPLITER).append(firstTime);
		buffer.append(ClientCookie.SPLITER).append(System.currentTimeMillis());
		return buffer.toString();
	}
}

 

 在我们的ClientCookie类中,保存有一个对cookieMask的引用,同时我们还有一个map,每次调用这个cookie 的setter方法的时候,同时把set过来的值保存到这个map中,在代码中看不到这个过程,因为这是通过cglib实现的。cglib(Code Generation Library)是一个强大的、高性能、高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。cglib封装了asm,可以在运行期动态生成新的class。

 

 

public abstract class ClientCookie {
	private ClientCookieWriter cookieWriter;
	public static char SPLITER = '|';
	public static String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
	
	//单位分钟
	private long maxLifeTime = -1;
	
	//单位分钟
	private long maxIdleTime = -1;
	
	public long getMaxIdleTime() {
		return maxIdleTime;
	}

	public void setMaxIdleTime(long maxIdleTime) {
		this.maxIdleTime = maxIdleTime;
	}

	boolean inited = false;
	
	//对ClientCookie使用setter方式的同时会把set的值保存到这个map中
	Map<String,Object> settedMap;
	boolean ipValidate = false; 
	String clientIp;
	
	//对cookieMask的引用
	private ClientCookieMask cookieMask;
	

	/**
	 * mask 编码规则:
	 * loginId + '|' + IP + '|' + 'first time' +'|'+'last time'
	 */
	private String mask;
	
	public long getMaxLifeTime() {
		return maxLifeTime;
	}

	public void setMaxLifeTime(long idleTime) {
		this.maxLifeTime = idleTime;
	}

	public String getClientIp() {
		return clientIp;
	}

	public abstract String getLoginId(); 
	
	//判断cookie是否有效
	public boolean isValid(){
		if(StringUtils.isEmpty(mask))return false;
		ClientCookieMask cookieMask = getCookieMask();
		if(cookieMask != null){
			return cookieMask.isValid() 
				&& StringUtils.equals(getLoginId(), cookieMask.getLoginId())
				&& validDuringTime(cookieMask.getFirstTime())
				&& validIdleTime(cookieMask.getLastTime())
				&& (ipValidate?StringUtils.equals(getClientIp(), cookieMask.getClientIp()):true);
		}else{
			return false;
		}
	}
	
	//生成cookieMask
	protected String generatMask(){
		StringBuffer buffer = new StringBuffer();
		buffer.append(this.getLoginId());
		buffer.append(ClientCookie.SPLITER).append(clientIp);
		buffer.append(ClientCookie.SPLITER).append(System.currentTimeMillis());
		buffer.append(ClientCookie.SPLITER).append(System.currentTimeMillis());
		
		return buffer.toString();
	}
	
	protected boolean validIdleTime(long lastTime){
		if(maxIdleTime >0){
			if((System.currentTimeMillis() - lastTime)<maxIdleTime * 60 * 1000){
				return true;
			}else{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * lastTime
	 * @param lastTime
	 * @return
	 */
	protected boolean validDuringTime(long lastTime){
		if(maxLifeTime >0){
			if((System.currentTimeMillis() - lastTime)<maxLifeTime * 60 * 1000){
				return true;
			}else{
				return false;
			}
		}else{
			return true;
		}
	}
	
	void setCookieWriter(ClientCookieWriter writer){
		this.cookieWriter = writer;
	}
	
	public String getMask() {
		return mask;
	}
	
	public ClientCookieMask getCookieMask() {
		if(cookieMask == null){
			cookieMask = new ClientCookieMask(mask);
		}
		return cookieMask;
	}

	public void setMask(String mask) {
		this.mask = mask;
	}
	
	/**
	 * 持久化cookie(客户端或者cookieServer)
	 */
	public void save(){
		setMask(generatMask());
		cookieWriter.writeCookie(this);
		settedMap.clear();
	}
	
	public void update(){
		getCookieMask().setLastTime(System.currentTimeMillis());
		setMask(this.getCookieMask().toString());
		cookieWriter.writeCookie(this);
		settedMap.clear();
	}
	
	public void clear(){
		setMask(null);
		cookieWriter.writeCookie(this);
		settedMap.clear();
	}
}

 

至此我们已经有了cookie类,但是还需建立客户友好的cookie与httpCookie之间的映射,我们把这个映射放在一个cookieMapping.xml的配置文件中。

 

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE cookieMapping SYSTEM "cookieMapping.dtd">
<cookieMapping
	cookieClass="com.meidusa.demo.web.Cookie"
	encryptKey="ei*736TR"
	loginUrl="http://www.meidusa.com:8080/demo/login.html" algorithm="DES" maxLifeTime="1440" maxIdleTime="-1">
	
	<cookie cookieName="meidusa_cookie2" innerCookieName="loginId">
		<property name="age">1000</property>
		<property name="domain">www.meidusa.com</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>

	<cookie cookieName="meidusa_mask" innerCookieName="mask">
		<property name="age">1000</property>
		<property name="domain">www.meidusa.com</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>
</cookieMapping>

 

 dtd定义这里就不介绍了,cookieClass指的是用户自己定义的cookie类,这个类需要继承我们上面的ClientCookie,encryptKey是我们用来对cookie加密的key,这里的加密指的是在将cookie写入到httpCookie时的加密,loginUrl当用户没有登录访问受保护的资源时被重定向到的登录页面的url。algorithm是指加密算法的类型,maxLifeTime是指cookie的最长有效时间,maxIdleTime是指最长空闲时间,-1代表没有这个限制。

 

下面的cookie项代表了每一个cookie映射,cookieName代表的是底层的httpCookie的名字,innerCookieName指的是对客户友好的cookie名,这里我们可以看到我们把ClientCookie中的cookeMask拿出来映射成了一个httpCookie,又把loginId单独拿出来映射成了一个httpCookie。当然,用户可以根据自己的需要定制自己的ClientCookie类,并自己定义这个cookie的映射关系。

 

 讲到这里,细心的读者可能已经发现,这个cookie的映射配置文件我们在上一篇文章中在讲自动配置的时候其实已经拿出来看过。是的,你猜对了,我们可以进一步的使用我们的autoconfig插件来管理这个配置文件,我们把部署特有的配置放到properties文件中,使用autoconfig通过模板生成我们真正需要的配置文件。

 

cookie.algorithm   = DES
cookie.domain      = www.meidusa.com
cookie.encryptKey  = ei*736TR
cookie.loginUrl    = http://www.meidusa.com:8080/demo/login.xml

 

cookieMapping的模板文件

 

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE cookieMapping SYSTEM "cookieMapping.dtd">
<cookieMapping
	cookieClass="com.meidusa.demo.web.Cookie"
	encryptKey="${cookie_encryptKey}"
	loginUrl="${cookie_loginUrl}" algorithm="${cookie_algorithm}" maxLifeTime="1440" maxIdleTime="-1">
	
	<cookie cookieName="meidusa_cookie2" innerCookieName="loginId">
		<property name="age">1000</property>
		<property name="domain">${cookie_domain}</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>
	
	<cookie cookieName="meidusa_mask" innerCookieName="mask">
		<property name="age">1000</property>
		<property name="domain">${cookie_domain}</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>
</cookieMapping>

 

我们在servlet容器启动的时候通过我们自己的一个listenser来载入cookieMapping配置文件。

	<context-param>
		<param-name>cookieMappingFile</param-name>
		<param-value>${project.home}/conf/cookieMapping.xml</param-value>
	</context-param>

	<listener>
		<listener-class>
			com.meidusa.toolkit.web.cookie.ClientCookieContextLoaderListener
		</listener-class>
	</listener>

 

这个listener的声明如下

public class ClientCookieContextLoaderListener implements ServletContextListener,ClientCookieWriter,ClientCookieReader,ClientNoLoginRedirector

 ServletContextListener接口是servlet API的一部分,这里就不讨论了。除了ServletContextListener接口之外,这个listener还实现了ClientCookieWriter,ClientCookieReader,ClientNoLoginRedirector这三个接口。

 

public interface ClientCookieReader {
	public ClientCookie readCookie(HttpServletRequest request) throws Exception;
}

public interface ClientCookieWriter {
	public void writeCookie(ClientCookie cookie);
}

public interface ClientNoLoginRedirector {
	public String getRedirectUrl(HttpServletRequest request);
}

 从上面的声明中可以看到,这三个接口的功能分别是用来读取,写入cookie和重定向httpRequest。

 

也就是说我们的listener不仅负责装配配置文件,同时还负责对cookie 的底层操作。

 

从下面的代码中可以看到在容器初始化的时候这个listener负责装配cookieMapping的配置文件,同时也做一些初始化工作。具体的装配配置文件的代码这里就不贴了。

 

	public void contextInitialized(ServletContextEvent sce) {
		cookieMappingFile = sce.getServletContext().getInitParameter("cookieMappingFile");
		this.setCookieMappingFile(cookieMappingFile);
		init();
	}
	
	public synchronized void init(){
		if(!inited){
			cookieMappingFile = ConfigUtil.filter(cookieMappingFile);
			loadConfig(cookieMappingFile);
			ClientCookieFactory.getInstance().setReader(this);
			ClientCookieFactory.getInstance().setWriter(this);
			ClientCookieFactory.getInstance().setRedirector(this);
			inited = true;
			algorithm = SymmetricAlgorithm.valueOf(SymmetricAlgorithm.class, cookieConfig.getAlgorithm());
		}
	}

 

对底层cookie操作的代码,在读取cookie的时候,我们这里用到了cglib中的Enhancer和MethodInterceptor来拦截通过setter对cookie的访问,将set过来的值同步保存到一个map中。

	public void writeCookie(ClientCookie cookie) {
		if(cookie.settedMap != null){
	    	for(Map.Entry<String, Object> entryProp : cookie.settedMap.entrySet()){
	    		ClientCookieConfigEntry entry = cookieInnerNameMapping.get(entryProp.getKey());
	    		if(entry!= null && entry.isWritable()){
	    			javax.servlet.http.Cookie httpCookie = new javax.servlet.http.Cookie(entry.getCookieName(),"");
					httpCookie.setDomain(entry.getDomain());
					httpCookie.setPath(entry.getPath());
					httpCookie.setVersion(1);
	    			Object value;
					try {
						value = entryProp.getValue();
						if(value == null){
							httpCookie.setMaxAge(0);
						}else{
							httpCookie.setMaxAge(entry.getAge());
							String cookieValue = value.toString();
							if(cookieValue != null && entry.isSecure()){
								cookieValue = encrypt(cookieValue);
							}
							httpCookie.setValue(cookieValue);
						}
		    			ServletActionContext.getResponse().addCookie(httpCookie);
					} catch (Exception e) {
						logger.error("write cookie error",e);
					}
	    			
	    		}
	    	}
		}
	}
	
	

	public ClientCookie readCookie(HttpServletRequest request) throws Exception {
		ClientCookie cookie = (ClientCookie)Enhancer.create(Class.forName(cookieConfig.getCookieClass()),cookieSetterInterceptor);
		cookie.setCookieWriter(this);
		cookie.ipValidate = this.ipValidate;
		cookie.clientIp = request.getRemoteAddr();
		cookie.setMaxLifeTime(cookieConfig.getMaxLifeTime());
		cookie.setMaxIdleTime(cookieConfig.getMaxIdleTime());
		cookieMapping(cookie,request);
		cookie.inited = true;
		return cookie;
		
	}

	public String getRedirectUrl(HttpServletRequest request) {
		String query = request.getQueryString();
		String fullQuery = request.getRequestURL()+ (query == null?"":("?"+ query));
		String encoded = fullQuery;
		try {
			encoded = URLEncoder.encode(fullQuery,"utf8");
		} catch (UnsupportedEncodingException e) {
		}
		return cookieConfig.getLoginUrl()+"?redirect="+encoded;
	}

 

在readCookie中的cookieSetterInterceptor是一个MethodInterceptor (注意,这里的MethodInterceptor并不是Struts2中的Interceptor,而是cglib中的MethodInterceptor)

class ClientCookieMothedInterceptor implements MethodInterceptor {
		public Object intercept(Object object, Method method, Object[] args,
				MethodProxy methodProxy) throws Throwable {
			ClientCookie cookie = (ClientCookie)object;
			Object result = methodProxy.invokeSuper(cookie,args);
			if(cookie.inited){
				if((method.getModifiers() & Modifier.PUBLIC)>0){
					String name = method.getName();
					if(name.startsWith("set") && name.length() > 3){
						char[] fieldChars = name.toCharArray();
						fieldChars[3] = Character.toLowerCase(fieldChars[3]);
						
						String fieldName = new String(fieldChars,3,fieldChars.length-3);
						
						if(cookie.settedMap == null){
							cookie.settedMap = new HashMap<String ,Object>();
						}
						cookie.settedMap.put(fieldName, args[0]);
					}
				}
			}
			return result;
		}
	};

 

现在,我们已经有了自己的cookie类,cookie的映射,以及底层操作的api,最后我们再来看一下如何利用Struts2中的拦截器来完成最终的控制访问。在这里,通过继承,我们自己实现了一个拦截器。

 

public class ClientCookieInterceptor extends AbstractInterceptor {
	private static final long serialVersionUID = 1L;
	private static Logger logger = Logger.getLogger(ClientCookieInterceptor.class);
	

	public void init() {
		super.init();
		
    }

	@SuppressWarnings("unchecked")
	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		ClientCookie cookie = null;
		HttpServletRequest request = ServletActionContext.getRequest();
		try{
			cookie = ClientCookieFactory.getInstance().readCookie(request);
		}catch(Exception e){
			logger.error("mapping cookie error",e);
			return redirect();
		}
		if(!(invocation.getAction() instanceof ClientCookieNotCare)){
			if(!cookie.isValid()){
				return redirect();
			}
		}
		
		if(invocation.getAction() instanceof ClientCookieAware){
			ClientCookieAware cookieAware = (ClientCookieAware)invocation.getAction();
			cookieAware.setClientCookie(cookie);
		}
		request.setAttribute("clientCookie", cookie);
		
		if(!(invocation.getAction() instanceof ClientCookieNotCare)){
			if(cookie != null){
				if(cookie.getCookieMask() != null){
					cookie.update();
				}
			}
		}
		return invocation.invoke();
	}
	
	protected String redirect() throws IOException{
		HttpServletRequest request = ServletActionContext.getRequest();
		String redirectUrl = ClientCookieFactory.getInstance().getRedirectUrl(request);
		ServletActionContext.getResponse().sendRedirect(redirectUrl);
		return Action.NONE;
	}

}

 

在这个拦截器中会检查,如果用户action没有实现ClientCookieNotCare接口并且cookie无效则会被重定向到登录页面;如果用户action实现了ClientCookieAware接口则会自动注入cookie以供用户使用。

 

ClientCookieNotCare和ClientCookieAware接口的定义

 

public interface ClientCookieAware<T extends ClientCookie> {
	public void setClientCookie(T t);
}

public interface ClientCookieNotCare {

}

 

最后别忘了在struts.xml配置一下这个拦截器,并把它一并放入常用的拦截器栈中

 

<interceptor name="cookieInterceptor"	class="com.meidusa.toolkit.web.cookie.ClientCookieInterceptor" />

 

			<interceptor-stack name="cookieDefaultStack">
				<interceptor-ref name="cookieInterceptor" />
				<interceptor-ref name="defaultStack"></interceptor-ref>
			</interceptor-stack>

 

至此,我们应该已经成功实现了一种基于cookie的访问控制方法。

分享到:
评论
3 楼 Bactryki 2013-01-05  
要用什么包呢?
2 楼 whw5452 2011-09-20  
cookieMapping.dtd 这个没有呀。不知道怎么办了。
1 楼 whw5452 2011-09-20  
还是不全呀。

相关推荐

    struts2+spring+Ibatis框架包

    这个“struts2+spring+iBatis框架包”集成了这三个框架,使得开发者能够快速构建基于MVC(Model-View-Controller)模式的Web应用。 Struts2作为MVC框架,负责处理应用程序的控制逻辑。它通过Action类和配置文件定义...

    struts2+spring3+ibatis项目整合案例

    Struts2、Spring3和iBATIS是Java Web开发中常用的三大框架,它们各自负责不同的职责,协同工作可以构建出高效、松耦合的Web应用。在这个“struts2+spring3+ibatis项目整合案例”中,我们将深入探讨这三个框架如何...

    Struts2+Spring2+iBatis2整合的例子

    Struts2、Spring和iBatis是Java Web开发中三个非常重要的框架,它们分别负责表现层、业务层和数据访问层。将这三个框架整合在一起,可以实现MVC(Model-View-Controller)架构,提高应用的灵活性和可维护性。 **...

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis是两种常见的Java Web应用程序集成框架,它们分别基于ORM框架Hibernate和轻量级数据访问框架Ibatis。这两种框架结合Spring,旨在提供一个强大的、可扩展的、易于...

    struts2+spring2+ibatis

    在这个案例中,开发者可能创建了一个简单的Web应用,包括了Struts2的Action类、Spring的Bean配置以及iBatis的数据访问对象(DAO)和SQL映射文件。 在实际的整合过程中,通常会首先配置Struts2的核心配置文件(struts...

    struts2+spring+ibatis+mysql

    "Struts2+Spring+Ibatis+MySQL" 是一个经典的Java Web开发框架组合,用于构建高效、可扩展的企业级应用程序。这个组合集成了强大的MVC(Model-View-Controller)框架Struts2、依赖注入与面向切面编程的Spring框架、...

    Struts2+Spring2+iBatis2+MySQL的完整示例

    开发环境说明 ...本示例完整地结合Struts2+Spring2+iBatis2+MySQL5,演示了一个用户表的增、删、改、查。 想完整学习Struts2+Spring+iBatis的同仁,可以在这个例子中学习或模仿最基本也是最核心的技术要点。

    Struts2+Spring2.5+Ibatis完整增删改查Demo(含全部jar包)

    Struts2、Spring和iBatis是Java Web开发中经典的三大框架,它们分别负责MVC模式中的Action层、业务逻辑层和服务数据访问层。这个"Struts2+Spring2.5+iBatis完整增删改查Demo"提供了一个完整的集成示例,包括所有必要...

    spring+struts2+ibatis整合的jar包

    在Java Web开发中,Spring、Struts2和iBatis是三个非常重要的框架,它们各自在不同的层面上提供了强大的功能。Spring是一个全面的后端应用框架,提供了依赖注入(DI)、面向切面编程(AOP)、事务管理等功能;Struts...

    Struts2+spring+ibatis三大框架整合实例

    Struts2、Spring和iBatis是Java Web开发中常用的三大框架,它们分别负责MVC模式中的Action层、业务逻辑层和服务数据访问层。本文将详细介绍这三个框架如何整合,以及在实际项目中如何运用。 首先,Struts2作为表现...

    struts2+spring2+ibatis集成

    Struts2、Spring2 和 iBatis 是三个在Java Web开发中广泛应用的开源框架,它们分别负责MVC架构中的控制层、服务层和数据访问层。这个集成项目,"SSITest",是为了帮助初学者理解和实践这三大框架的协同工作。 **...

    struts2+spring+ibatis项目实例

    Struts2、Spring和iBatis是Java Web开发中经典的三大框架,它们组合起来可以构建出高效、可维护的企业级应用程序。在这个项目实例中,我们将深入探讨这三个框架如何协同工作,以及它们各自的核心功能。 首先,...

    struts2+spring+ibatis的小demo

    Struts2、Spring和iBatis是Java Web开发中经典的三大框架,它们组合起来可以构建出高效、可维护的企业级应用程序。在这个“struts2+spring+ibatis”的小demo中,我们将深入探讨这三个框架的核心功能以及它们如何协同...

    struts2+spring+ibatis整合项目实例

    Struts2、Spring和iBatis是Java Web开发中常用的三个开源框架,它们各自负责不同的职责,协同工作可以构建出高效、松耦合的Web应用。这个整合项目实例旨在展示如何将这三个框架集成到一起,以实现更强大的功能。 1....

    struts2 + spring + ibatis 整合例子

    Struts2、Spring和iBatis是Java Web开发中常用的三大框架,它们分别负责MVC模式中的Action层、业务逻辑层以及数据访问层。将这三个框架整合在一起,可以构建出高效、灵活且易于维护的Web应用程序。下面我们将详细...

    struts2+spring2+ibatis简单登录例子

    Struts2、Spring2和iBatis是Java Web开发中常用的三大框架,它们结合使用可以构建高效、可扩展的企业级应用程序。在这个简单的登录例子中,我们将深入探讨这三个框架如何协同工作来实现用户身份验证。 首先,Struts...

    struts2+spring+ibatis+oracle+分页搜索+上传附件实例

    综上所述,这个实例展示了如何整合Struts2、Spring、iBatis和Oracle来构建一个完整的Web应用,实现了动态分页搜索和附件上传功能。这种架构具有良好的可扩展性和可维护性,适用于各种中大型企业级项目。开发者可以...

    spring3+struts2+ibatis

    在现代企业级Web应用开发中,Spring、Struts2和Ibatis是常见的三大组件,它们各自负责不同的职责,共同构建出高效、灵活的应用架构。Spring作为全能型的框架,提供依赖注入(DI)和面向切面编程(AOP)等功能;...

    Struts2+Spring2.5+Ibatis2.3架构

    Struts2+Spring2.5+Ibatis2.3架构是一种经典的Java Web开发技术栈,广泛应用于企业级应用系统中。这个架构结合了Struts2的MVC框架、Spring的依赖注入(DI)和面向切面编程(AOP)以及Ibatis的持久层解决方案,为...

    struts2 + spring2.5 + ibatis2.3.4整合包文件

    这个"SSI整合包文件"包含了所有必要的配置文件、库文件、示例代码等,帮助开发者快速搭建起一个基于Struts2、Spring和iBatis的Web应用。开发者只需要根据实际需求调整配置和代码,即可开始开发工作。这样的整合包极...

Global site tag (gtag.js) - Google Analytics