文章目录
- 1. 缓存
- 2. Java的Web缓存
1. 缓存
Web浏览器会缓存页面和图片,将资源缓存在本地,每次需要时会从缓存中重新加载,而不是每次都请求远程服务器。一些HTTP首部(包括Expires和Cache-Control)可以控制首部。默认情况下,一般认为使用GET通过HTTP访问的页面都可以缓存,也应当缓存。使用HTTPS或POST访问的页面通常不应当缓存,不过HTTP可以对此进行调整。
- Expires首部(主要针对HTTP 1.0)指示可以缓存这个资源表示,直到时间为止
- Cache-control首部(HTTP 1.1)提供了细粒度的缓存策略
- max-age=[seconds]:从现在直到缓存项过期之前的秒数
- s-maxage=[seconds]:从现在起,知道缓存项在共享缓存中过期之前的秒数
- public:可以缓存一个经过认证的响应。否则已认证的响应不会缓存
- private:仅单个缓存可以保存响应,而共享缓存不应保存
- no-cache:客户端每次访问时要用一个Etag或Last-modified首部重新验证响应的状态
- no-store:不管怎么样都不缓存
如果Cache-control和Expires首部都出现,Cache-control会覆盖Expires。服务器可以在一个首部中发送多个Cache-contrll首部,只要它们没有冲突
- Last-modified:表示资源最后一次修改的时间,只有本地缓存的时间早于这个时间时才会执行get来获得资源
- Etag首部(HTTP 1. 1)是资源改变时这个资源的唯一标识符,只有当本地缓存的副本有一个不同的Etag时,它才会执行GET来获取资源。下面代码展示了Java解析和查询Cache-control首部的过程:
public class QuizCardBuilder {
public static void main(String[] args) throws IOException {
URL u = new URL("\n" +
"https://hm.baidu.com/hm.gif?hca=36FB9E633015756B&cc=1&ck=1&cl=24-bit&ds=1920x1080&vl=1001&ep=7111%2C2543&et=10&ja=0&ln=zh-cn&lo=0<=1684721081&rnd=108138644&si=6bcd52f51e9b3dce32bec4a3997715ac&su=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DQUDdVYc4ExLePOefaclpkkQyDsywEmMmz4W37KJFhafJByp_lyapL-JzapNuyFy2rK_qZJfRbkQ6CuWmXotAc4dSqZuZENMBepfFjFjxwdq%26wd%3D%26eqid%3D95614ab00000085400000005646f571f&v=1.3.0&lv=3&sn=52924&r=0&ww=705&p=islogin*1*1!isonline*1*1!isvip*0*1!uid_*qq_43456605*1!view_h_*914&u=https%3A%2F%2Fblog.csdn.net%2Fqq_44231797%2Farticle%2Fdetails%2F121343892 ");
String header="";
URLConnection ur=u.openConnection();
for (int i = 1; ; i++) {
if(ur.getHeaderField(i)==null)break;
String myheader=ur.getHeaderFieldKey(i)+": "+ur.getHeaderField(i);
System.out.println(myheader);
header+=myheader;
}
CacheControl cacheControl=new CacheControl(header);
System.out.println("________________________________________________________");
System.out.println(cacheControl);
}
}
class CacheControl{
private Date MaxAge=null;
private Date sMaxAge=null;
private boolean mustRevalidate=false;
private boolean noCache=false;
private boolean noStore=false;
private boolean proxyRevalidate=false;
private boolean publicCache=false;
private boolean privateCache=false;
public Date getMaxAge() {
return MaxAge;
}
public void setMaxAge(Date maxAge) {
MaxAge = maxAge;
}
public Date getsMaxAge() {
return sMaxAge;
}
public void setsMaxAge(Date sMaxAge) {
this.sMaxAge = sMaxAge;
}
public boolean isMustRevalidate() {
return mustRevalidate;
}
public void setMustRevalidate(boolean mustRevalidate) {
this.mustRevalidate = mustRevalidate;
}
public boolean isNoCache() {
return noCache;
}
public void setNoCache(boolean noCache) {
this.noCache = noCache;
}
public boolean isNoStore() {
return noStore;
}
public void setNoStore(boolean noStore) {
this.noStore = noStore;
}
public boolean isProxyRevalidate() {
return proxyRevalidate;
}
public void setProxyRevalidate(boolean proxyRevalidate) {
this.proxyRevalidate = proxyRevalidate;
}
public boolean isPublicCache() {
return publicCache;
}
public void setPublicCache(boolean publicCache) {
this.publicCache = publicCache;
}
public boolean isPrivateCache() {
return privateCache;
}
public void setPrivateCache(boolean privateCache) {
this.privateCache = privateCache;
}
public CacheControl(String s){
if(s==null|| !s.contains(":")){
return;
}
//trim() 函数移除字符串两侧的空白字符或其他预定义字符
String value=s.split(":")[1].trim();
String[] components=value.split(",");
Date now=new Date();
for(String component: components){
try {
//表示中国时间
component=component.trim().toLowerCase(Locale.CHINA);
if(component.startsWith("max-age=")){
int secondsInTheFuture=Integer.parseInt(component.substring(8));
MaxAge=new Date(now.getTime()+1000*secondsInTheFuture);
}else if(component.startsWith("s-maxage=")){
int secondsInTheFuture=Integer.parseInt(component.substring(8));
sMaxAge=new Date(now.getTime()+1000*secondsInTheFuture);
}else if(component.equals("must-revalidate")){
mustRevalidate=true;
}else if(component.equals("proxy-revalidate")){
proxyRevalidate=true;
}else if(component.equals("no-cache")){
noCache=true;
}else if(component.equals("public")){
publicCache=true;
}else if(component.equals("private")){
privateCache=true;
}
}catch (RuntimeException e){
continue;
}
}
}
@Override
public String toString() {
return "CacheControl{" +
"MaxAge=" + MaxAge +
", sMaxAge=" + sMaxAge +
", mustRevalidate=" + mustRevalidate +
", noCache=" + noCache +
", noStore=" + noStore +
", proxyRevalidate=" + proxyRevalidate +
", publicCache=" + publicCache +
", privateCache=" + privateCache +
'}';
}
}
2. Java的Web缓存
默认情况下Java并不完成缓存,要安装URL类使用的系统级缓存,需有有:
- ResponseCache的一个具体子类
- CacheRequest的一个具体子类
- CacheResponse的一个具体子类
要安装你的ResponseCache子类来处理你的CacheRequest和CacheResponse子类,需要把它传递到静态方法ResponseCache.setDefault(),这会把这个缓存对象安装为系统默认缓存,Java虚拟机只支持一个共享缓存。一旦安装了缓存,只要系统尝试加载一个新的URL,它首先会从这个缓存中查找,如果缓存返回了所要的内容,URLConnection就不需要与远程服务器连接了。不过,如果所请求的数据不在缓存中,协议处理器将从远程服务器下载相应的数据。完成之后,它会把这个响应放在缓存里面,下一次就可以直接查缓存了。
在Java中,协议处理器(Protocol Handler)是一种用于处理特定协议的组件。它允许Java应用程序通过各种网络协议(如HTTP、HTTPS、FTP等)与远程资源进行通信。Java的协议处理器是通过Java的URL(统一资源定位符)类和URLConnection(统一资源链接)类来实现的。URL类表示一个统一资源定位符,而URLConnection类用于建立与指定URL之间的连接,并可以进行读取和写入操作。当Java应用程序需要与远程资源进行通信时,可以使用URL类创建一个URL对象,然后通过openConnection()方法获取与该URL的连接。根据URL的协议,Java会选择适当的协议处理器来处理连接。例如,如果URL的协议是HTTP,Java将使用内置的HTTP协议处理器来处理连接。Java的协议处理器提供了一组标准的方法和接口,用于发送请求、接收响应、处理Cookie、处理重定向等常见的协议操作。同时,它也支持自定义的协议处理器,允许开发人员根据需要实现自定义的协议处理逻辑。总之,Java的协议处理器是一种用于处理特定协议的组件,它允许Java应用程序通过各种网络协议与远程资源进行通信,并提供了一组标准的方法和接口来处理常见的协议操作。
ResponseCache提供了两个抽象方法,可以存储和获取缓存中的数据:
public abstract CacheResponse get(URI uri,String reuqestMethod, Map<String,List<String>> requestHeaders) throws IOException
public abstract CacheRequest put(URI uri,URLConnection connection)
put方法返回一个CacheRequest对象,包装了一个OutputStream,URL将把读取的可缓存数据写入这个输出流,CacheRequest是一个抽象类,它有两个方法
public abstract class CacheRequest{
public abstract OutputStream getBody() throws IOException
public abstract void abort();
}
子类中的getOutputStream()方法应当返回一个OutputStream,指向缓存中的“数据库”,这个数据库与同时传入put()方法的URI对应。例如一个数据存储在一个文件中 ,就要返回连接到这个文件的FileOutputStream。协议处理器会把读取的数据复制到这个OutputStream。如果复制时出现了问题(例如,服务器意外的关闭了连接),协议处理器就会调用abort()方法。这个方法应当从缓存中删除为这个请求存储的所有数据。下面实现一个简单的CacheRequest一个子类:
public class SimpleCacheRequest extends CacheRequest{
private ByteArrayOutputStream out=new ByteArrayOutputStream();
@Override
public OutputStream getBody() throws IOException
{
return out;
}
@Override
public void abort(){
out.reset();
}
public byte[] getData(){
if(out.size()==0)
return null;
else
return out.toByteArray();
}
}
ResponseCache的get()方法从缓存中获取数据和首部,包装在CacheRespose对象中返回。如果所需的URI不在缓存中,则返回null,在这种情况上,协议处理器会正常地从远程服务器加载这个URI。
public abstract class ResponseCache {
private static ResponseCache theResponseCache;
public synchronized static ResponseCache getDefault() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.GET_RESPONSECACHE_PERMISSION);
}
return theResponseCache;
}
public synchronized static void setDefault(ResponseCache responseCache) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.SET_RESPONSECACHE_PERMISSION);
}
theResponseCache = responseCache;
}
public abstract CacheResponse
get(URI uri, String rqstMethod, Map<String, List<String>> rqstHeaders)
throws IOException;
public abstract CacheRequest put(URI uri, URLConnection conn) throws IOException;
}
前面说过Java要求一次只能有一个URL缓存,要安装或改变缓存,需要使用静态方法ResponseCache.setDefault()和ResponseCache.getDefault(),这些方法会设置同一个java虚拟机中运行的所有程序所使用的缓存。