001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.components.cache.interceptor; 017 018 019import com.jfinal.aop.Interceptor; 020import com.jfinal.aop.Invocation; 021import com.jfinal.core.Action; 022import com.jfinal.core.CPI; 023import com.jfinal.core.Controller; 024import com.jfinal.plugin.activerecord.Page; 025import com.jfinal.render.Render; 026import com.jfinal.render.RenderManager; 027import io.jboot.components.cache.AopCache; 028import io.jboot.components.cache.annotation.Cacheable; 029import io.jboot.db.model.JbootModel; 030import io.jboot.exception.JbootException; 031import io.jboot.utils.AnnotationUtil; 032import io.jboot.utils.ClassUtil; 033import io.jboot.utils.ModelUtil; 034import io.jboot.utils.StrUtil; 035import io.jboot.web.render.JbootRenderFactory; 036import io.jboot.web.render.JbootReturnValueRender; 037 038import javax.servlet.http.HttpServletRequest; 039import javax.servlet.http.HttpServletResponse; 040import java.lang.reflect.Method; 041import java.util.*; 042 043/** 044 * 缓存操作的拦截器 045 * 046 * @author michael yang 047 */ 048public class CacheableInterceptor implements Interceptor { 049 050 private static final String NULL_VALUE = "NULL_VALUE"; 051 public static final String IGNORE_CACHED_ATTRS = "__ignore_cached_attrs"; 052 053 //是否开启 Controller 的 Action 缓存 054 //可用在 dev 模式下关闭,生产环境开启的场景,方便调试数据 055 private static boolean actionCacheEnable = true; 056 private static String actionCacheRefreshKey; 057 private static String actionCacheRefreshValue = "1"; 058 059 public static boolean isActionCacheEnable() { 060 return actionCacheEnable; 061 } 062 063 public static void setActionCacheEnable(boolean actionCacheEnable) { 064 CacheableInterceptor.actionCacheEnable = actionCacheEnable; 065 } 066 067 public static String getActionCacheRefreshKey() { 068 return actionCacheRefreshKey; 069 } 070 071 public static void setActionCacheRefreshKey(String actionCacheRefreshKey) { 072 CacheableInterceptor.actionCacheRefreshKey = actionCacheRefreshKey; 073 } 074 075 public static String getActionCacheRefreshValue() { 076 return actionCacheRefreshValue; 077 } 078 079 public static void setActionCacheRefreshValue(String actionCacheRefreshValue) { 080 if (actionCacheRefreshValue == null || actionCacheRefreshValue.trim().length() == 0){ 081 throw new NullPointerException("actionCacheRefresValue can not be null or empty."); 082 } 083 CacheableInterceptor.actionCacheRefreshValue = actionCacheRefreshValue; 084 } 085 086 @Override 087 public void intercept(Invocation inv) { 088 089 Method method = inv.getMethod(); 090 Cacheable cacheable = method.getAnnotation(Cacheable.class); 091 if (cacheable == null || (inv.isActionInvocation() && !actionCacheEnable)) { 092 inv.invoke(); 093 return; 094 } 095 096 if (inv.isActionInvocation()) { 097 forController(inv, method, cacheable); 098 } else { 099 forService(inv, method, cacheable); 100 } 101 } 102 103 104 private void forController(Invocation inv, Method method, Cacheable cacheable) { 105 String unlessString = AnnotationUtil.get(cacheable.unless()); 106 if (Utils.isUnless(unlessString, method, inv.getArgs())) { 107 inv.invoke(); 108 return; 109 } 110 111 Class<?> targetClass = inv.getTarget().getClass(); 112 String cacheName = AnnotationUtil.get(cacheable.name()); 113 Utils.ensureCacheNameNotBlank(method, cacheName); 114 String cacheKey = Utils.buildCacheKey(AnnotationUtil.get(cacheable.key()), targetClass, method, inv.getArgs()); 115 116 Controller controller = inv.getController(); 117 118 //刷新当前页面缓存 119 if (StrUtil.isNotBlank(actionCacheRefreshKey) 120 && actionCacheRefreshValue.equals(inv.getController().getPara(actionCacheRefreshKey))){ 121 inv.invoke(); 122 cacheActionContent(cacheName, cacheKey, cacheable.liveSeconds(), inv, method); 123 return; 124 } 125 126 ActionCachedContent actionCachedContent = AopCache.get(cacheName, cacheKey); 127 if (actionCachedContent != null) { 128 renderActionCachedContent(controller, actionCachedContent); 129 return; 130 } 131 132 inv.invoke(); 133 cacheActionContent(cacheName, cacheKey, cacheable.liveSeconds(), inv, method); 134 } 135 136 137 /** 138 * 对 action 内容进行缓存 139 * 140 * @param cacheName 141 * @param cacheKey 142 * @param liveSeconds 143 * @param inv 144 * @param method 145 */ 146 public static void cacheActionContent(String cacheName, String cacheKey, int liveSeconds, Invocation inv, Method method) { 147 148 Render render = getControllerRender(inv, method); 149 150 if (render == null){ 151 return; 152 } 153 154 ActionCachedContent cachedContent = new ActionCachedContent(render); 155 156 Controller controller = inv.getController(); 157 158 // 忽略的缓存配置 159 Set<String> ignoreCachedAttrs = controller.getAttr(IGNORE_CACHED_ATTRS); 160 if (ignoreCachedAttrs != null) { 161 ignoreCachedAttrs.add(IGNORE_CACHED_ATTRS); 162 } 163 164 HttpServletRequest request = controller.getRequest(); 165 for (Enumeration<String> names = request.getAttributeNames(); names.hasMoreElements(); ) { 166 String name = names.nextElement(); 167 if (ignoreCachedAttrs == null || !ignoreCachedAttrs.contains(name)) { 168 cachedContent.addAttr(name, request.getAttribute(name)); 169 } 170 } 171 172 HttpServletResponse response = controller.getResponse(); 173 Collection<String> headerNames = response.getHeaderNames(); 174 headerNames.forEach(name -> cachedContent.addHeader(name, response.getHeader(name))); 175 176 AopCache.putDataToCache(cacheName, cacheKey, cachedContent, liveSeconds); 177 } 178 179 180 protected static final RenderManager renderManager = RenderManager.me(); 181 182 private static Render getControllerRender(Invocation inv, Method method) { 183 Render render = inv.getController().getRender(); 184 if (render == null && void.class != method.getReturnType() 185 && renderManager.getRenderFactory() instanceof JbootRenderFactory) { 186 187 JbootRenderFactory factory = (JbootRenderFactory) renderManager.getRenderFactory(); 188 JbootReturnValueRender returnValueRender = factory.getReturnValueRender(inv.getReturnValue()); 189 190 //有可能为 null,比如 Forward 的情况 191 render = returnValueRender.getRealRender(); 192 193 }else if (render == null) { 194 Action action = CPI.getAction(inv.getController()); 195 render = renderManager.getRenderFactory().getDefaultRender(action.getViewPath() + action.getMethodName()); 196 } 197 198 return render; 199 } 200 201 202 /** 203 * 渲染缓存的 ActionCachedContent 204 * 205 * @param controller 206 * @param actionCachedContent 207 */ 208 private void renderActionCachedContent(Controller controller, ActionCachedContent actionCachedContent) { 209 Map<String, Object> cachedAttrs = actionCachedContent.getAttrs(); 210 if (cachedAttrs != null) { 211 HttpServletRequest request = controller.getRequest(); 212 Set<String> existAttrNames = getRequestAttrNames(request); 213 cachedAttrs.forEach((cachedAttrName, value) -> { 214 if (!existAttrNames.contains(cachedAttrName)) { 215 request.setAttribute(cachedAttrName, value); 216 } 217 }); 218 } 219 220 221 Map<String, String> headers = actionCachedContent.getHeaders(); 222 if (headers != null) { 223 HttpServletResponse response = controller.getResponse(); 224 headers.forEach((name, value) -> { 225 String existHeaderValue = response.getHeader(name); 226 if (existHeaderValue != null) { 227 value = existHeaderValue; 228 } 229 response.setHeader(name, value); 230 }); 231 } 232 233 controller.render(actionCachedContent.createRender()); 234 } 235 236 237 private Set<String> getRequestAttrNames(HttpServletRequest request) { 238 Set<String> ret = new HashSet<>(); 239 for (Enumeration<String> attrNames = request.getAttributeNames(); attrNames.hasMoreElements(); ) { 240 ret.add(attrNames.nextElement()); 241 } 242 return ret; 243 } 244 245 /** 246 * Service 层的 Cacheable 使用 247 * 248 * @param inv 249 * @param method 250 * @param cacheable 251 */ 252 private void forService(Invocation inv, Method method, Cacheable cacheable) { 253 String unlessString = AnnotationUtil.get(cacheable.unless()); 254 if (Utils.isUnless(unlessString, method, inv.getArgs())) { 255 inv.invoke(); 256 return; 257 } 258 259 Class<?> targetClass = inv.getTarget().getClass(); 260 String cacheName = AnnotationUtil.get(cacheable.name()); 261 Utils.ensureCacheNameNotBlank(method, cacheName); 262 String cacheKey = Utils.buildCacheKey(AnnotationUtil.get(cacheable.key()), targetClass, method, inv.getArgs()); 263 264 Object data = AopCache.get(cacheName, cacheKey); 265 if (data != null) { 266 if (NULL_VALUE.equals(data)) { 267 inv.setReturnValue(null); 268 } else if (cacheable.returnCopyEnable()) { 269 inv.setReturnValue(getCopyObject(inv, data)); 270 } else { 271 inv.setReturnValue(data); 272 } 273 } else { 274 inv.invoke(); 275 data = inv.getReturnValue(); 276 if (data != null) { 277 278 AopCache.putDataToCache(cacheName, cacheKey, data, cacheable.liveSeconds()); 279 280 //当启用返回 copy 值的时候,返回的内容应该是一个进行copy之后的值 281 if (cacheable.returnCopyEnable()) { 282 inv.setReturnValue(getCopyObject(inv, data)); 283 } 284 285 } else if (cacheable.nullCacheEnable()) { 286 AopCache.putDataToCache(cacheName, cacheKey, NULL_VALUE, cacheable.liveSeconds()); 287 } 288 } 289 } 290 291 292 private <M extends JbootModel> Object getCopyObject(Invocation inv, Object data) { 293 if (data instanceof List) { 294 return ModelUtil.copy((List<? extends JbootModel>) data); 295 } else if (data instanceof Set) { 296 return ModelUtil.copy((Set<? extends JbootModel>) data); 297 } else if (data instanceof Page) { 298 return ModelUtil.copy((Page<? extends JbootModel>) data); 299 } else if (data instanceof JbootModel) { 300 return ModelUtil.copy((JbootModel) data); 301 } else if (data.getClass().isArray() 302 && JbootModel.class.isAssignableFrom(data.getClass().getComponentType())) { 303 return ModelUtil.copy((M[]) data); 304 } else { 305 throw newException(null, inv, data); 306 } 307 } 308 309 310 private JbootException newException(Exception ex, Invocation inv, Object data) { 311 String msg = "Can not copy data for type [" + data.getClass().getName() + "] in method :" 312 + ClassUtil.buildMethodString(inv.getMethod()) 313 + " , can not use @Cacheable(returnCopyEnable=true) annotation"; 314 315 return ex == null ? new JbootException(msg) : new JbootException(msg, ex); 316 } 317 318 319}