O cache do trampolim é usado no trabalho, o que é bastante conveniente de usar. Ele apresenta diretamente pacotes de dependência de cache, como Redis ou Ehcache, e os pacotes de dependência inicial de caches relacionados e, em seguida, adiciona a anotação @enableCaching à aula de inicialização e, em seguida, você pode usar @cacable e @cacheevict para usar e excluir o cache sempre que necessário. Isso é muito simples de usar. Acredito que aqueles que usaram o Springboot Cache tocam, então não vou dizer mais aqui. A única desvantagem é que o Springboot usa integração de plug-in. Embora seja muito conveniente usar, quando você integra o Ehcache, você usa o Ehcache e quando integra o Redis, usa o Redis. Se você deseja usar os dois juntos, o ehcache é usado como cache de nível 1 local e redis é usado como um cache de nível 2 integrado. Até onde eu sei, é impossível alcançar o método padrão (se houver um especialista que possa implementá -lo, me dê alguns conselhos). Afinal, muitos serviços exigem implantação de vários pontos. Se você escolher o Ehcache sozinho, poderá perceber bem o cache local. No entanto, se você compartilhar o cache entre várias máquinas, levará tempo para causar problemas. Se você escolher o Cache Redis centralizado, porque precisará ir para a rede sempre que obtiver dados, sempre sentirá que o desempenho não será muito bom. Este tópico discute principalmente como integrar perfeitamente o ehcache e o redis como caches de primeiro e segundo nível com base no Springboot e realizar a sincronização do cache.
Para não invadir o método de cache original do Springboot, eu defini duas anotações relacionadas ao cache aqui, como segue
@Target ({elementType.method}) @retention (retentionPolicy.Runtime) public @Interface Cacheable {string value () padrão ""; String key () padrão ""; // classe de classe genérica classe <?> Type () excepção padrão.class; } @Target ({elementType.method}) @retention (retentionPolicy.Runtime) public @Interface cacheevict {string value () padrão ""; String key () padrão ""; }Como as duas anotações acima são basicamente as mesmas que as anotações em cache na primavera, mas alguns atributos usados com pouca frequência são removidos. Falando nisso, me pergunto se algum amigo percebeu que, quando você usa o Redis Cache sozinho no Springboot, os atributos de valor anotados pelo cache e o Cacheevict se tornam uma chave de valor do zset no Redis, e o zset ainda está vazio, como @cachable (value = "cache1", key = "key1"). Sob circunstâncias normais, o Cache1 -> MAP (KEY1, Value1) deve aparecer no Redis, onde o Cache1 é usado como nome do cache, mapa como valor do cache e chave como chave no mapa, que pode efetivamente isolar caches sob diferentes nomes de cache. Mas, de fato, existem cache1 -> vazio (zset) e key1 -> value1, dois pares independentes de valor -chave. O experimento descobriu que o cache sob diferentes nomes de cache é completamente compartilhado. Se você estiver interessado, pode tentar. Ou seja, esse atributo de valor é na verdade uma decoração, e a singularidade da chave é garantida apenas pelo atributo chave. Só posso pensar que este é um bug na implementação do cache da primavera, ou ele foi projetado especificamente (se você souber o motivo, me dê alguns conselhos).
De volta ao tópico, com anotação, também há aula de processamento de anotação. Aqui eu uso a seção da AOP para interceptação, e a implementação nativa é realmente semelhante. A classe de processamento da seção é a seguinte:
importar com.xuanwu.apaas.core.multicache.annotation.cacheevict; importar com.xuanwu.apaas.core.multicache.annotation.cacheable; importar com.xuanwu.apaas.core.utils.jsonutil; importar org.apache.commons.lang3.StringUtils; importar org.aspectj.lang.proedingJoinPoin; importar org.aspectj.lang.annotation.around; importar org.aspectj.lang.annotation.around; importar org.aspectj.lang.annotation.aspect; importar org.aspectj.lang.annotation.pointcut; importar org.aspectj.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.localvariAbleTableParameternamediscoverer; importar org.springframework.expression.expressionParser; importar org.springframework.expression.spel.standard.spelexpressionParser; importar org.springframework.expression.spel.support.StandardEvaluationContext; importar org.springframework.tereotype.component; importar java.lang.reflect.method; / *** Seção de cache multinível* @author rongdi*/ @aspect @component public class Multicacheaspect {private estático Logger Logger = LoggerFactory.getLogger (multicacheaspecto.class); @AUTOWIRED CACHEFACTORY CACHEFACTORY; // aqui o ouvinte é inicializado através de um contêiner e o interruptor de cache é controlado de acordo com o @enablecaching anotação de cacheene de anotação @enablecaching; @PointCut ("@anotação (com.xuanwu.apaas.core.multicache.annotation.cacheable)") public void Cacheaspect () {} @pointcut ("@annotation (com.xuanwu.apaas.core.multicache.annotation.cacheevict)" @Around ("cacheBleaspecpe ()") public Object Cache (ProceedingJoinPoint Junção) {// Obtenha a lista de parâmetros do método que é modificado pelo objeto FACET [] args = junçãoPoint.getargs (); // resultado é o resultado final de retorno do resultado do objeto Método = null; // Se o cache não estiver ativado, chame diretamente o método de processamento para retornar se (! Cacheenable) {try {resultado = junçãoPoint.proeced (args); } catch (throwable e) {Logger.error ("", e); } resultado de retorno; } // Obtenha o tipo de retorno tipo da classe de proxy Classe returnType = ((MethodSignature) junçãoPoint.getSignature ()). GetreturnType (); // Obtenha o método do proxy Método Método = ((MethodSignature) junçãoPoint.getSignature ()). GetMethod (); // Obtenha o comentário sobre o método proxy cache CA = Method.getAnnotation (cacheable.class); // obtém o valor da chave analisado por el string key = parsekey (ca.key (), método, args); Classe <?> ElementClass = ca.type (); // Obtenha o nome do cache no nome da string de anotação = ca.Value (); tente {// primeiro obtenha dados do ehcache string cachevalue = cachefactory.ehget (nome, chave); if (stringUtils.isEmpty (Cachevalue)) {// Se não houver dados no ehcache, obtenha dados de Redis Cachevalue = Cachefactory.redisget (nome, chave); if (stringUtils.isEmpty (Cachevalue)) {// Se não houver dados no ehcache, obtenha dados de Redis Cachevalue = Cachefactory.redisget (nome, chave); if (stringUtils.isEmpty (Cachevalue)) {// se não houver dados no redis //, chame o método de negócios para obter o resultado do resultado = jun juntpoint.proeced (args); // serialize o resultado e coloque -o no Redis Cachefactory.redisput (nome, chave, serialização (resultado)); } else {// se os dados puderem ser obtidos em redis // deserialize os dados obtidos no cache e retornar if (elementClass == excepcion.class) {resultado = Deserialize (cachevalue, returnType); } else {resultado = Deserialize (Cachevalue, ReturnType, ElementClass); }} // serialize o resultado e coloque -o no ehcache cachefactory.ehput (nome, chave, serialize (resultado)); } else {// Deserialize os dados obtidos no cache e retorne if (elementClass == excepcion.class) {resultado = Deserialize (Cachevalue, ReturnType); } else {resultado = Deserialize (Cachevalue, ReturnType, ElementClass); }}} Catch (arremesso de arremesso) {Logger.error ("", Throwable); } resultado de retorno; } / ** * Limpe o cache antes que o método seja chamado e, em seguida, chame o método de negócios * @param junção * @return * @throws throwable * * / @around ("Cacheevict ()") public Object despetcache (prossegundo o ponto de junção) lança throwable {// obting o método proxy ((métodsSign)) // Obtenha a lista de parâmetros do método modificado pelo objeto FACET [] args = junçãoPoint.getargs (); // Obtenha a anotação no método proxy Cacheevict CE = Method.getAnnotation (Cacheevict.class); // obtém o valor da chave analisado por el string key = parsekey (ce.key (), método, args); // Obtenha o nome do cache no nome da string de anotação = ce.value (); // limpe o cache cache correspondente.cachedel (nome, chave); retornar junhopoint.proeced (args); } / ** * Obtenha a tecla em cache * definida na anotação, suportando expressões de spel * @return * / private string parsekey (chave de string, método, método, objeto [] args) {if (stringUtils.isEmpty (key)) retornar nulo; // Obtenha a lista de nomes de parâmetro do método interceptado (usando a biblioteca da classe de suporte da mola) LocalVariAbleTableParameterNamediscoverer u = new LocalVariAbleTableParameterNamesCoverer (); String [] paranamearr = u.getParameterNames (método); // Use Spel para Parsing de chavesPARSER PARSER = new SpelExpressionParser (); // SPEL CONTECT STANDARDEVALUETIONCONTEXT CONTECTTO = NOVA STANDARDEVALUETIONCONTEXT (); // Coloque os parâmetros do método no contexto do spel for (int i = 0; i <paranamearr.length; i ++) {context.setVariable (paranamearr [i], args [i]); } return parser.parseexpression (key) .getValue (context, string.class); } // serializar seriar a string privada serialize (object obj) {string resultado = null; tente {resultado = jsonutil.serialize (obj); } catch (Exceção e) {resultado = obj.toString (); } resultado de retorno; } // Deserialize o objeto privado Deserialize (String str, classe clazz) {resultado do objeto = null; tente {if (clazz == jsonObject.class) {resultado = novo jsonObject (str); } else if (clazz == jsonArray.class) {resultado = new jsonArray (str); } else {resultado = jsonutil.DeSerialize (str, clazz); }} Catch (Exceção e) {} Retorno Resultado; } // Deserialização, lista de suporte <XXX> Objeto privado Deserialize (String str, classe clazz, classe ElementClass) {resultado do objeto = null; tente {if (clazz == jsonObject.class) {resultado = novo jsonObject (str); } else if (clazz == jsonArray.class) {resultado = new jsonArray (str); } else {resultado = jsonutil.Deserialize (str, clazz, elementClass); }} Catch (Exceção e) {} Retorno Resultado; } public void setCacheenable (Cacheenable boolean) {this.cacheenable = Cacheenable; }}A interface acima usa uma variável Cacheenable para controlar se deve usar o cache. Para obter acesso contínuo ao Springboot, é necessário ser controlado pela anotação @enableCaching nativa. Aqui eu uso um ouvinte carregado pelo recipiente de mola e encontro no ouvinte se existe uma classe modificada pela anotação @enableCaching. Nesse caso, obtenha o objeto multicacacheaspecto do recipiente de mola e defina o cacheenable como true. Isso permitirá acesso contínuo ao Springboot. Gostaria de saber se há mais uma maneira elegante para os amigos? Bem -vindo ao comunicado! A aula do ouvinte é a seguinte
importar com.xuanwu.apaas.core.multicache.cachefactory; importar 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.tereotype.component; importar java.util.map; / ** * Usado para descobrir se há anotação para ativar o cache no projeto após a conclusão do carregamento da mola @enableCaching * @Author Rongdi */ @Component public classe contextReFreshedListener implementa o aplicativo (contextRefreshEvent> @Override Public Void onPplicing (contextExtreTeVent) @Override « Contêiner para impedir a ocorrência de duas chamadas (o carregamento do MVC também será desencadeado uma vez) se (event.getApplicationContext (). getParent () == null) {// obtém todas as classes modificadas por @enableCaching mapa de anotação <string, objeto> beans = EventApplicationContext (). GetBeanTation; if (feijão! = null &&! beans.isEmpty ()) {multicacheaspect multicache = (multicacheaspect) event.getApplicationContext (). getBean ("multicacheaspecto"); multicache.setCacheenable (true); }}}}}Para obter acesso contínuo, também precisamos considerar como o ehcache de vários pontos é consistente com o cache do Redis ao implantar o ehcache de vários pontos. Em aplicações normais, o Redis geralmente é adequado para o cache centralizado a longo prazo, e o Ehcache é adequado para o cache local de curto prazo. Suponha que agora existam servidores A, B e C, A e B implantam serviços comerciais e C implanta serviços Redis. Quando uma solicitação entra, a entrada front-end, seja software de carga como LVS ou NGINX, encaminhará a solicitação a um servidor específico. Supondo que seja encaminhado para o servidor A e um determinado conteúdo é modificado e esse conteúdo está disponível no Redis e no Ehcache. No momento, o cache Ehcache do servidor A e Redis do servidor C são mais fáceis de controlar se o cache é inválido ou excluído. Mas como controlar o ehcache do servidor B neste momento? O método comumente usado é usar o modo de assinatura de publicação. Quando você precisa excluir o cache, você publica uma mensagem em um canal fixo. Em seguida, cada servidor de negócios assina este canal. Depois de receber a mensagem, você exclua ou expira o cache local do ehcache (é melhor usar o expirado, mas atualmente o Redis suporta apenas operações expiradas na chave. Não há como operar a expiração dos membros no mapa sob a chave. Menos escreve aqui, por conveniência, eles excluirão diretamente o cache). Em resumo, o processo é atualizar uma determinada peça de dados, primeiro excluir o cache correspondente em Redis e depois publicar uma mensagem com cache inválido em um certo canal de redis. O Serviço de Negócios Local assina a mensagem deste canal. Quando o serviço comercial recebe esta mensagem, ele exclui o cache local do ehcache. As várias configurações de Redis são as seguintes.
importar com.fasterxml.jackson.annotation.jsonAutodeTect; importar com.fasterxml.jackson.annotation.propertyAccessor; importar com.fasterxml.jackson.databind.objectmapper; importar com.xuanwu.apaas.core.multicache.subScrincier.messagesubScriptr; 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.MessaGelisteRadapter; importar org.springframework.data.redis.serializer.jackson2jsonRedisserializer; @Configuration public class Redisconfig {@Bean Public Cachemanager Cachemanager (Redistemplate Redistemplate) {Rediscachemanager rcm = new Rediscachemanager (Redistemplate); // Definir tempo de expiração do cache (segundos) rcm.SetDefaultExpiration (600); retornar RCM; } @Bean public Redistemplate <String, String> Redistemplate (RedisconnectionFactory Factory) {StringReDistemplate modelo = new StringReDistemplate (Factory); Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new Jackson2JSONredisseRializer (Object.Class); ObjectMapper OM = new ObjecjotMapper (); om.setVisibility (PropertyAccessor.all, jsonAutodetect.visibility.any); om.enabledfaulttyping (objectmapper.defaulttyping.non_final); Jackson2jsonRedisserializer.setObjectMapper (OM); template.setValueSerializer (Jackson2JsonRedisSerializer); template.afterpropertiesset (); modelo de retorno; } /*** Redis Mensagem Listener Container* Você pode adicionar vários ouvintes redis que ouvem tópicos diferentes. Você só precisa vincular o ouvinte da mensagem e o processador de assinatura de mensagem correspondente, e o ouvinte de mensagens * Chamando métodos relacionados do processador de assinaturas de mensagens através da tecnologia de reflexão para algum processamento de negócios * @param ConnectionFactory * @param listeRAdApter * @return */ @Bean RedissessagelistErContainer Container (RedisconnectionFactory Connection, MessagelSagELSagELISTERAner (RedisconnectionFactory RedismessagelistEnerContainer Container = new RedismEssagelistEnerContainer (); container.setConnectionFactory (ConnectionFactory); // Inscreva -se em um contêiner de canal. // Este contêiner pode adicionar vários contêineres de retorno do Messagelistener; } /** * Adaptador do ouvinte de mensagens, liga o processador de mensagens e usa a tecnologia de reflexão para chamar os métodos de negócios do processador de mensagens * @Param Receiver * @return * /@Bean MessagelisteneRadapter luterAdApter (MessageScript Receiver) {// Este lugar é para passar um MessagelisterataPapter para o MessagelScorret) MessagelistenerAdapter (receptor, "Handle"); }}A classe de publicação de mensagens é a seguinte:
importar 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.tereotype.component; @Component public Class MessageSubScript {private Static Final Logger Logger = LoggerFactory.getLogger (MessageSubScript.class); @AUTOWIRED CACHEFACTORY CACHEFACTORY; / *** Depois de receber a mensagem da assinatura Redis, o cache do ehcache é invalidado* @param mensagem de mensagem é name_key*/ public void handle (string message) {logger.debug ("redis.ehcache:"+mensagem); if (stringUtils.isEmpty (mensagem)) {return; } String [] strs = message.split ("#"); Nome da string = strs [0]; String key = null; if (strs.length == 2) {key = strs [1]; } Cachefactory.ehdel (nome, chave); }}As classes de cache de operação específicas são as seguintes:
importar com.xuanwu.apaas.core.multicache.publisher.messagePublisher; importação net.sf.ehcache.cache; importação net.sf.ehcache.cachemanager; importação 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.tereotype.component; importar java.io.inputStream; / *** Seção de cache de vários níveis* @author rongdi*/ @component public class Cachefactory {private static final Logger Logger = LoggerFactory.getLogger (Cachefactory.class); @Autowired Private Redistemplate Redistemplate; @Autowired MessagePublicer MessagePublisher; Cachemanager privado Cachemanager; public Cachefactory () {inputStream IS = this.getClass (). getResourceasStream ("/ehcache.xml"); if (é! = null) {cachemanager = cachemanager.create (is); }} public void cachedel (nome da string, chave de string) {// exclua o cache correspondente a redis; // Exclua o cache local do ehcache, que não é necessário, e o assinante excluirá // ehdel (nome, chave); if (cachemanager! = null) {// publica uma mensagem informando ao serviço inscrito que o cache é inválido messagePublisher.publish (nome, chave); }} public string ehget (nome da string, chave de string) {if (cachemanager == null) retorna null; Cache cache = cachemanager.getCache (nome); if (cache == null) retorna nulo; cache.AcquireReadlockonKey (chave); tente {elemento ele = cache.get (chave); if (ele == null) retornará nulo; return (string) ele.getObjectValue (); } finalmente {cache.ReleasEreadlockonKey (chave); }} public string redisget (nome da string, chave de string) {hashoperations <string, string, string> opera = redistemplate.opsforhash (); tente {return opera.get (nome, chave); } catch (RedisconnectionFailureException e) {// A conexão falha, nenhum erro é lançado e o logger.error ("Connect Redis Error", e); retornar nulo; }} public void ehput (nome da string, tecla String, String Value) {if (Cachemanager == null) return; if (! cachemanager.cacheexists (nome)) {cachemanager.addcache (nome); } Cache cache = cachemanager.getCache (nome); // Obtenha o bloqueio de gravação na chave, as teclas diferentes não se afetam, semelhantes a sincronizadas (key.intern ()) {} cache.acquireWritelockonkey (key); tente {cache.put (novo elemento (chave, valor)); } finalmente {// libere o cache de bloqueio de gravação.ReleaseWriteLockonKey (chave); }} public void redisput (nome da string, tecla String, String value) {hashoperations <string, string, string> opera = redistemplate.opsforhash (); tente {operator.put (nome, chave, valor); } catch (RedisconnectionFailureException e) {// A conexão falhou, nenhum erro foi lançado e o logger.error ("Connect Redis Error", e); }} public void ehdel (nome da string, chave de string) {if (cachemanager == null) return; if (cachemanager.cacheexists (nome)) {// Se a chave estiver vazia, exclua diretamente de acordo com o nome do cache if (stringUtils.isEmpty (key)) {cachemanager.removecache (nome); } else {cache cache = cachemanager.getCache (nome); cache.remove (chave); }}} public void redisdel (nome da string, chave de string) {hashoperations <string, string, string> opera = redistemplate.opsforhash (); tente {// se a chave estiver vazia, exclua se (stringutils.isEmpty (key)) {redistemplate.delete (nome); } else {opera.delete (nome, chave); }} catch (RedisconnectionFailureException e) {// A conexão falhou, nenhum erro foi lançado e o logger.error ("Connect Redis Error", e); }}}A classe de ferramentas é a seguinte
importar com.fasterxml.jackson.core.type.typereference; importar com.fasterxml.jackson.databind.deserializationfeature; importar com.fasterxml.jackson.databind.javatype; importar com.fasterxml.jackson.databind.objectmapper; importar org.apache.commons.lang3.StringUtils; importar org.json.jsonArray; importar org.json.jsonObject; importar java.util.*; classe pública jsonutil {private estático objectmapper mapper; static {mapper = new objectMapper (); mapper.configure (deserializationfeature.fail_on_unknown_properties, false); } / ** * serialize o objeto em json * * @param obj objeto a ser serializado * @return * @throws exceção * / public static string serialize (objeto obj) lança exceção {if (obj == null) {tiro new ilegalargumentException ("obj não deve ser nulo"); } retornar mapper.WriteValuEasString (OBJ); } / ** Deserialização com genéricos, como a desertalização de um JSONArray na lista <suser>* / public static <t> t Deserialize (String jSonstr, classe <?> CollectionClass, classe <?> ... elementclasses) lança exceção {javatype Javatype = mapTyTeStTeStyPefstory). elementClasses); return mapper.readValue (JSonstr, Javatype); } / *** Deserialize a string json em um objeto* @param src A sequência JSON deve ser desserializada* @Param t O tipo de classe do objeto desserrinizado em* @return* @throws exceção* / public static <T> t DeSerialize (string src, classe <t> t) throws (se (se (Src) nulo"); } if ("{}". Equals (src.trim ())) {return null; } return mappper.readValue (src, t); }}Para usar o cache especificamente, basta prestar atenção às anotações @cacheable e @cacheevict e também apoiar as expressões da primavera. Além disso, o nome do cache representado pelo atributo de valor aqui não tem o problema mencionado acima. Diferentes caches podem ser isolados usando o valor. Exemplos são os seguintes
@Cacheable (value = "bo", key = "#session.productVersionCode+''+#session.tenantCode+''+#objectCode")@Cacheevict (value = "bo", key = "#session.productVersionCode+'+#session.tenantcode+' '+#object)
Anexou o pacote principal de dependência
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.