一、需求

  将原来基于url的工程改成使用shiro实现

二、代码

  https://github.com/bjlhx15/shiro.git 中的permission_shiro

三、去除原项目拦截器

  去掉springmvc.xml中配置的LoginInterceptor和PermissionInterceptor拦截器。<mvc:interceptors>

四、pom  

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lhx.shiro</groupId>
<artifactId>permission-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.7.RELEASE</spring.version>
<shiro.version>1.3.2</shiro.version>
</properties>
<dependencies>
<!-- shiro 开始 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency> <!-- shiro 结束 --> <dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency> <dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency> <dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
<version>3.3</version>
</plugin>
<plugin>
<!-- 命令:mvn mybatis-generator:generate -->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

五、web.xml中的shiro filter

  在web系统中,shiro也通过filter进行拦截。filter拦截后将操作权交给spring中配置的filterChain(过虑链儿)shiro提供很多filter。

  在web.xml中配置filter

    <!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean -->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

  securityManager:这个属性是必须的。

  loginUrl:没有登录认证的用户请求将跳转到此地址进行认证,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。

  unauthorizedUrl:没有权限默认跳转的页面。

六、application-shiro.xml

在applicationContext-shiro.xml 中配置web.xml中fitler对应spring容器中的bean

<beans xmlns="http://www.springframework.org/schema/beans"
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.3.xsd"> <!-- web.xml中shiro的filter对应的bean -->
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action"/>
<!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
<property name="successUrl" value="/first.action"/>
<!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 -->
<property name="unauthorizedUrl" value="/refuse.jsp"/>
<!-- 自定义filter配置 -->
<property name="filters">
<map>
<!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中 -->
<entry key="authc" value-ref="formAuthenticationFilter"/>
</map>
</property> <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!-- 对静态资源设置匿名访问 -->
/images/** = anon
/js/** = anon
/styles/** = anon
<!-- 验证码,可匿名访问 -->
/validatecode.jsp = anon <!-- 请求 logout.action地址,shiro去清除session -->
/logout.action = logout
<!--商品查询需要商品查询权限 ,取消url拦截配置,使用注解授权方式 -->
<!-- /items/queryItems.action = perms[item:query] /items/editItems.action
= perms[item:edit] -->
<!-- 配置记住我或认证通过可以访问的地址 -->
/index.jsp = user
/first.action = user
/welcome.jsp = user
<!-- /** = authc 所有url都必须认证通过才可以访问 -->
/** = authc
<!-- /** = anon所有url都可以匿名访问 --> </value>
</property>
</bean> <!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm"/>
<!-- 注入缓存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager"/>
<!-- 记住我 -->
<property name="rememberMeManager" ref="rememberMeManager"/> </bean> <!-- realm -->
<bean id="customRealm" class="com.lhx.ssm.shiro.CustomRealm">
<!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean> <!-- 凭证匹配器 -->
<bean id="credentialsMatcher"
class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="1"/>
</bean> <!-- 缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean> <!-- 会话管理器 -->
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 -->
<property name="globalSessionTimeout" value="600000"/>
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
</bean> <!-- 自定义form认证过虑器 -->
<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
<bean id="formAuthenticationFilter" class="com.lhx.ssm.shiro.CustomFormAuthenticationFilter">
<!-- 表单中账号的input名称 -->
<property name="usernameParam" value="username"/>
<!-- 表单中密码的input名称 -->
<property name="passwordParam" value="password"/>
<!-- 记住我input的名称 -->
<property name="rememberMeParam" value="rememberMe"/>
</bean> <!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- 记住我cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- rememberMe是cookie的名字 -->
<constructor-arg value="rememberMe"/>
<!-- 记住我cookie生效时间30天 -->
<property name="maxAge" value="2592000"/>
</bean>
</beans>

七、自定义realm

此realm先不从数据库查询权限数据,当前需要先将shiro整合完成,在上边章节定义的realm基础上修改。

public class CustomRealm extends AuthorizingRealm {

    @Override
public String getName() {
return "customRealm";
} // 支持什么类型的token
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
} // 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException { // 从token中 获取用户身份信息
String username = (String) token.getPrincipal();
// 拿username从数据库中查询
// ....
// 如果查询不到则返回null
if (!username.equals("zhang")) {// 这里模拟查询不到
return null;
} // 获取从数据库查询出来的用户密码
String password = "123";// 这里使用静态数据模拟。。 // 根据用户id从数据库取出菜单
//...先用静态数据
List<SysPermission> menus = new ArrayList<SysPermission>();; SysPermission sysPermission_1 = new SysPermission();
sysPermission_1.setName("商品管理");
sysPermission_1.setUrl("/item/queryItem.action");
SysPermission sysPermission_2 = new SysPermission();
sysPermission_2.setName("用户管理");
sysPermission_2.setUrl("/user/query.action"); menus.add(sysPermission_1);
menus.add(sysPermission_2); // 构建用户身份信息
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(username);
activeUser.setUsername(username);
activeUser.setUsercode(username);
activeUser.setMenus(menus); // 返回认证信息由父类AuthenticatingRealm进行认证
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password, getName()); return simpleAuthenticationInfo;
} // 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// 获取身份信息
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
//用户id
String userid = activeUser.getUserid();
// 根据用户id从数据库中查询权限数据
// ....这里使用静态数据模拟
List<String> permissions = new ArrayList<String>();
permissions.add("item:query");
permissions.add("item:update"); // 将权限信息封闭为AuthorizationInfo SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (String permission : permissions) {
simpleAuthorizationInfo.addStringPermission(permission);
} return simpleAuthorizationInfo;
} }

八、静态资源

对静态资源设置逆名访问:

修改applicationContext-shiro.xml:

                <!-- 对静态资源设置匿名访问 -->
/images/** = anon
/js/** = anon
/styles/** = anon

九、登录

9.1、原理

  使用FormAuthenticationFilter过虑器实现 ,原理如下:

    1、将用户没有认证时,请求loginurl进行认证,用户身份和用户密码提交数据到loginurl

    2、FormAuthenticationFilter拦截住取出request中的username和password(两个参数名称是可以配置的)

    3、FormAuthenticationFilter调用realm传入一个token(username和password)

    4、realm认证时根据username查询用户信息(在Activeuser中存储,包括 userid、usercode、username、menus)。

      如果查询不到,realm返回null,FormAuthenticationFilter向request域中填充一个参数(记录了异常信息)

9.2、登录页面

  由于FormAuthenticationFilter的用户身份和密码的input的默认值(username和password),修改页面的账号和密码 的input的名称为username和password

9.3、登录代码的实现  

    // 登陆提交地址,和applicationContext-shiro.xml中配置的loginurl一致
@RequestMapping("login")
public String login(HttpServletRequest request) throws Exception {
// 如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
// 根据shiro返回的异常类路径判断,抛出指定异常信息
if (exceptionClassName != null) {
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
// 最终会抛给异常处理器
throw new CustomException("账号不存在");
} else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
throw new CustomException("用户名/密码错误");
} else if ("randomCodeError".equals(exceptionClassName)) {
throw new CustomException("验证码错误 ");
} else {
throw new Exception();// 最终在异常处理器生成未知错误
}
}
// 此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
// 登陆失败还到login页面
return "login";
}

9.4、认证拦截器过滤

在applicationContext-shiro.xml中配置:

                <!-- /** = authc 所有url都必须认证通过才可以访问 -->
/** = authc

十、退出

  由于使用shiro的sessionManager,不用开发退出功能,使用shiro的logout拦截器即可

  在applicationContext-shiro.xml配置LogoutFilter:

<!-- 退出拦截,请求logout.action执行退出操作 -->
/logout.action = logout

可以删除原来的logout的controller方法代码。

十一、无权限refuse.jsp

在applicationContext-shiro.xml配置LogoutFilter:

        <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 -->
<property name="unauthorizedUrl" value="/refuse.jsp"/>

十二、shiro过滤器总结

过滤器简称

对应的java类

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port

org.apache.shiro.web.filter.authz.PortFilter

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl

org.apache.shiro.web.filter.authz.SslFilter

user

org.apache.shiro.web.filter.authc.UserFilter

logout

org.apache.shiro.web.filter.authc.LogoutFilter

anon:例子/admins/**=anon 没有参数,表示可以匿名使用。

authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数

roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。

perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。

port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。

authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查

注:

anon,authcBasic,auchc,user是认证过滤器,

perms,roles,ssl,rest,port是授权过滤器

007-shiro与spring web项目整合【一】基础搭建的更多相关文章

  1. 010-shiro与spring web项目整合【四】缓存Ehcache

    一.Ehcache shiro每次授权都会通过realm获取权限信息,为了提高访问速度需要添加缓存,第一次从realm中读取权限数据,之后不再读取,这里Shiro和Ehcache整合. 1.添加Ehc ...

  2. 008-shiro与spring web项目整合【二】认证、授权、session管理

    一.认证 1.添加凭证匹配器 添加凭证匹配器实现md5加密校验. 修改applicationContext-shiro.xml: <!-- realm --> <bean id=&q ...

  3. 009-shiro与spring web项目整合【三】验证码、记住我

    一.验证码 1.自定义FormAuthenticationFilter 需要在验证账号和名称之前校验验证码 /** * * <p>Title: CustomFormAuthenticati ...

  4. (转)shiro权限框架详解06-shiro与web项目整合(下)

    http://blog.csdn.net/facekbook/article/details/54962975 shiro和web项目整合,实现类似真实项目的应用 web项目中认证 web项目中授权 ...

  5. (转) shiro权限框架详解06-shiro与web项目整合(上)

    http://blog.csdn.net/facekbook/article/details/54947730 shiro和web项目整合,实现类似真实项目的应用 本文中使用的项目架构是springM ...

  6. spring web项目中整合netty, akka

    spring web项目中整合netty, akka 本身的web项目仍然使用tomcat/jetty8080端口, 在org.springframework.beans.factory.Initia ...

  7. Spring与Web项目整合的原理

    引言: 在刚开始我们接触IOC时,我们加载并启用SpringIOC是通过如下代码手动加载 applicationContext.xml 文件,new出context对象,完成Bean的创建和属性的注入 ...

  8. 实战突击: Java Web项目整合开发(PDF)

    实战突击:  Java  Web项目整合开发(PDF)

  9. 【工具】Spring项目转化Spring Web项目插件

    前言 源于前一篇博文中提到,将Spring项目转化为Spring Web项目,发现添加项目文件和修改pom.xml文件等都是手动完成的,心想着开发一个Idea插件来自动化完成上面的过程,实现一键转化. ...

随机推荐

  1. python 同时遍历多个变量

    最近在用python的时候,用到遍历多个变量: import sys import math F58=11491939491.7 F=[11429229079.7,11374540753.7,1132 ...

  2. mysql用事务插入数据

    Connection conn = null; try { conn = queryRunner.getDataSource().getConnection(); ConnectionUtils.se ...

  3. plsql 查询到别的用户下面的表

    原因:勾选了"Allow multiple connections" 解决方法:打开"Tools"下面的"Perference",里面有个& ...

  4. iOS-回收键盘的几种方法

    在开发过程中,为了实现点击屏幕其它位置收起键盘的目的,我们使用过许多的方法. 如果是在UIViewController中收起键盘,除了通过调用控件的resignFirstResponder方法外,还有 ...

  5. PHP——通过下拉列表选择时间(转)

    实现效果: 主页代码: <script type="text/javascript" src="jquery.min.js"></script ...

  6. display:flex和display:box布局浏览器兼容性分析

    display:flex和display:box都可用于弹性布局,不同的是display:box是2009年的命名,已经过时,用的时候需要加上前缀:display:flex是2012年之后的命名.在实 ...

  7. 虚拟机Ubuntu系统下kaldi安装与编译简介

    kaldi官网:http://www.kaldi-asr.org/doc/index.html 在http://github.com/kaldi-asr/kaldi中直接下载kaldi的zip包,没有 ...

  8. ubuntu14.04安装pycurl

    一. 安装依赖项 sudo apt-get install libcurl4-gnutls-dev 二. 安装pycurl pip install pycurl 三. 检验是否安装成功 进入pytho ...

  9. 第二百四十二节,Bootstrap列表组面板和嵌入组件

    Bootstrap列表组面板和嵌入组件 学习要点: 1.列表组组件 2.面板组件 3.响应式嵌入组件 本节课我们主要学习一下 Bootstrap 的三个组件功能:列表组组件.面板组件. 响应 式嵌入组 ...

  10. java强软弱虚引用详解(转载)

    转载自:http://zhangjunhd.blog.51cto.com/113473/53092/ ava:对象的强.软.弱和虚引用 2007-12-01 17:20:20 标签:Java 软引用  ...