El caché de SpringBoot se usa en el trabajo, lo cual es bastante conveniente de usar. Introduce directamente paquetes de dependencia de caché, como Redis o Ehcache y los paquetes de dependencia de inicio de los cachés relacionados, y luego agrega la anotación @Enablecaching a la clase de inicio, y luego puede usar @Cacheable y @CacheEvict para usar y eliminar el caché donde sea necesario. Esto es muy simple de usar. Creo que aquellos que hayan usado el caché de brotación de trampolín jugarán, por lo que no diré más aquí. El único inconveniente es que SpringBoot usa la integración de complementos. Aunque es muy conveniente de usar, cuando integra ehcache, usa ehcache y cuando integra redis, usa redis. Si desea usar ambos juntos, Ehcache se usa como la caché local de nivel 1 y Redis se usa como un caché de nivel 2 integrado. Hasta donde yo sé, es imposible lograr el método predeterminado (si hay un experto que pueda implementarlo, por favor dame algún consejo). Después de todo, muchos servicios requieren una implementación de varios puntos. Si elige ehcache solo, puede realizar bien el caché local. Sin embargo, si comparte caché entre varias máquinas, llevará tiempo causar problemas. Si elige el caché centralizado de Redis, porque debe ir a la red cada vez que obtenga datos, siempre sentirá que el rendimiento no será demasiado bueno. Este tema analiza principalmente cómo integrar a la perfección ehcache y redis como cachés de primer y segundo nivel basados en trampolín, y realizar la sincronización de caché.
Para no invadir el método de caché original de Springboot, he definido dos anotaciones relacionadas con el caché aquí, de la siguiente manera
@Target ({elementType.method}) @Retention (retentionPolicy.Runtime) public @Interface Cachable {String value () predeterminado ""; Cadena de cadena () predeterminada ""; // clase genérica tipo clase <?> Type () excepción predeterminada.class; } @Target ({elementType.method}) @Retention (retentionPolicy.Runtime) public @Interface CacheEvict {String Value () default ""; Cadena de cadena () predeterminada ""; }Como las dos anotaciones anteriores son básicamente las mismas que las anotaciones en caché en la primavera, pero se eliminan algunos atributos utilizados con poca frecuencia. Hablando de esto, me pregunto si algún amigo ha notado que cuando usas Redis Cache solo en SpringBoot, los atributos de valor anotados por Cacheable y CacheEvict en realidad se convierten en una clave de valor ZSet en Redis, y el ZSet todavía está vacío, como @Cacheable (valor = "Cache1", Key = "Key1"). En circunstancias normales, Cache1 -> MAP (Key1, Value1) debe aparecer en Redis, donde Cache1 se usa como nombre de caché, MAP como valor de caché y clave como la clave en el mapa, que puede aislar efectivamente los cachés bajo diferentes nombres de caché. Pero, de hecho, hay caché1 -> vacío (zset) y key1 -> valor1, dos pares de valor clave independientes. El experimento encontró que el caché bajo diferentes nombres de caché está completamente compartido. Si está interesado, puede probarlo. Es decir, este atributo de valor es en realidad una decoración, y la singularidad de la clave solo está garantizada por el atributo clave. Solo puedo pensar que este es un error en la implementación de la memoria caché de Spring, o fue diseñado específicamente (si sabe la razón, por favor dame algunos consejos).
Volviendo al tema, con anotación, también hay una clase de procesamiento de anotaciones. Aquí uso la sección de AOP para la intercepción, y la implementación nativa es realmente similar. La clase de procesamiento de la sección es la siguiente:
import com.xuanwu.apaas.core.multicache.annotation.cacheevict; import com.xuanwu.apaas.core.multicache.annotation.cachable; import com.xuanwu.apaas.core.utils.jsonutil; importar org.apache.commons.lang3.stringutils; importar org.spectj.lang.procedingjoinpoint; importar org.spectj.lang.annotation.around; importar org.spectj.lang.annotation.around; importar org.spectj.lang.annotation.spect; importar org.spectj.lang.annotation.pointcut; importar org.spectj.lang.reflect.methodsignature; importar org.json.jsonarray; importar org.json.jsonObject; importar org.slf4j.logger; importar org.slf4j.loggerFactory; importar org.springframework.beans.factory.annotation.aUtowired; importar org.springframework.core.localvariAbletableParamEternamedScoverer; importar org.springframework.expression.expressionParser; importar org.springframework.expression.spel.standard.spelexpressionParser; importar org.springframework.expression.spel.support.standardevaluationContext; importar org.springframework.stereotype.component; import java.lang.reflect.method; / *** Sección de caché multinivel* @Author Rongdi*/ @Aspect @Component Public Class MulticacheACHEASPECT {private estático final de logger de logger = loggerFactory.getLogger (multicacheSpect.class); @Autowired privado Cachefactory CacheFactory; // Aquí el oyente se inicializa a través de un contenedor, y el interruptor de caché se controla de acuerdo con la anotación de @EnableCaching configurada externamente en caché booleano privado; @PointCut ("@annotation (com.xuanwu.apaas.core.multicache.annotation.cacheable)") public void cacheaspect () {} @pointcut ("@annotation (com.xuanwu.apaas.core.multicache.annotation.cacheevict)") public vecio CheCheevict () {) @Around ("CacheAleAlespect ()") Public Object Cache (procedimientojoinpoint unkenpoint) {// Obtenga la lista de parámetros del método modificado por el objeto facetado [] args = unión.getArgs (); // El resultado es el resultado de retorno final del objeto del método resultado = nulo; // Si el caché no está habilitado, llame directamente al método de procesamiento para devolver if (! Cacheenable) {try {result = unkePoint.proced (args); } catch (lanzable e) {logger.error ("", e); } resultado de retorno; } // Obtenga el tipo de valor de retorno de la clase de método proxy returnType = ((Methodsignature) unePoint.getSignature ()). GetReturnType (); // Obtener el método del método proxy Método = ((Methodsignature) unePoint.getSignature ()). GetMethod (); // Obtener el comentario sobre el método proxy en caché ca = método.getAnnotation (cacheo Cachable.class); // Obtener el valor de la clave analizado por la tecla de cadena de Ely = parsekey (ca.key (), método, args); Clase <?> ElementClass = Ca.Type (); // Obtenga el nombre de caché del nombre de la cadena de anotación = CA.Value (); Pruebe {// Primero obtenga datos de la cadena de ehcache Cachevalue = CacheFactory.ehget (nombre, clave); if (StringUtils.isEmpty (CacheValue)) {// Si no hay datos en ehcache, obtenga datos de redis cachevalue = cachefactory.redisget (nombre, clave); if (StringUtils.isEmpty (CacheValue)) {// Si no hay datos en ehcache, obtenga datos de redis cachevalue = cachefactory.redisget (nombre, clave); if (StringUtils.isEmpty (CacheValue)) {// Si no hay datos en Redis // llame al método comercial para obtener el resultado resultado = unkenPoint.proced (args); // Serialice el resultado y colóquelo en Redis Cachefactory.RedIsput (nombre, clave, serialize (resultado)); } else {// Si los datos se pueden obtener de Redis // Deserialize los datos obtenidos en la caché y return if (elementClass == Exception.Class) {result = deserialize (CacheValue, returnType); } else {resultado = deserialize (CacheValue, returntype, elementClass); }} // Serialice el resultado y colóquelo en Ehcache Cachefactory.ehput (nombre, clave, serialize (resultado)); } else {// Deserialice los datos obtenidos en la caché y return if (elementClass == Exception.Class) {result = deserialize (CacheValue, returnType); } else {resultado = deserialize (CacheValue, returntype, elementClass); }}} catch (showable showable) {logger.error ("", showable); } resultado de retorno; } / ** * Borre el caché antes de llamar al método, y luego llame al método de negocio * @param unión unpoint * @return * @throws showable * * / @around ("cacheEvict ()") public EvictCache (procedingjoinPoint unoPoint) lanza Showable {// obtiene el método de método proxy = ((methet MethodeSignature) JoinPoint.getSignature ())). // Obtenga la lista de parámetros del método modificado por el objeto facetado [] args = unkenpoint.getArgs (); // Obtenga la anotación en el método proxy cacheEvict ce = método.getAnnotation (cacheEvict.class); // Obtener el valor de la clave analizado por la tecla de cadena EL = parsekey (ce.key (), método, args); // Obtener el nombre de la memoria caché del nombre de la cadena de anotación = CE.Value (); // Borrar el caché correspondiente cachefactory.cachedel (nombre, clave); return se unepoint.proceed (args); } / ** * Obtenga la tecla Caced * definida en la anotación, admitiendo expresiones Spel * @return * / private string parsekey (clave de cadena, método método, objeto [] args) {if (stringUtils.isEmpty (key)) return null; // Obtenga la lista de nombres de parámetros del método interceptado (utilizando la biblioteca de clases de soporte de Spring) LocalVariaBletableParameternamedScoverer u = new LocalVariaBletableParamTernamedScoverer (); String [] paranamearr = u.getParamTernames (método); // Use Spel para el análisis clave de expresión de Parser = new SpelExpressionParser (); // contexto de spel StandaryEvaluationContext context = new StandardEvaluationContext (); // Ponga los parámetros del método en el contexto SPEL para (int i = 0; i <paranamearr.length; i ++) {context.setVariable (paranamearr [i], args [i]); } return parser.parseExpression (clave) .getValue (context, string.class); } // Serialize String private Serialize (Object obj) {String result = null; intente {resultado = jsonutil.serialize (obj); } catch (excepción e) {resultado = obj.ToString (); } resultado de retorno; } // Deserializar objeto privado deserialize (string str, class clazz) {objeto resultado = null; intente {if (clazz == jsonObject.class) {result = new jsonObject (str); } else if (clazz == jsonArray.class) {result = new JSonArray (str); } else {resultado = jsonutil.deserialize (str, clazz); }} Catch (Exception e) {} return resultado; } // Deserialización, Lista de soporte <xxx> Objeto privado Deserialize (String Str, Class Clazz, Class ElementClass) {Object result = Null; intente {if (clazz == jsonObject.class) {result = new jsonObject (str); } else if (clazz == jsonArray.class) {result = new JSonArray (str); } else {resultado = jsonutil.deserialize (str, clazz, elementclass); }} Catch (Exception e) {} return resultado; } public void setCacheenable (boolean Cacheenable) {this.cacheenable = cacheenable; }}La interfaz anterior utiliza una variable en caché para controlar si usar caché. Para lograr un acceso perfecto a SpringBoot, es necesario controlar la anotación nativa de @enablecaching. Aquí uso un oyente cargado por el contenedor de resorte, y luego encuentro en el oyente si hay una clase modificada por la anotación @enablecaching. Si es así, obtenga el objeto de multicacheaspect desde el contenedor de resorte y luego configure en caché en verdad. Esto permitirá un acceso sin problemas a SpringBoot. Me pregunto si hay una manera más elegante para los amigos. ¡Bienvenido a comunicarse! La clase del oyente es la siguiente
import com.xuanwu.apaas.core.multicache.cachefactory; import com.xuanwu.apaas.core.multicache.multicacheaspect; importar org.springframework.cache.annotation.enablecaching; importar org.springframework.context.applicationListener; importar org.springframework.context.event.contextrefreshedevent; importar org.springframework.stereotype.component; import java.util.map; / ** * Se utiliza para encontrar si hay anotación para habilitar el caché en el proyecto después de que se complete la carga de resorte @enablecaching * @author rongdi */ @Component public class contreTreFreshedListener implementos implementos Applicatione <ContextreFreshedevent> {@OverRide public contenedor para evitar la ocurrencia de dos llamadas (la carga de MVC también se activará una vez) if (event.getApplicationContext (). getParent () == null) {// Obtener todas las clases modificadas por @enablecaching annotation map <string, object> beans = event.getApplicationContex. if (beans! = null &&! beans.isempty ()) {multicacheaspect multicache = (multicacheCeSpect) event.getApplicationContext (). getBean ("mulicacheuspect"); multicache.setCacheenable (verdadero); }}}}}Para lograr un acceso sin problemas, también debemos considerar cómo Ehcache de múltiples puntos es consistente con Redis Cache al implementar EHCACHE múltiple. En aplicaciones normales, Redis es generalmente adecuado para caché centralizado a largo plazo, y Ehcache es adecuado para caché local a corto plazo. Suponga que ahora hay servidores A, B y C, A y B implementan servicios comerciales, y C implementa servicios Redis. Cuando entra una solicitud, la entrada frontal, ya sea un software de carga como LVS o NGINX, reenviará la solicitud a un servidor específico. Suponiendo que se reenvía al servidor A y se modifica un determinado contenido, y este contenido está disponible tanto en Redis como en Ehcache. En este momento, el caché Ehcache del servidor A y Redis del servidor C son más fáciles de controlar si el caché no es válido o se elimina. ¿Pero cómo controlar el ehcache del servidor B en este momento? El método de uso común es utilizar el modo de suscripción de publicación. Cuando necesite eliminar el caché, publique un mensaje en un canal fijo. Luego, cada servidor comercial se suscribe a este canal. After receiving the message, you delete or expire the local ehcache cache (it is best to use expired, but Redis currently only supports expired operations on the key. There is no way to operate the expiration of members in the map under the key. If you have to force the expiration, you can add a timestamp to implement it yourself. However, the chance of using delete to cause problems is very small. After all, those who add caches are applications with more reads and Menos escritos. En resumen, el proceso es actualizar una cierta pieza de datos, primero eliminar el caché correspondiente en Redis y luego publicar un mensaje con caché no válido en un cierto canal de Redis. El servicio comercial local se suscribe al mensaje de este canal. Cuando el servicio comercial recibe este mensaje, elimina el caché de ehcache local. Las diversas configuraciones de Redis son las siguientes.
import com.fasterxml.jackson.annotation.jsonautodetect; import com.fasterxml.jackson.annotation.propertyaccessor; import com.fasterxml.jackson.databind.objectMapper; import com.xuanwu.apaas.core.multicache.subscriber.messageSubsCriber; importar org.springframework.cache.cachemanager; importar org.springframework.context.annotation.bean; importar org.springframework.context.annotation.configuration; importar org.springframework.data.redis.cache.rediscachemanager; importar org.springframework.data.redis.connection.redisconnectionFactory; importar org.springframework.data.redis.core.redistemplate; importar org.springframework.data.redis.core.stringredistemplate; importar org.springframework.data.redis.listener.patterntopic; importar org.springframework.data.redis.listener.redismessageListenerContainer; importar org.springframework.data.redis.listener.adapter.messageListenerAdapter; importar org.springframework.data.redis.serializer.jackson2jsonredisserializer; @Configuration public class Redisconfig {@Bean Public Cachemanager Cachemanager (redistemplate redistemplate) {redisCachemanager rcm = new redisCachemanager (redistemplate); // establecer tiempo de vencimiento de caché (segundos) rcm.setDefaultExpiration (600); devolver RCM; } @Bean public Redistemplate <String, String> redistemplate (redisConnectionFactory factory) {stringRedistEmplate Template = new StringRedistEmplate (factory); JACKSON2JSONREDISSERIATOR JACKSON2JSONREDISSERIATIZATOR = new Jackson2JsonRedisserializer (Object.Class); ObjectMapper om = new ObjectMapper (); om.setVisibility (PropertyAccessor.all, jsonautodetect.visibility.yy); om.enabledFaultTyping (ObjectMapper.DefaultTyping.Non_Final); Jackson2JSONREDISSERIATER.SetObjectMapper (OM); Template.SetValuueSerializer (Jackson2JsonRedisserializer); Template.AfterPropertIesset (); plantilla de retorno; } /*** Contenedor de escucha de mensajes Redis* Puede agregar múltiples oyentes de Redis que escuchen diferentes temas. Solo necesita vincular al oyente de mensajes y al procesador de suscripción de mensajes correspondiente, y el oyente de mensajes * llamando a métodos relacionados de procesador de suscripción de mensajes a través de la tecnología de reflexión para algunos procesos comerciales * @param ConnectionFactory * @param oyenteaderadapter * @return */ @bean public rojosageListenercontainer (contenedor de redisconnectionFactoryFactoryFactory, MessageListenerApter Listeneradapter) {{{oreyeradapter) {{{oreyeraderer) {{{oreyeradapter) {{{oreyeradapter) {{oreyeraderer) {{{olor) {{{olor) RedismesSageListenContainer Container = New RedismesSageListenerContainer (); Container.SetConnectionFactory (ConnectionFactory); // suscribirse a un contenedor de canal.addMessageListener (LOYERAdapter, nuevo Patterntopic ("Redis.uncache")); // Este contenedor puede agregar múltiples MessageListener de retorno contenedor; } /** * Adaptador del oyente de mensajes, vincula el procesador de mensajes y usa la tecnología de reflexión para llamar a los métodos comerciales del procesador de mensajes * @param receptor * @return * /@bean MessageListenerAdapter ListenAdapter (MessageSubsCriber receptor) {// Este Método de MessageListenerAnstenerAntener para el Método de MessageNeSeNeraderAnstener y el Método de reflexión y el Método de Reflexión de nuevo " MessageListenerAdapter (receptor, "manejar"); }}La clase de publicación de mensajes es la siguiente:
import com.xuanwu.apaas.core.multicache.cachefactory; importar org.apache.commons.lang3.stringutils; importar org.slf4j.logger; importar org.slf4j.loggerFactory; importar org.springframework.beans.factory.annotation.aUtowired; importar org.springframework.stereotype.component; @Component Public Class MessageSubSCriber {private static final logger logger = loggerFactory.getLogger (MessagesUbSCriber.class); @Autowired privado Cachefactory CacheFactory; / *** Después de recibir el mensaje de la suscripción de Redis, el caché de Ehcache se invalida* @param Mensaje Format es name_key*/ public void Handle (mensaje de cadena) {logger.debug ("redis.ehcache:"+mensaje); if (stringUtils.isEmpty (mensaje)) {return; } String [] strs = message.split ("#"); Name de cadena = strs [0]; Tecla de cadena = nulo; if (strs.length == 2) {key = strs [1]; } cachefactory.ehdel (nombre, clave); }}Las clases de caché de operación específicas son las siguientes:
import com.xuanwu.apaas.core.multicache.publisher.messagePublisher; importar net.sf.ehcache.cache; importar net.sf.ehcache.cachemanager; importar net.sf.ehcache.element; importar org.apache.commons.lang3.stringutils; importar org.slf4j.logger; importar org.slf4j.loggerFactory; importar org.springframework.beans.factory.annotation.aUtowired; importar org.springframework.data.redis.redisconnectionfailureException; importar org.springframework.data.redis.core.hashoperations; importar org.springframework.data.redis.core.redistemplate; importar org.springframework.stereotype.component; import java.io.inputstream; / *** Sección de caché de niveles múltiples* @author rongdi*/ @Component public class Cachefactory {private static final logger logger = loggerFactory.getLogger (cachefactory.class); @Autowired private redistemplate redistemplate; @Autowired Private MessagePublisher MessagePublisher; Cachemanager privado Cachemanager; public CacheFactory () {InputStream IS = this.getClass (). GetResourCeasstream ("/ehcache.xml"); if (is! = null) {Cachemanager = Cachemanager.create (is); }} public void Cachedel (nombre de cadena, tecla de cadena) {// Eliminar el caché correspondiente a redis; // Eliminar el caché local ehcache, que no es necesario, y el suscriptor eliminará // ehdel (nombre, clave); if (Cachemanager! = NULL) {// Publicar un mensaje que le dice al servicio suscrito que el caché es inválido MessagePublisher.Publish (nombre, clave); }} public String ehget (nombre de cadena, tecla de cadena) {if (Cachemanager == null) return null; Cache Cache = Cachemanager.getCache (nombre); if (cache == null) return null; Cache.acquireReadLockOnkey (clave); intente {elemento ele = cache.get (clave); if (ele == null) return null; return (string) ele.getObjectValue (); } Finalmente {Cache.ReleaseReadLockOnkey (clave); }} public String redisget (nombre de cadena, tecla de cadena) {Hashoperations <String, String, String> Opera = redistemplate.opsforhash (); intente {return ópera.get (nombre, clave); } Catch (redisConnectionFailureException e) {// La conexión falla, no se lanza ningún error y el logger.error ("Conecte Redis Error", E); regresar nulo; }} public void ehput (nombre de cadena, tecla de cadena, valor de cadena) {if (Cachemanager == null) return; if (! Cachemanager.CacheExists (nombre)) {Cachemanager.addcache (nombre); } Caché cache = cachemanager.getCache (nombre); // Obtenga el bloqueo de escritura en la llave, diferentes claves no se afectan entre sí, similar a sincronizadas (key.intern ()) {} cache.acquirewriteLockOnkey (clave); intente {cache.put (nuevo elemento (clave, valor)); } Finalmente {// libera el bloqueo de escritura cache.ReleaseWriteLockOnkey (tecla); }} public void redisput (nombre de cadena, tecla de cadena, valor de cadena) {Hashoperations <String, String, String> Opera = redistemplate.opsforhash (); intente {operator.put (nombre, clave, valor); } Catch (redisConnectionFailureException e) {// La conexión falló, no se lanzó ningún error y el logger.error ("Conecte Redis Error", E); }} public void ehdel (nombre de cadena, tecla de cadena) {if (cachemanager == null) return; if (Cachemanager.CacheExists (name)) {// Si la clave está vacía, elimine directamente de acuerdo con el nombre de caché if (StringUtils.isEmpty (Key)) {Cachemanager.RemoVeCache (name); } else {caché cache = cachemanager.getCache (nombre); cache.remove (clave); }}} public void redisdel (nombre de cadena, tecla de cadena) {Hashoperations <String, String, String> Opera = redistemplate.opsforhash (); Pruebe {// si la tecla está vacía, elimina if (StringUtils.isEmpty (key)) {redistemplate.delete (nombre); } else {ópera.delete (nombre, clave); }} Catch (redisConnectionFailureException e) {// La conexión falló, no se lanzó ningún error y el logger.error ("Conecte Redis Error", e); }}}La clase de herramientas es la siguiente
import com.fasterxml.jackson.core.type.typereference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.javatype; import com.fasterxml.jackson.databind.objectMapper; importar org.apache.commons.lang3.stringutils; importar org.json.jsonarray; importar org.json.jsonObject; import java.util.*; clase pública JSONUTIL {Mapeador de mapas de objetos estáticos privados; static {mapper = new ObjectMapper (); mapper.configure (deserializationFeature.fail_on_unknown_properties, falso); } / ** * Serialice el objeto en json * * @param obj object a serializar * @return * @throws excepción * / public static string serialize (object obj) lanza excepción {if (obj == null) {tire nueva ilegalArgumentException ("obj no debe ser null"); } return mapper.WriteValueAsString (obj); } / ** Deserialización con genéricos, como la deserialización de un JSONArray en la lista <serem>* / public static <t> t deserialize (String JSonstr, class <?> CollectSclass, class <?> ... ElementClasses) lanza excepción {javatype javatype = mappper.getTypeFactory (). elementClasses); return mapper.ReadValue (JSonstr, Javatype); } / *** Deserialice la cadena JSON en un objeto* @param src La cadena JSON para estar deserializada* @param t El tipo de clase del objeto deserializado en* @return* @throws excepción* / public static <t> t desserialize (string src, class <T> t) Exception {if (src == null) nulo"); } if ("{}". Equals (src.trim ())) {return null; } return mappper.readValue (src, t); }}Para usar caché específicamente, solo preste atención a las anotaciones @Cachable y @CacheEvict, y también admite Spring El Expresiones. Además, el nombre de caché representado por el atributo de valor aquí no tiene el problema mencionado anteriormente. Se pueden aislar diferentes cachés usando valor. Los ejemplos son los siguientes
@Cacheable (value = "bo", key = "#session.productversionCode+''+#session.tenantCode+'' '+#objectCode")@cacheEvict (valor = "bo", key = "#session.productVersionCode+' '+#session.tenantCode+'+#ObjectCode")
Adjunto el paquete de dependencia principal
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.