pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--freemarker--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.61</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <!--redis--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--自定义注解--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <!-- spring热部署 --> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.6.RELEASE</version> </dependency> </dependencies> <configuration><fork>true</fork></configuration> </plugin> </plugins> </build>
application.properties
server.port=1234 server.servlet.context-path=/redis_freemarker # 开启热部署 spring.devtools.restart.enabled=true spring.mvc.view.suffix=.ftl spring.mvc.static-path-pattern: /** # freemarker静态资源配置 # 设定ftl文件路径 spring.freemarker.tempalte-loader-path=classpath:/templates # 关闭缓存,及时刷新,上线生产环境需要修改为true spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.check-template-location=true spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=true spring.freemarker.expose-session-attributes=true spring.freemarker.request-context-attribute=request spring.freemarker.suffix=.ftl # 空值不报错 spring.freemarker.settings.classic_compatible=true spring.redis.host=localhost spring.redis.port=6379 spring.redis.timeout=3 spring.redis.password=123456 spring.redis.jedis.pool.max-wait=3 spring.redis.jedis.pool.max-idle=10 spring.redis.jedis.pool.max-total=10
redis 注册类
RedisConfig package com.example.redis_freemarker; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @Component @PropertySource(value = {"classpath:application.properties"}) public class RedisConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.timeout}") private int timeout;//秒 @Value("${spring.redis.password}") private String password; @Value("${spring.redis.jedis.pool.max-total}") private int poolMaxTotal; @Value("${spring.redis.jedis.pool.max-idle}") private int poolMaxIdle; @Value("${spring.redis.jedis.pool.max-wait}") private int poolMaxWait;//秒 public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getPoolMaxTotal() { return poolMaxTotal; } public void setPoolMaxTotal(int poolMaxTotal) { this.poolMaxTotal = poolMaxTotal; } public int getPoolMaxIdle() { return poolMaxIdle; } public void setPoolMaxIdle(int poolMaxIdle) { this.poolMaxIdle = poolMaxIdle; } public int getPoolMaxWait() { return poolMaxWait; } public void setPoolMaxWait(int poolMaxWait) { this.poolMaxWait = poolMaxWait; } @Bean public JedisPool jedisPoolFactory() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxIdle(poolMaxIdle); jedisPoolConfig.setMaxTotal(poolMaxTotal); jedisPoolConfig.setMaxWaitMillis(poolMaxWait * 1000); JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout * 1000, password); return jedisPool; } }
redis 工具类
RedisService package com.example.redis_freemarker; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @Service public class RedisService { @Autowired private JedisPool jedisPool; /** * 获取存储对象 * @param key * @param clazz * @param <T> * @return */ public <T> T get(String key, Class<T> clazz) { Jedis jedis = null; try { jedis = jedisPool.getResource(); String str = jedis.get(key); T t = stringToBean(str, clazz); return t; } finally { returnToPool(jedis); } } /** * 设置对象 * @param key * @param expireSeconds * @param value * @param <T> * @return */ public <T> boolean set(String key, int expireSeconds, T value) { Jedis jedis = null; try { jedis = jedisPool.getResource(); String str = beanToString(value); if (null == str) { return false; } if (expireSeconds <= 0) { jedis.set(key, str); } else { jedis.setex(key, expireSeconds, str); } return true; } finally { returnToPool(jedis); } } /** * 判断key是否存在 * */ public <T> boolean exists(String key) { Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.exists(key); }finally { returnToPool(jedis); } } /** * 增加值 * */ public <T> Long incr(String key) { Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.incr(key); }finally { returnToPool(jedis); } } /** * 减少值 * */ public <T> Long decr(String key) { Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.decr(key); }finally { returnToPool(jedis); } } private <T> String beanToString(T value) { if (null == value) { return null; } if (value instanceof Integer || value instanceof Long) { return "" + value; } else if (value instanceof String) { return (String) value; } else { return JSON.toJSONString(value); } } private <T> T stringToBean(String str, Class<T> clazz) { if (StringUtils.isEmpty(str) || clazz==null) { return null; } if (clazz==int.class || clazz==Integer.class) { return (T) Integer.valueOf(str); } else if (clazz == String.class) { return (T) str; } else if (clazz==long.class || clazz==Long.class) { return (T)Long.valueOf(str); } else { return JSON.toJavaObject(JSON.parseObject(str), clazz); } } private void returnToPool(Jedis jedis) { if (null != jedis) { jedis.close(); } } }
freemaker 工具类
import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateExceptionHandler; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.Map; public class FreemarkerUtils { public static String getTemplate(String template, Map<String,Object> map) throws IOException, TemplateException { Configuration cfg = new Configuration(Configuration.VERSION_2_3_28); String templatePath = FreemarkerUtils.class.getResource("/").getPath()+"/templates"; cfg.setDirectoryForTemplateLoading(new File(templatePath)); cfg.setDefaultEncoding("UTF-8"); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setLogTemplateExceptions(false); cfg.setWrapUncheckedExceptions(true); cfg.setClassicCompatible(true);// 空值不报错 Template temp = cfg.getTemplate(template); StringWriter stringWriter = new StringWriter(); temp.process(map, stringWriter); return stringWriter.toString(); } }
新建一个自定义注解 AutoRedis
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface AutoRedis { String key(); // redis key 值 String returnFtl(); // 返回的ftl页面路径 int expireSeconds() default 86400; // 缓存过期时间,默认24小时 boolean soutIfTrue() default true; // 是否打印信息 }
新建一个 AOP
RedisAspect package com.example.redis_freemarker; import com.alibaba.fastjson.JSON; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; import java.lang.reflect.Method; import java.util.Map; @Aspect @Configuration public class RedisAspect { @Autowired private RedisService redisService; //定义切点方法 @Pointcut("@annotation(com.example.redis_freemarker.AutoRedis)") public void pointCut(){} //环绕通知 @Around("pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable,Exception { //1.获取到所有的参数值的数组 Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; //2.获取到方法的所有参数名称的字符串数组 String[] parameterNames = methodSignature.getParameterNames(); Method method = methodSignature.getMethod(); Class<?> returnType = method.getReturnType(); // 返回类型 Class<?> classTarget = joinPoint.getTarget().getClass(); AutoRedis redis=(AutoRedis)method.getAnnotation(AutoRedis.class); boolean soutIfTrue=redis.soutIfTrue(); soutIfTrue(soutIfTrue,"■■■■■■■■■■■■■■■■■■■■■-AutoRedis注解开始-■■■■■■■■■■■■■■■■■■■■■"); String key=redis.key(); String returnFtl=redis.returnFtl(); int expireSeconds=redis.expireSeconds(); soutIfTrue(soutIfTrue,"自定义注解 key:" + key); soutIfTrue(soutIfTrue,"自定义注解 returnFtl:" + returnFtl); soutIfTrue(soutIfTrue,"自定义注解 expireSeconds:" + expireSeconds); soutIfTrue(soutIfTrue,"---------------参数列表开始-------------------------"); for (int i =0 ,len=parameterNames.length;i < len ;i++){ soutIfTrue(soutIfTrue,"参数名:"+ parameterNames[i] + " = " +args[i]); key=redis.key().replaceAll("【"+parameterNames[i]+"】",args[i].toString()); } soutIfTrue(soutIfTrue,"---------------参数列表结束-------------------------"); soutIfTrue(soutIfTrue,"自定义注解解析后 key:" + key); // String html = redisService.get(key, String.class); Map<String,Object> map = redisService.get(key, Map.class); /* 1.这里有两种方式存储,可以存储整个HTML代码放进redis,这种数据库压力较大 2.也可以把 Object result 放进redis,这样redis库不会浪费空间,然后读缓存的时候用freemarker 转换一下,这种服务器压力较大。 */ // 1. /* if (!StringUtils.isEmpty(html)) { soutIfTrue(soutIfTrue,"有缓存,返回缓存数据"); soutIfTrue(soutIfTrue,"■■■■■■■■■■■■■■■■■■■■■-AutoRedis注解结束-■■■■■■■■■■■■■■■■■■■■■\n\n\n"); return html; }else{ Object result=joinPoint.proceed(args); String cmtTpl= null; cmtTpl = FreemarkerUtils.getTemplate(returnFtl, (Map<String,Object>)JSON.parseObject(result.toString())); // 存入缓存 if(!StringUtils.isEmpty(cmtTpl)){ soutIfTrue(soutIfTrue,"没有缓存,返回刚查询的数据,并且存入redis"); redisService.set(key, expireSeconds, cmtTpl); } soutIfTrue(soutIfTrue,"■■■■■■■■■■■■■■■■■■■■■-AutoRedis注解结束-■■■■■■■■■■■■■■■■■■■■■\n\n\n"); return cmtTpl; } */ // 2. if (map!=null) { soutIfTrue(soutIfTrue,"有缓存,返回缓存数据"); String cmtTpl= null; cmtTpl = FreemarkerUtils.getTemplate(returnFtl,map); soutIfTrue(soutIfTrue,"■■■■■■■■■■■■■■■■■■■■■-AutoRedis注解结束-■■■■■■■■■■■■■■■■■■■■■\n\n\n"); return cmtTpl; }else{ Object result=joinPoint.proceed(args); String cmtTpl= null; cmtTpl = FreemarkerUtils.getTemplate(returnFtl, (Map<String,Object>)JSON.parseObject(result.toString())); // 存入缓存 if(!StringUtils.isEmpty(cmtTpl)){ soutIfTrue(soutIfTrue,"没有缓存,返回刚查询的数据,并且存入redis"); redisService.set(key, expireSeconds, (Map)JSON.parseObject(result.toString())); } soutIfTrue(soutIfTrue,"■■■■■■■■■■■■■■■■■■■■■-AutoRedis注解结束-■■■■■■■■■■■■■■■■■■■■■\n\n\n"); return cmtTpl; } } public void soutIfTrue(Boolean flag,String str){ if(flag){ System.out.println(str); } } }
最后来实验一下
package com.example.redis_freemarker; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap; import java.util.Map; @Controller public class test { @Autowired private RedisService redisService; @RequestMapping(value = "/test") @ResponseBody public String test(String page) { if(StringUtils.isEmpty(page)){ return "page参数不能为空"; } //判断redis是否有缓存 String html = redisService.get(page, String.class); if (!StringUtils.isEmpty(html)) { System.out.println("有缓存,返回缓存数据"); //System.out.println(html); return html; } // 这里模拟一下查询数据,浪费时间3秒 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } Map<String,Object> map=new HashMap<>(); map.put("NAME","吴彦祖"); map.put("ID","14567"); map.put("AGE","18"); map.put("PAGE",page); String cmtTpl= null; try { cmtTpl = FreemarkerUtils.getTemplate("/test/test.ftl",map); } catch (Exception e) { e.printStackTrace(); } // 存入缓存 if(!StringUtils.isEmpty(cmtTpl)){ System.out.println("没有缓存,返回刚查询的数据,并且存入redis"); redisService.set(page, 86400, cmtTpl); } return cmtTpl; } @RequestMapping("/test2") @ResponseBody @AutoRedis(key = "HELLO_【page】",returnFtl = "/test/test.ftl",expireSeconds = 86400,soutIfTrue = true) public String test2(String page){ if(StringUtils.isEmpty(page)){ return "page参数不能为空"; } // 这里模拟一下查询数据,浪费时间3秒 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } Map<String,Object> map=new HashMap<>(); map.put("NAME","吴彦祖"); map.put("ID","14567"); map.put("AGE","18"); map.put("PAGE",page); return JSON.toJSONString(map); } }
test.ftl
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> </head> <body> <table border="1"> <thead> <tr> <th>PAGE</th> <th>NAME</th> <th>ID</th> <th>AGE</th> </tr> </thead> <tbody> <tr> <td>${PAGE}</td> <td>${NAME}</td> <td>${ID}</td> <td>${AGE}</td> </tr> </tbody> </table> </body> </html>