Le cache de Springboot est utilisé dans le travail, ce qui est assez pratique à utiliser. Il introduit directement des packages de dépendance au cache tels que redis ou ehcache et les packages de dépendance de démarrage des caches associés, puis ajoute l'annotation @enableCaching à la classe de démarrage, puis vous pouvez utiliser @Cacheable et @Cacheevict pour utiliser et supprimer le cache si nécessaire. C'est très simple à utiliser. Je crois que ceux qui ont utilisé le cache Springboot joueront, donc je n'en parlerai pas ici. Le seul inconvénient est que Springboot utilise l'intégration du plug-in. Bien qu'il soit très pratique à utiliser, lorsque vous intégrez ehcache, vous utilisez ehcache et lorsque vous intégrez redis, vous utilisez redis. Si vous souhaitez utiliser les deux ensemble, ehcache est utilisé comme cache de niveau 1 local et redis est utilisé comme cache de niveau 2 intégré. Pour autant que je sache, il est impossible de réaliser la méthode par défaut (s'il y a un expert qui peut le mettre en œuvre, veuillez me donner des conseils). Après tout, de nombreux services nécessitent un déploiement à plusieurs points. Si vous choisissez ehcache seul, vous pouvez bien réaliser le cache local. Cependant, si vous partagez du cache entre plusieurs machines, il faudra du temps pour causer des problèmes. Si vous choisissez le cache Redis centralisé, car vous devez aller au réseau chaque fois que vous obtenez des données, vous sentirez toujours que les performances ne seront pas trop bonnes. Ce sujet explique principalement sur la façon d'intégrer de manière transparente ehcache et redis en tant que caches de premier et de deuxième niveau basés sur le Springboot et de réaliser la synchronisation du cache.
Afin de ne pas envahir la méthode de cache d'origine de Springboot, j'ai défini deux annotations liées au cache ici, comme suit
@Target ({elementType.Method}) @retention (retenderPolicy.runtime) public @Interface Cacheable {String Value () Default ""; String key () par défaut ""; // classe générique Type Classe <?> Type () Par défaut Exception.class; } @Target ({elementType.Method}) @retention (retenderPolicy.runtime) public @Interface Cacheevict {String Value () Default ""; String key () par défaut ""; }Comme les deux annotations ci-dessus sont fondamentalement les mêmes que les annotations en cache au printemps, mais certains attributs rarement utilisés sont supprimés. En parlant de cela, je me demande si des amis ont remarqué que lorsque vous utilisez Redis Cache seul à Springboot, les attributs de valeur annotés par Cacheable et Cacheevict deviennent en fait une clé de valeur ZSET dans Redis, et le Zset est toujours vide, comme @Cacheable (Value = "Cache1", Key = "Key1"). Dans des circonstances normales, CACHE1 -> MAP (Key1, Value1) doit apparaître dans Redis, où Cache1 est utilisé comme nom de cache, MAP comme valeur de cache et la clé comme clé de MAP, qui peut isoler efficacement les caches sous différents noms de cache. Mais en fait, il existe Cache1 -> vide (zset) et key1 -> value1, deux paires de valeurs de clé indépendantes. L'expérience a révélé que le cache sous différents noms de cache est complètement partagé. Si vous êtes intéressé, vous pouvez l'essayer. C'est-à-dire que cet attribut de valeur est en fait une décoration, et le caractère unique de la clé n'est garanti que par l'attribut clé. Je peux seulement penser qu'il s'agit d'un bug dans la mise en œuvre du cache du printemps, ou il a été conçu spécifiquement (si vous connaissez la raison, donnez-moi des conseils).
Retour au sujet, avec l'annotation, il y a aussi une classe de traitement d'annotation. Ici, j'utilise la section d'AOP pour l'interception, et l'implémentation native est en fait similaire. La classe de traitement de la section est la suivante:
import com.xuanwu.apaas.core.multicache.annotation.cacheevict; import com.xuanwu.apaas.core.multicache.annotation.cacheable; import com.xuanwu.apaas.core.utils.jsonutil; import org.apache.commons.lang3.stringutils; import org.aspectj.lang.proceedingJoinpoint; import org.aspectj.lang.annotation.around; 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.json.jsonArray; import org.json.jsonObject; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.LocalvariabEtableParameterNameDiscoverer; import org.springframework.expression.expressionPaSer; import org.springframework.expression.spel.standard.spexpressionPaSer; import org.springframework.expression.spel.support.standardEvaluationContext; import org.springframework.sterereotype.Component; import java.lang.reflect.method; / ** * Section de cache multi-niveaux * @author rongdi * / @aspect @component public class MultiCACheaSpect {private static final logger = loggerfactory.getLogger (multicAcacheSpect.class); @Autowired Private Cachefactory Cachefactory; // Ici, l'auditeur est initialisé via un conteneur, et le commutateur de cache est contrôlé en fonction du cacheenable @EnableCaching annotation configuré à l'extérieur; @Pointcut ("@ annotation (com.xuanwu.apaas.core.multicache.annotation.cacheable)") public void cacheSpect () {} @pointcut ("@ annotation (com.xuanwu.apaas.core.multicache.annotation.cacheevict)") @Around ("cacheBeLeSpect ()") Cache d'objet public (ProcedingJoinpoint joinpoint) {// Obtenez la liste des paramètres de la méthode modifiée par l'objet facet [] args = joindPoint.getArgs (); // Résultat est le résultat de retour final du résultat de l'objet de méthode = null; // Si le cache n'est pas activé, appelez directement la méthode de traitement pour retourner if (! Cacheenable) {try {result = joincePoint.proceed (args); } catch (Throwable E) {logger.error ("", e); } Retour Résultat; } // Obtenez le type de valeur de retour de la classe de méthode proxy returnType = ((Methodsignature) jointurePoint.getSignature ()). GetReturnType (); // Obtenez la méthode de la méthode de proxy Méthode Méthode = ((MethoDIGNATURE) joinPoint.getSignature ()). GetMethod (); // Obtenez le commentaire sur la méthode proxy CACHEABLE CA = Method.getAnnotation (cacheable.class); // Obtenez la valeur clé analysée par El String key = parsekey (ca.key (), méthode, args); Class <?> ElementClass = CA.Type (); // Obtenez le nom du cache de l'annotation String name = CA.Value (); essayez {// obtenez d'abord des données de ehcache String cachevalue = cachefactory.ehget (name, key); if (stringUtils.isempty (cachevalue)) {// s'il n'y a pas de données dans ehcache, obtenez des données de redis cachevalue = cachefactory.redisget (nom, key); if (stringUtils.isempty (cachevalue)) {// s'il n'y a pas de données dans ehcache, obtenez des données de redis cachevalue = cachefactory.redisget (nom, key); if (stringUtils.isempty (cachevalue)) {// s'il n'y a pas de données dans redis // appelez la méthode commerciale pour obtenir le résultat résultat = joinpoint.proceed (args); // sérialiser le résultat et le mettre dans redis cachefactory.redisput (nom, key, serialize (résultat)); } else {// Si des données peuvent être obtenues à partir de redis // désérialiser les données obtenues dans le cache et return if (elementClass == exception.class) {result = désérialize (cachevalue, returnType); } else {result = désérialize (cachevalue, returnType, elementClass); }} // sérialiser le résultat et le mettre dans ehcache cachefactory.ehput (nom, key, serialize (result)); } else {// désérialiser les données obtenues dans le cache et return if (elementClass == exception.class) {result = désérialiser (cachevalue, returnType); } else {result = désérialize (cachevalue, returnType, elementClass); }}} catch (throwable throwable) {logger.error ("", throwable); } Retour Résultat; } / ** * Effacer le cache avant que la méthode ne soit appelée, puis appelez la méthode commerciale * @param joinpoint * @return * @throws throwable * * / @around ("cacheevict ()") Objet public EvictCache (ProcedingJoinppoint Point) lance le throwsSSIGNATURE {// Obtenir la méthode de la méthode proxy. // Obtenez la liste des paramètres de la méthode modifiée par l'objet facette [] args = joinpoint.getargs (); // Obtenez l'annotation sur la méthode proxy Cacheevict CE = Method.getAnnotation (cacheevict.class); // Obtenez la valeur clé analysée par El String key = parsekey (ce.key (), méthode, args); // Obtenez le nom du cache de l'annotation String name = ce.value (); // effacer le cache Cachefactory.Cachedel correspondant (nom, clé); return joinpoint.proceed (args); } / ** * Obtenez la touche cachée * Clé définie sur l'annotation, prenant en charge les expressions Spel * @return * / private String parsekey (String Key, Method Method, Object [] args) {if (stringUtils.isempty (key)) return null; // Obtenez la liste des noms de paramètre de la méthode interceptée (en utilisant la bibliothèque de classe de support Spring) localVariabEtableParameterNameDameDiscover u = new localVariabEtableParameterNameDaMiscoverer (); String [] paranamearr = u.getParameTernames (méthode); // Utilisez Spel pour l'analyse clé ExpressionPaSer Parser = new SpexpressionPaSer (); // Spel Context StandardEvaluationContext context = new StandardEvaluationContext (); // Mettez les paramètres de méthode dans le contexte de Spel pour (int i = 0; i <paranamearr.length; i ++) {context.setVariable (paranamearr [i], args [i]); } return parser.parseExpression (key) .getValue (context, string.class); } // serialize private String serialize (objet obj) {String result = null; try {result = jsonUtil.serialize (obj); } catch (exception e) {result = obj.toString (); } Retour Résultat; } // désérialialiser l'objet privé désérialize (String str, class Clazz) {objet result = null; try {if (Clazz == JSONObject.class) {result = new JSONObject (str); } else if (Clazz == JSONArray.class) {result = new JSONArray (str); } else {result = jsonutil.deserialize (str, clazz); }} catch (exception e) {} return result; } // désérialisation, listes de support <xxx> objet privé Desérialize (String str, class Clazz, class elementClass) {objet result = null; try {if (Clazz == JSONObject.class) {result = new JSONObject (str); } else if (Clazz == JSONArray.class) {result = new JSONArray (str); } else {result = jsonutil.deserialize (str, Clazz, elementClass); }} catch (exception e) {} return result; } public void setCacheenable (boolean cacheenable) {this.cacheenable = cacheenable; }}L'interface ci-dessus utilise une variable cachenable pour contrôler s'il faut utiliser le cache. Afin d'atteindre un accès transparent à Springboot, il est nécessaire d'être contrôlé par l'annotation native @enablecaching. Ici, j'utilise un auditeur chargé par le conteneur à ressort, puis je trouve dans l'auditeur s'il y a une classe modifiée par l'annotation @enablecaching. Si c'est le cas, procurez-vous l'objet MultiCacheSpect du conteneur à ressort, puis définissez Cacheenable sur true. Cela permettra un accès transparent à Springboot. Je me demande s'il existe un autre moyen élégant pour les amis? Bienvenue à communiquer! La classe d'auditeur est la suivante
import com.xuanwu.apaas.core.multicache.cachefactory; import com.xuanwu.apaas.core.multicache.multicAceAsspect; import org.springframework.cache.annotation.enablecaching; import org.springframework.context.applicationListener; import org.springframework.context.event.contextreFreshEdEvent; import org.springframework.sterereotype.Component; importation java.util.map; / ** * Utilisé pour découvrir s'il y a une annotation pour activer le cache dans le projet une fois le chargement de ressort terminé @EnableCaching * @Author Rongdi * / @Component public class ContextreFreshEdListener implémente ApplicationListener Spring Container pour éviter l'occurrence de deux appels (le chargement MVC déclenchera également une fois) if (event.getApplicationContext (). GetParent () == null) {// Obtenez toutes les classes modifiées par @EnableCaching Annotation Map <String, objet> beans = event.getApplicationContext (). if (beans! = null &&! beans.isempty ()) {multicacheSpect multicache = (multicacheSpect) event.getApplicationContext (). getBean ("multicacheSpect"); multicache.setCacheenable (true); }}}}}Pour obtenir un accès transparent, nous devons également considérer comment EHCache multi-points est cohérent avec le cache Redis lors du déploiement d'EhCache multi-points. Dans les applications normales, Redis convient généralement au cache centralisé à long terme, et EHCACH convient au cache local à court terme. Supposons qu'il existe maintenant des serveurs A, B et C, A et B Déployer les services commerciaux, et C déploie les services Redis. Lorsqu'une demande entre, l'entrée frontale, qu'il s'agisse d'un logiciel de chargement tel que LVS ou NGINX, transférera la demande à un serveur spécifique. En supposant qu'il est transmis au serveur A et un certain contenu est modifié, et ce contenu est disponible à la fois dans Redis et Ehcache. À l'heure actuelle, le cache ehcache du serveur A et Redis du serveur C sont plus faciles à contrôler si le cache n'est pas valide ou supprimé. Mais comment contrôler l'Ehcache du serveur B pour le moment? La méthode couramment utilisée consiste à utiliser le mode d'abonnement publié. Lorsque vous devez supprimer le cache, vous publiez un message sur une chaîne fixe. Ensuite, chaque serveur commercial s'abonne à ce canal. Après avoir reçu le message, vous supprimez ou expirez le cache ehcache local (il est préférable d'utiliser l'expiration, mais Redis ne prend actuellement en charge que les opérations expirées sur la clé. Il n'y a aucun moyen d'exploiter l'expiration des membres dans la carte sous la clé. Si vous devez forcer l'expiration Moins écrit ici. En résumé, le processus consiste à mettre à jour un certain morceau de données, à supprimer d'abord le cache correspondant dans Redis, puis à publier un message avec un cache non valide dans un certain canal de redis. Le service commercial local s'abonne au message de ce canal. Lorsque le service commercial reçoit ce message, il supprime le cache ehcache local. Les différentes configurations de redis sont les suivantes.
Importer com.fasterxml.jackson.annotation.jsonAutoDetect; import com.fasterxml.jackson.annotation.propertyaccessor; import com.fasterxml.jackson.databind.objectmapper; Importer com.xuanwu.apaas.core.multicache.subscriber.messagesubscriber; import org.springframework.cache.cacheManager; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.data.redis.cache.rediscacheManager; import org.springframework.data.redis.connection.redisconnectionfactory; import org.springframework.data.redis.core.redistemplate; import org.springframework.data.redis.core.stringRedistEmplate; import org.springframework.data.redis.Listener.Patterntopic; import org.springframework.data.redis.Listener.redisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.sessageListEnerAdapter; import org.springframework.data.redis.serializer.jackson2jsonredisserializer; @Configuration classe publique Redisconfig {@bean public cacheManager CacheManager (reidemplate reistetemplate) {re-discacheManager rcm = new rediscacheManager (reistetemplate); // Définir le temps d'expiration du cache (secondes) RCM.SetDefaultExpiration (600); retour RCM; } @Bean public Redistetemplate <String, String> reidemplate (redisconnectionfactory factory) {stringRedistemplate template = new StringRedIstemplate (factory); Jackson2jsonredisserializer jackson2jsonredisserializer = new Jackson2jsonRisserializer (object.class); ObjectMapper om = new ObjectMapper (); om.setvisibilité (PropertyAccessor.all, jsonautodetect.visibilité.Any); OM.EnabledEfaultTyping (objectMapper.defaultTyping.Non_Final); jackson2jsonredisserializer.setObjectMapper (OM); Template.SetValueLeSerializer (jackson2jsonredisserializer); template.afterpropertiseset (); modèle de retour; } / ** * Redis Message Écouteur Container * Vous pouvez ajouter plusieurs écouteurs Redis qui écoutent différents sujets. Vous n'avez qu'à lier l'écouteur de messages et le processeur d'abonnement à message, et l'auditeur de messages * Appeler les méthodes liées au processeur d'abonnement à message à travers la technologie de réflexion pour certains traitements commerciaux * @param connexionfactory * @param écouteurdapter * @return * / @Bean Public RedismeMessageListenderContainer Container (Redisconnection ConnectionFactory, MessageMelisteDapter AowenerAdapter) {ConnectionFactory, messageListeRadapter AutonderApter) {Connecte RedisMessageListenderConainer Container = Nouvelle RedismeMessageListenerContainer (); contener.setConnectionFactory (ConnectionFactory); // Abonnez-vous à un canal contener.AddMessageListener (audinerAdapter, new PatternTopic ("redis.unCache")); // Ce conteneur peut ajouter plusieurs conteneurs de retour MessageListener; } / ** * Adaptateur d'écouteur de messages, lie le processeur de messages et utilise la technologie de réflexion pour appeler les méthodes commerciales du processeur de messages * @Param Receiver * @return * / @bean messageListEnerAdapter écouteurradapter (messubscriver) {// Cet endroit est pour passer une méthode de réflexion sur "Retour pour le nouveau messageListeDapter MessageListEnerAdapter (récepteur, "manche"); }}La classe de publication de messages est la suivante:
import com.xuanwu.apaas.core.multicache.cachefactory; import org.apache.commons.lang3.stringutils; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.sterereotype.Component; @Component public class MessageSubscriber {private static final logger logger = loggerfactory.getLogger (messuSubscriber.class); @Autowired Private Cachefactory Cachefactory; / ** * Après avoir reçu le message de l'abonnement redis, le cache d'Ehcache est invalidé * @param que le format de message est name_key * / public void handle (String Message) {logger.debug ("redis.ehcache:" + message); if (stringUtils.isempty (message)) {return; } String [] strs = message.split ("#"); String name = strs [0]; String key = null; if (str.length == 2) {key = strs [1]; } cachefactory.ehdel (nom, key); }}Les classes de cache d'opération spécifiques sont les suivantes:
Import com.xuanwu.apaas.core.multicache.publisher.MessagePublisher; Importer net.sf.ehcache.cache; importer net.sf.ehcache.cacheManager; importation net.sf.ehcache.element; import org.apache.commons.lang3.stringutils; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.redisconnectionfailureException; import org.springframework.data.redis.core.hashoperations; import org.springframework.data.redis.core.redistemplate; import org.springframework.sterereotype.Component; import java.io.inputStream; / ** * Section de cache à plusieurs niveaux * @author rongdi * / @component public class cachefactory {private static final logger = loggerfactory.getLogger (cachefactory.class); @Autowired Private Redemplate Redemplate; @Autowired Private MessagePublisher MessagePublisher; Cachemanager privé Cachemanager; public cachefactory () {inputStream est = this.getClass (). getResourceSstream ("/ ehcache.xml"); if (is! = null) {cacheManager = cacheManager.Create (is); }} public void cachedel (nom de chaîne, clé de chaîne) {// supprimer le cache correspondant à redis; // supprime le cache ehcache local, qui n'est pas nécessaire, et l'abonné supprimera // ehdel (nom, clé); if (cacheManager! = null) {// Publier un message indiquant au service souscrit que le cache est invalide MessagePublisher.publish (nom, key); }} public String ehget (nom de chaîne, clé de chaîne) {if (cacheManager == null) return null; Cache cache = cacheManager.getCache (name); if (cache == null) renvoie null; cache.acquireReadLockOnkey (clé); essayez {élément ele = cache.get (key); if (ele == null) renvoie null; return (string) ele.getObjectValue (); } enfin {cache.releSeReadLockOnkey (key); }} public String Redisget (String Name, String Key) {Hashoperations <String, String, String> Opera = reditemplate.opsForHash (); essayez {return opera.get (name, key); } catch (redisconnectionfailureException e) {// La connexion échoue, aucune erreur n'est lancée, et le logger.Error ("Connect Redis Error", E); retourner null; }} public void ehput (nom de chaîne, clé de chaîne, valeur de chaîne) {if (cacheManager == null) return; if (! cacheManager.Cacheexists (nom)) {cacheManager.addcache (name); } Cache cache = cacheManager.getCache (name); // obtient le verrouillage d'écriture sur la clé, les différentes clés ne se affectent pas, similaires à synchronisées (key.intern ()) {} cache.acquirewritelockonkey (key); try {cache.put (nouvel élément (clé, valeur)); } Enfin {// Libérez le cache de verrouillage de l'écriture.releaseWriteLockOnkey (KEY); }} public void redisput (nom de chaîne, clé de chaîne, valeur de chaîne) {hashoperations <string, string, string> opera = reidemplate.opsforhash (); essayez {operator.put (name, key, valeur); } catch (redisconnectionfailureException e) {// La connexion a échoué, aucune erreur n'a été lancée, et le logger.Error ("Connect Redis Error", E); }} public void ehdel (nom de chaîne, clé de chaîne) {if (cacheManager == null) return; if (cacheManager.Cacheexists (name)) {// Si la clé est vide, supprimez directement en fonction du nom du cache if (stringUtils.isempty (clé)) {cacheManager.RemoveCache (name); } else {cache cache = cacheManager.getCache (name); cache.remove (clé); }}} public void redisdel (nom de chaîne, clé de chaîne) {hashoperations <string, string, string> opera = reidemplate.opsforhash (); essayez {// si la touche est vide, supprimez if (stringUtils.isempty (clé)) {redetemplate.delete (name); } else {opera.delete (name, key); }} catch (redisconnectionfailureException e) {// La connexion a échoué, aucune erreur n'a été lancée, et le logger.Error ("Connect Redis Error", E); }}}La classe d'outils est la suivante
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; import org.apache.commons.lang3.stringutils; import org.json.jsonArray; import org.json.jsonObject; import java.util. *; classe publique JSonUtil {mappeur d'objet statique statique privé; statique {mapper = new ObjectMapper (); MAPPER.CONFIGURE (DeserializationFeature.fail_on_unknown_properties, false); }! } return Mappe.WriteValueAsString (OBJ); } / ** La désérialisation avec des génériques, telles que la désérialisation d'un jsonArray dans la liste <utilisateur> * / public static <t> t désérialize (String JSontr, class <?> CollectionClass, classe <?> ... elementClasses) lance une exception {javatype javatype = mappper.gettypefactory). return mapper.readvalue (jsonstr, javatype); } / ** * désérialiser la chaîne JSON dans un objet * @param src La chaîne JSON à désérialiser * @param t le type de classe de l'objet désérialisé en * @return * @throws exception * / public static <T> t désérialize (String src, class <t> t) lance une exception (src == not être nul "); } if ("{}". equals (src.trim ())) {return null; } return mappPer.readValue (src, t); }}Pour utiliser le cache spécifiquement, faites simplement attention aux annotations @Cacheable et @Cacheevict, et soutenez également les expressions Spring EL. De plus, le nom de cache représenté par l'attribut de valeur ici n'a pas le problème mentionné ci-dessus. Différents caches peuvent être isolés en utilisant la valeur. Les exemples sont les suivants
@Cacheable (value = "bo", key = "# session.productVersionCode + '' + # session.tenantcode + '' + # objectcode") @ cacheevict (value = "bo", key = "# session.productVerverOcode + '' + # session.tenantcode + '' + # objectcode")
J'ai joint le package de dépendance principal
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.