【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(四)
SpringSecurity(1)
其实啊,这部分我是最不想写的,因为最麻烦的也是这部分,真的是非常非常的麻烦。关于SpringSecurity的配置,让我折腾了好半天,网上的配置方式一大把,但总有一些功能不完全,版本不是最新等等的问题在,所以几乎没有一个教程,是可以整个贯通的。当然我的意思不是说那些不好,那些也不错,但就对于我来说,还不够全面。另外,SpringSecurity的替代品是shiro,据说,两者的区别在于,前者涵盖的范围更广,但前者也相对学习成本更高。又因为SpringSecurity是Spring家族的成员之一,所以在Spring框架下应用的话,可以做到非常高度的自定义,算是非常灵活的安全框架,就是配置起来,真心复杂。
SpringSecurity的配置文件
目录:resource/config/spring,文件名:applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd"> <!--过滤资源 start-->
<!--不进行拦截的静态资源-->
<sec:http pattern="/css/*" security="none"/>
<sec:http pattern="/images/*" security="none"/>
<sec:http pattern="/images/**" security="none"/>
<sec:http pattern="/js/*" security="none"/>
<sec:http pattern="/fonts/*" security="none"/>
<!--不进行拦截的页面-->
<sec:http pattern="/WEB-INF/views/index.jsp" security="none"/>
<!--<sec:http pattern="WEB-INF/views/login.jsp" security="none"/>-->
<!--过滤资源 end--> <!--权限配置及自定义登录界面 start-->
<sec:http auto-config="true" access-decision-manager-ref="accessDecisionManager">
<sec:form-login
login-page="/user/login"
login-processing-url="/login.do"
authentication-success-handler-ref="loginController"
authentication-failure-handler-ref="loginController"/>
<!--登出-->
<sec:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/"/>
<!--session管理及单点登录-->
<sec:session-management session-authentication-strategy-ref="concurrentSessionControlStrategy"/>
<!--资源拦截器配置-->
<sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
<sec:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER"/>
</sec:http> <!--自定义验证结果控制器-->
<bean id="loginController" class="com.magic.rent.controller.LoginAuthenticationController">
<property name="successURL" value="/user/home"/>
<property name="failURL" value="/user/login"/>
<property name="attrName" value="loginResult"/>
<property name="byForward" value="false"/>
<property name="userInfo" value="userInfo"/>
</bean> <sec:authentication-manager alias="myAuthenticationManager">
<sec:authentication-provider ref="daoAuthenticationProvider"/>
</sec:authentication-manager> <!--权限查询服务-->
<bean id="cachingUserDetailsService"
class="org.springframework.security.config.authentication.CachingUserDetailsService">
<constructor-arg name="delegate" ref="webUserDetailsService"/>
<property name="userCache">
<bean class="org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache">
<property name="cache" ref="userEhCacheFactory"/>
</bean>
</property>
</bean> <bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="messageSource" ref="messageSource"/>
<property name="passwordEncoder" ref="messageDigestPasswordEncoder"/>
<property name="userDetailsService" ref="cachingUserDetailsService"/>
<property name="saltSource" ref="saltSource"/>
<property name="hideUserNotFoundExceptions" value="false"/>
</bean> <!--MD5加密盐值-->
<bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource">
<property name="userPropertyToUse" value="username"/>
</bean> <!--决策管理器 start-->
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg name="decisionVoters">
<list>
<ref bean="roleVoter"/>
<ref bean="authenticatedVoter"/>
</list>
</constructor-arg>
<property name="messageSource" ref="messageSource"/>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
<property name="rolePrefix" value="ROLE_"/>
</bean>
<bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>
<!--决策管理器 end--> <!--资源拦截器 start-->
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="authenticationManager" ref="myAuthenticationManager"/>
<property name="securityMetadataSource" ref="resourceSecurityMetadataSource"/>
</bean> <!--方法拦截器 start-->
<bean id="methodSecurityInterceptor"
class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="authenticationManager" ref="myAuthenticationManager"/>
<property name="securityMetadataSource" ref="methodSecurityMetadataSource"/>
</bean>
<aop:config>
<aop:advisor advice-ref="methodSecurityInterceptor" pointcut="execution(* com.magic.rent.service.*.*(..))"
order="1"/>
</aop:config>
<!--方法拦截器 end--> <!--session管理器 start-->
<bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
<constructor-arg name="expiredUrl" value="/user/timeout"/>
</bean> <bean id="concurrentSessionControlStrategy"
class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
<property name="maximumSessions" value="1"/>
</bean> <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
<!--session管理器 end-->
</beans>
applicationContext-security.xml
来吧,简单的,从头到尾的解释一下。
首先呢,最先看到的应该是过滤资源的配置:
<!--过滤资源 start-->
<!--不进行拦截的静态资源-->
<sec:http pattern="/css/*" security="none"/>
<sec:http pattern="/images/*" security="none"/>
<sec:http pattern="/images/**" security="none"/>
<sec:http pattern="/js/*" security="none"/>
<sec:http pattern="/fonts/*" security="none"/>
<!--不进行拦截的页面-->
<sec:http pattern="/WEB-INF/views/index.jsp" security="none"/>
<!--过滤资源 end-->
这些pattern意味着这些资源,不进行安全过滤,即在访问这些资源的时候,不需要进行Security的权限验证,举一个例子:在以“webapp”为根目录的情况下,css文件夹下的任何文件被访问将不进行安全验证,即任何用户都可以毫无顾忌的直接访问这些资源。
接下来的配置,相当重要,是整个框架的核心部分,如果不理解这部分,将无法好好使用这个框架。
<!--权限配置及自定义登录界面 start-->
<sec:http auto-config="true" access-decision-manager-ref="accessDecisionManager">
<sec:form-login
login-page="/user/login"
login-processing-url="/login.do"
authentication-success-handler-ref="loginController"
authentication-failure-handler-ref="loginController"/>
<!--登出-->
<sec:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/"/>
<!--session管理及单点登录-->
<sec:session-management session-authentication-strategy-ref="concurrentSessionControlStrategy"/>
<!--资源拦截器配置-->
<sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
<sec:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER"/>
</sec:http>
首先可以看到,“access-decision-manager-ref”是自定义框架的决策管理器(1),这个决策管理器是比如,当一个资源,被配置给3个不同的权限可以访问的时候,你可以决定,是只要拥有三个中的一个权限,就能访问资源,还是至少拥有2个权限,还是必须满足三个权限都拥有的情况下,才能访问资源。这就是决策管理器,就是制定放行规则。所以我们紧接着就要配置它了,这个决策管理器的配置,是这样的:
<!--决策管理器 start-->
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg name="decisionVoters">
<list>
<ref bean="roleVoter"/>
<ref bean="authenticatedVoter"/>
</list>
</constructor-arg>
<property name="messageSource" ref="messageSource"/>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
<property name="rolePrefix" value="ROLE_"/>
</bean>
<bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>
<!--决策管理器 end-->
值得一提的是,bean:roleVoter中,有一个属性是”rolePrefix“,这个是用于设置角色前缀的。什么是角色前缀呢?先要解释什么是角色。SpringSecurity这个框架,默认的规则是以角色来判断是否有访问权限的,当然这并不符合我们的实际情况,我们使用的时候,更喜欢的是把角色更细化一层,比如,一个角色,具有多个“权限”,然后根据“权限”来判断是否有访问资源的资格。如果有资格,则访问,没资格,则返回403无权访问错误页面(当然默认的403有点丑,大部分情况我们都会对404、500、403这些常见的错误页面来去替换成我们自己编写的页面,这个回头再说。)。而角色权限,就是说,当系统读取到的一个字符串,判断它是否为一个用于表示角色的字符串,就是根据这个前缀来判断的,如果有心得朋友,可以查看“RoleVoter”这个类,可以发现,其实系统对rolePrefix设置了一个默认值,就是“ROLE_”,而我们在这里配置,只是我为了说明这个问题,当然我们可以通过配置Bean来修改这个前缀,不过我个人觉得这个“ROLE_”挺好的,就采用原有的了。那这边设置了前缀,就意味着,我们以后将角色存在数据库当中的时候,就必须给我们的角色定义这个前缀,比如我在数据库中存一个角色为管理员:ROLE_ADMIN。如果我们没有以约定好的前缀来定义角色,系统就会不识别,然后直接报无权限访问。这个也可以在RoleVoter这个类中的“supports”方法中得到查证。顺便说一下,框架会先调用这个supports方法,来校验是否是符合角色前缀的定义规则,如果不符合,根本都不进入后面的对比阶段,直接返回false,然后就被判定为无权访问了。可能就有朋友会想知道从哪里看出,先执行supports这个方法的,我在测试的时候,Debug了整个流程,但是现在已经不记得了,如果有想弄清楚的朋友,可以自行Debug,反正IDEA的Debug有记录整个执行过程,所以只需要在这个supports方法上打一个断点,然后查看上一个步骤就能找到调用的地方。
接着我们继续往下配置文件的下面看,
<sec:form-login
login-page="/user/login"
login-processing-url="/login.do"
authentication-success-handler-ref="loginController"
authentication-failure-handler-ref="loginController"/>
这里呢,定义了前台页面中,登录表单的一些规则,
- login-page:这个参数,配置的是登录页面的访问地址,因为我们是使用了SpringMVC,所以我自定义了一个Controller用于访问登录页面,而地址就是“/user/login”:
其实就是很简单的指向了login.jsp这个页面,也没有做什么其他的处理。- login-processing-url:这个参数呢,是当你在jsp或者html页面中,设计登录的表单<form>标签时,其中action元素的地址,就是你配置的这个参数,比如:
- authentication-success-handler-ref:这个参数,是定义一个当登录验证成功时要执行操作的控制器。
- authentication-failure-handler-ref:这个参数,是定一个,当登录验证失败时,要执行操作的控制器。
这两个参数,所对应的控制器,我为了简略,就把它们合并成为一个,这个控制器怎么写呢?实际很简单,登录验证成功的控制器呢,就是一个普通的java类,去实现AuthenticationSuccessHandler这个接口的方法“onAuthenticationSuccess”,而登录验证失败呢,就是实现AuthenticationFailureHandler的接口“anAuthenticationFailure”。我的实现类:
package com.magic.rent.controller; import com.magic.rent.pojo.SysUsers;
import com.magic.rent.service.IUserService;
import com.magic.rent.util.HttpUtil;
import com.magic.rent.util.JsonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.Locale; public class LoginAuthenticationController implements AuthenticationSuccessHandler, AuthenticationFailureHandler, InitializingBean { @Autowired
private IUserService iUserService; private String successURL; private String failURL; private boolean byForward = false; private String AttrName; private String userInfo; private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); private static Logger logger = LoggerFactory.getLogger(LoginAuthenticationController.class); public void setSuccessURL(String successURL) {
this.successURL = successURL;
} public void setFailURL(String failURL) {
this.failURL = failURL;
} public void setByForward(boolean byForward) {
this.byForward = byForward;
} public void setAttrName(String attrName) {
AttrName = attrName;
} public void setUserInfo(String userInfo) {
this.userInfo = userInfo;
} @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
SysUsers users;
JsonResult jsonResult;
try {
users = (SysUsers) authentication.getPrincipal();
Date date = new Date();
users.setLastLogin(date);
users.setLoginIp(HttpUtil.getIP(request));
try {
iUserService.updateUserLoginInfo(users);
} catch (DataAccessException e) {
logger.error("登录异常:保存登录数据失败!", e);
}
} catch (Exception e) {
jsonResult = JsonResult.error("用户登录信息保存失败!");
logger.error("登录异常:用户登录信息保存失败!", e);
request.getSession().setAttribute(AttrName, jsonResult);
return;
}
jsonResult = JsonResult.success("登录验证成功!", users);
request.getSession().setAttribute(userInfo, jsonResult);
httpReturn(request, response, true);
} @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
JsonResult jsonResult;
logger.info("登录失败:请求IP地址[{}];失败原因:{};", HttpUtil.getIP(request), exception.getMessage());
jsonResult = JsonResult.error(exception.getMessage());
request.getSession().setAttribute(AttrName, jsonResult);
httpReturn(request, response, false);
} public void afterPropertiesSet() throws Exception {
if (StringUtils.isEmpty(successURL))
throw new ExceptionInInitializerError("成功后跳转的地址未设置!");
if (StringUtils.isEmpty(failURL))
throw new ExceptionInInitializerError("失败后跳转的地址未设置!");
if (StringUtils.isEmpty(AttrName))
throw new ExceptionInInitializerError("Attr的Key值未设置!");
} private void httpReturn(HttpServletRequest request, HttpServletResponse response, boolean success) throws IOException, ServletException {
if (success) {
if (this.byForward) {
logger.info("登录成功:Forwarding to [{}]", successURL);
request.getRequestDispatcher(this.successURL).forward(request, response);
} else {
logger.info("登录成功:Redirecting to [{}]", successURL);
this.redirectStrategy.sendRedirect(request, response, this.successURL);
}
} else {
if (this.byForward) {
logger.info("登录失败:Forwarding to [{}]", failURL);
request.getRequestDispatcher(this.failURL).forward(request, response);
} else {
logger.info("登录失败:Redirecting to [{}]", failURL);
this.redirectStrategy.sendRedirect(request, response, this.failURL);
}
} }
}
估计还是需要简单解释一下,因为这个类我最终也是在Spring中装配的,所以一些字段我也就没有定义,只是做了get和set方法,等待配置。为了防止漏了这些字段的配置,所以我把这个类又另外实现了InitializingBean接口的afterPropertiesSet方法,这个方法可以在Spring框架启动,生产Bean对象对其属性进行装配的时候执行,然后我在这个方法中,对所有需要配置的属性,进行了非空验证。其实这个类的作用很简单,就是登陆成功后,保存登陆信息,然后跳转到登陆后的界面。对了,不能忘了这个LoginAuthenticationController的配置文件了:
<!--自定义验证结果控制器-->
<bean id="loginController"class="com.magic.rent.controller.LoginAuthenticationController">
<property name="successURL" value="/user/home"/>
<property name="failURL" value="/user/login"/>
<property name="attrName" value="loginResult"/>
<property name="byForward" value="false"/>
<property name="userInfo" value="userInfo"/>
</bean>
这配置应该算浅显易懂把,因为使用SpringMVC,所以每个地址其实都是SpringMVC的映射地址。
哦读了!上面那个类,有一个对象,就是JsonResult,这是我用于传输到前端的一个包装工具。
package com.magic.rent.util; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.io.Serializable; /**
* Created by wuxinzhe on 16/9/20.
*/
public class JsonResult implements Serializable { private static final long serialVersionUID = 8134245754393400511L; private boolean status = true;
private String message;
private Object data;
private static Logger logger = LoggerFactory.getLogger(JsonResult.class); public JsonResult() {
} public JsonResult(Object data) {
this.data = data;
} public boolean getStatus() {
return status;
} public JsonResult setStatus(boolean status) {
this.status = status;
return this;
} public String getMessage() {
return message;
} public JsonResult setMessage(String message) {
this.message = message;
return this;
} public Object getData() {
return data;
} public JsonResult setData(Object data) {
this.data = data;
return this;
} public static JsonResult success() {
return new JsonResult().setStatus(true);
} public static JsonResult success(Object data) {
JsonResult jsonResult = success().setData(data);
logger.info(jsonResult.toString());
return jsonResult;
} public static JsonResult success(String message, Object data) {
JsonResult jsonResult = success().setData(data).setMessage(message);
logger.info(jsonResult.toString());
return jsonResult;
} public static JsonResult error() {
return new JsonResult().setStatus(false);
} public static JsonResult error(String message) {
JsonResult jsonResult = error().setMessage(message);
logger.info(jsonResult.toString());
return jsonResult;
} @Override
public String toString() {
return "JsonResult{" +
"status=" + status +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
这个类还是跟朋友借鉴的呢,之前我也没有做过这种,不过这个说实话,真的很有用。
【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(四)的更多相关文章
- 【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(三)
Spring+SpringMVC MVC呢,现在似乎越来越流行使用SpringMVC框架,我自己用的感觉,是非常好,确实很舒服,配置一开始是麻烦了一点点,但是后续的开发真的是很清爽! SpringMV ...
- 【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(前言)
一直希望能够搭建一个完整的,基础Web框架,方便日后接一些外快的时候,能够省时省力,终于花了一周的时间,把这个东西搞定了.特此写下此博客,一来是纪念,二来是希望能够为别人提供方便.顺带说一下,恩,组合 ...
- Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(前言)
简单介绍一下,本框架的基本功能点: Spring:整个框架的主体部分,这个自不用说. SpringMVC:MVC部分我还是比较喜欢Spring的. MyBatis:选型的时候选择这个ORM主要也是考虑 ...
- 【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(五)
SpringSecurity(2) 好久没有写了,之前只写了一半,我是一边开发一边写Blog一边上班,所以真心没有那么多时间来维护Blog,项目已经开发到编写逻辑及页面部分了,框架基本上已经搭建好不会 ...
- 【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(一)
Spring+MyBatis 首先要搭建的是Spring+MyBatis的整合框架,毕竟Spring是整个Web框架的核心部位,而数据库操作是一切测试的基础嘛. 目录结构 ━java ┣ contro ...
- 【JavaWeb】Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(二)
Log4j 这个东西,大家都熟悉,就简单的介绍一下,算是一个抛砖引玉,因为我自己在Log日志的搭建方面,没有什么经验,但这东西确实是非常重要的,日后调Bug没有它基本不可能,如果有朋友有什么比较好的L ...
- 【JavaWeb】SSM+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(六)
Showings 我个人的项目,当前不断地在更新. 我希望做成一个好项目,同时,也是在锻炼自己的技术. 在项目中发现问题,学习知识,是比较可取的一条路子. 这样学习到的知识,虽然分散,但是都很实用,而 ...
- Spring+SpringMVC+Mybatis+MAVEN+Eclipse+项目完整环境搭建
1.新建一个Maven项目,创建父项目. 2.创建子项目模块 3.创建javaWeb项目 4.创建后的项目目录结构 5.Maven文件配置 parent父项目pom.xml文件配置 <?xml ...
- Quartz学习——SSMM(Spring+SpringMVC+Mybatis+Mysql)和Quartz集成详解(四)
当任何时候觉你得难受了,其实你的大脑是在进化,当任何时候你觉得轻松,其实都在使用以前的坏习惯. 通过前面的学习,你可能大致了解了Quartz,本篇博文为你打开学习SSMM+Quartz的旅程!欢迎上车 ...
随机推荐
- 几句话就能让你理解:this、闭包、原型链
以下是个人对这三个老大难的总结(最近一直在学习原生JS,翻了不少书,不少文档,虽然还是新手,但我会继续坚持走我自己的路) 原型链 所有对象都是基于Object.prototype,Object.pro ...
- ES6笔记(一):ES6所改良的javascript“缺陷”
块级作用域 ES5没有块级作用域,只有全局作用域和函数作用域,由于这一点,变量的作用域甚广,所以一进入函数就要马上将它创建出来.这就造成了所谓的变量提升. ES5的"变量提升"这一 ...
- JavaScript和jQuery的类型判断
此博文为原创,转载请注明出处! 对于类型的判断,JavaScript用typeof来进行. 栗子: console.log(typeof null); //object console.log(typ ...
- IOS之Objective-C学习 策略模式
对于策略模式,我个人理解策略模式就是对各种规则的一种封装的方法,而不仅仅是对算法的封装与调用而已.与工厂模式中简单工厂有点类似,但是比简单工厂更有耦合度,因为策略模式以相同的方法调用所有的规则,减少了 ...
- swift学习笔记1——基础部分
之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习.总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询 ...
- currentStyle与getComputedStyle应用
getComputedStyle获取的是计算机(浏览器)计算后的样式,但是不兼容IE6.7.8(主要用于非IE浏览器) currentStyle方法兼容IE6.7.8,但是不兼容标准浏览器(主要用于I ...
- 使用Java实现单线程模式
我们都知道单例模式,有很多种实现方法.今天我们实现一个单线程实例模式,也就是说只能实例化该类的一个线程来运行,不允许有该类的多个线程实例存在.直接上代码: public class Singleton ...
- sys.dm_os_waiting_tasks 引发的疑问(上)
很多人在查看SQL语句等待的时候都是通过sys.dm_exec_requests查看,等待类型也是通过wait_type得出,sys.dm_os_waiting_tasks也可以看到session的等 ...
- AngularJS(1)
AngularJS(1) 在讲正题之前,先说一下有关angular简介方面的信息: 1. angularJS 诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优 ...
- JAVA设计模式之3-抽象工厂模式
书接上文,简单工厂模式解决的是可以枚举种类的类的问题,但是带来了高耦合的问题,并且对类系列繁多无从下手,那么我们想起了一种方法,那就是抽象类,建一个抽象工厂,抽象工厂里的方法都是根据系列类的差异区分出 ...