Кэш Springboot используется в работе, который довольно удобен в использовании. Он непосредственно вводит пакеты зависимостей к кешу, такие как Redis или Ehcache, и пакеты зависимостей стартера связанных кешей, а затем добавляет аннотация @enablecaching в класс запуска, а затем вы можете использовать @cachable и @cacheevict для использования и удаления кэша, где это необходимо. Это очень просто в использовании. Я считаю, что те, кто использовал Cache Springboot, будут играть, поэтому я не скажу здесь больше. Единственным недостатком является то, что Springboot использует интеграцию плагина. Хотя его очень удобно использовать, когда вы интегрируете ehcache, вы используете ehcache, а когда интегрируете Redis, вы используете Redis. Если вы хотите использовать оба вместе, Ehcache используется в качестве локального кэша уровня 1, а Redis используется в качестве интегрированного кэша уровня 2. Насколько я знаю, невозможно достичь метода по умолчанию (если есть эксперт, который может его реализовать, дайте мне несколько советов). В конце концов, многие услуги требуют развертывания многоточечных. Если вы выберете ehcache в одиночку, вы можете хорошо реализовать локальный кеш. Однако, если вы разделяете кэш между несколькими машинами, потребуется время, чтобы доставить проблемы. Если вы выберете централизованный кэш Redis, потому что вам нужно ходить в сеть каждый раз, когда получаете данные, вы всегда будете чувствовать, что производительность не будет слишком хорошей. В этой теме в основном обсуждается, как плавно интегрировать Ehcache и Redis в качестве кэша первого и второго уровня на основе Springboot, и реализовать синхронизацию кэша.
Чтобы не вторгаться в оригинальный метод кэша Springboot, я определил две аннотации, связанные с кэшем здесь, следующим образом
@Target ({elementtype.method}) @retention (armentpolicy.runtime) public @Interface cachable {string value () default ""; String key () default ""; // универсальный класс класса класса <?> Type () default exception.class; } @Target ({elementType.method}) @retention (armentpolicy.runtime) public @Interface cacheevict {string value () default ""; String key () default ""; }Поскольку вышеупомянутые две аннотации в основном такие же, как и кэшированные аннотации весной, но некоторые редко используемые атрибуты удаляются. Говоря об этом, мне интересно, заметили ли какие -либо друзья, что, когда вы используете только кэш Redis в Springboot, атрибуты значения, аннотируемые кэшируемыми и качеевиктами, фактически становятся ключом значения ZSET в Redis, и ZSET по -прежнему пуст, такой как @Cachable (value = "cache1", key = "key1"). При нормальных обстоятельствах cache1 -> map (key1, value1) должна отображаться в Redis, где Cache1 используется в качестве имени кэша, карта в качестве значения кэша и клавишу в качестве ключа в карте, который может эффективно изолировать кэши под разными именами кэша. Но на самом деле, есть cache1 -> umpt (zset) и key1 -> value1, две независимые пары клавиш. Эксперимент обнаружил, что кэш под разными именами кэша полностью общий. Если вам интересно, вы можете попробовать. То есть этот атрибут значения на самом деле является украшением, а уникальность ключа гарантируется только атрибутом ключа. Я могу только думать, что это ошибка в реализации кэша весны, или она была разработана специально (если вы знаете причину, пожалуйста, дайте мне несколько советов).
Вернемся к теме, с аннотацией, есть также класс обработки аннотаций. Здесь я использую раздел AOP для перехвата, и нативная реализация на самом деле похожа. Класс обработки раздела выглядит следующим образом:
Импорт com.xuanwu.apaas.core.multicache.annotation.cacheevict; импорт com.xuanwu.apaas.core.multicache.annotation.cachable; импорт com.xuanwu.apaas.core.utils.jsonutil; Импорт org.apache.commons.lang3.stringutils; Импорт org.aspectj.lang.proceedingjoinpoint; Импорт org.aspectj.lang.annotation.around; Импорт org.aspectj.lang.annotation.around; Импорт org.aspectj.lang.annotation.aspept; Импорт org.aspectj.lang.annotation.pointcut; Импорт org.aspectj.lang.reflect.methodsignature; Импорт org.json.jsonarray; Импорт org.json.jsonobject; Импорт org.slf4j.logger; Импорт org.slf4j.loggerfactory; Импорт org.springframework.beans.factory.annotation.autowired; Импорт org.springframework.core.localvariabletableparameternamediscoverer; Импорт org.springframework.expression.expressionParser; Импорт org.springframework.expression.spel.standard.spelexpressionParser; Импорт org.springframework.expression.spel.support.standardevaluationContext; Import org.springframework.stereotype.component; импортировать java.lang.reflect.method; / *** Раздел многоуровневого кэша* @author rongdi*/ @aspept @component public class multicacheaspect {private static final logger logger = loggerfactory.getlogger (multicacheaspect.class); @Autowired private cachefactory cachefactory; // Здесь слушатель инициализируется через контейнер, а кеш -переключатель управляется в соответствии с внешне настроенной @enablecaching аннотацией частной логины Cacheenable; @Pointcut ("@annotation (com.xuanwu.apaas.core.multicache.annotation.cachable)") public void cacheaspect () {} @pointcut ("@annotation (com.xuanwu.apaas.core.multicache.annotation.cacheevict) @Around ("cacheableaspect ()") public object Cache (Tootingjoinpoint joinpoint) {// Получить список параметров метода, который изменяется Facet объектом [] args = joinpoint.getargs (); // Результат является окончательным результатом возврата объекта метода result = null; // Если кэш не включен, напрямую вызовите метод обработки, чтобы вернуть if (! Cacheenable) {try {result = joinpoint.proceed (args); } catch (throwable e) {logger.error ("", e); } return Result; } // Получить тип возвращаемого значения метода прокси -метода returntype = ((methodignature) joinpoint.getSignature ()). GetReturnType (); // Получить метод прокси -метода метода = ((Methodignature) joinpoint.getSignature ()). GetMethod (); // Получить комментарий к прокси -методу CACHABLE CA = MEDICE.Getannotation (cachable.class); // Получение значения ключа, проанализированного с помощью el string key = parsekey (ca.key (), method, args); Класс <?> ElementClass = ca.type (); // Получить имя кэша из названия строки аннотации = ca.value (); try {// сначала получить данные из ehcache string cachevalue = cachefactory.ehget (name, key); if (stringutils.isempty (cachevalue)) {// Если в ehcache нет данных, получите данные из Redis cachevalue = cachefactory.redisget (name, key); if (stringutils.isempty (cachevalue)) {// Если в ehcache нет данных, получите данные из Redis cachevalue = cachefactory.redisget (name, key); if (stringUtils.isempty (cachevalue)) {// Если в Redis // Call // вызовите бизнес -метод, чтобы получить результат результата = joinpoint.proceed (args); // сериализовать результат и поместить его в redis cachefactory.redisput (имя, ключ, сериализовать (результат)); } else {// Если данные могут быть получены из Redis // Deserialization данные, полученные в кэше, и вернуть if (elementClass == exception.class) {result = deserialize (cachevalue, returntype); } else {result = deserialize (cachevalue, returntype, elementClass); }} // сериализуйте результат и поместите его в ehcache cachefactory.ehput (имя, ключ, сериализация (результат)); } else {// deserialize данные, полученные в кэше, и return if (elementClass == exection.class) {result = deserialize (cachevalue, returntype); } else {result = deserialize (cachevalue, returntype, elementClass); }}} catch (throwable throwable) {logger.error ("", throwable); } return Result; } / ** * Очистить кэш до того, как метод будет вызван, а затем вызовите бизнес -метод * @param joinpoint * @return * @Throws Throwable * * / @Around ("cacheevict ()") Общедоступный объект evictCache (ходатайство Joinpoint joinpoint) throwable {// Получить метод прокси -метода = (MethodingGinature) (MethodGinature). // Получить список параметров метода, измененный Facet объектом [] args = joinpoint.getargs (); // Получить аннотацию на прокси -методе Cacheevict ce = method.getannotation (cacheevict.class); // Получение значения ключа, проанализированного с помощью el string key = parsekey (ce.key (), method, args); // Получить имя кэша из названия строки аннотации = ce.value (); // очистить соответствующий кэш cachefactory.cachedel (имя, ключ); return jointpoint.proceed (args); } / ** * Получить клавишу кэшированного * клавиши, определенную на аннотации, поддерживая экспрессии SPEL * @return * / partice parsekey (string key, метод метода, объект [] args) {if (stringUtils.isempty (key)) return null; // Получить список имен параметров перехваченного метода (с использованием библиотеки классов поддержки Spring) LocalVariabletableParametRameDiscoverer u = new localvariabletable pparameternamediscoverer (); String [] paranamearr = u.getParameternames (метод); // Использовать SPEL для клавиш анализ ExpressionParser parser = new SpelexPressionParser (); // context spel StandardEvaluationContext Context = new StandardEvaluationContext (); // Поместите параметры метода в контекст SPEL для (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 (Object obj) {String result = null; try {result = jsonutil.serialize (obj); } catch (Exception e) {result = obj.toString (); } return Result; } // Deserialize Private Object Deserialize (String Str, Class Clazz) {Object 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; } // deserialization, список поддержки <xxx> частный объект Deserialize (String Str, Class Clazz, Class ElementClass) {Object 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; }}Приведенный выше интерфейс использует кахентскую переменную для управления, использовать ли кеш. Чтобы получить бесшовный доступ к Springboot, необходимо контролировать нативную аннотацию @enablecaching. Здесь я использую слушателя, загруженный пружинным контейнером, а затем нахожу в слушателе, есть ли класс, измененный аннотацией @enablecaching. Если это так, получите объект MultiCaheAspect из пружинного контейнера, а затем установите Cacheenable на True. Это обеспечит бесшовный доступ к Springboot. Интересно, есть ли еще более элегантный способ для друзей? Добро пожаловать, чтобы общаться! Класс слушателей выглядит следующим образом
Импорт com.xuanwu.apaas.core.multicache.cachefactory; Импорт com.xuanwu.apaas.core.multicache.multicacheaspect; Импорт org.springframework.cache.annotation.enableCaching; Импорт org.springframework.context.applicationListener; Импорт org.springframework.context.event.contextrefreshedevent; Import org.springframework.stereotype.component; импортировать java.util.map; / ** * Используется для обнаружения, существует ли аннотация для включения кэша в проекте после завершения весенней загрузки @enablecaching * @author rongdi */ @component public class contextrefreshedlistener реализует ApplicationListener <contextrefreshedevent> {@Override public opplicationEv (contextrefresevent> @override voad onaveVenteevent (contextresevent> rootevent {rootereshedevent> rootevent {contextresevent) rooteventevent). Контейнер, чтобы предотвратить возникновение двух вызовов (MVC загрузка также будет запускать один раз) if (event.getApplicationContext (). getParent () == null) {// Получить все классы, измененные @enableCaching Annotation Map <String, Object> Beans = Event.getApplicationContext (). GetBeanSwantation (enablecaching); if (beans! = null &&! beans.isempty ()) {multicaheaspect multicache = (multicaheaspepe) event.getapplicationcontext (). getbean ("multicaheasepepe"); multicache.setCacheenable (true); }}}}}Для достижения бесшовного доступа мы также необходимо рассмотреть, как многоточечный ehcache соответствует кэшу Redis при развертывании многоточечного Ehcache. В нормальных приложениях Redis, как правило, подходит для долгосрочного централизованного кэша, а Ehcache подходит для краткосрочного локального кеша. Предположим, что в настоящее время существуют серверы A, B и C, A и B развертывают бизнес -услуги, а C развертывает Redis Services. Когда вступает запрос, вход в интерфейс, будь то программное обеспечение для загрузки, такое как LVS или NGINX, будет пересылать запрос на конкретный сервер. Предполагая, что он перенаправлен на сервер A, и определенный контент изменен, и этот контент доступен как в Redis, так и в Ehcache. В настоящее время кэш EHCache сервера A и Redis сервера C проще контролировать, является ли кэш недействительным или удаленным. Но как контролировать EHCache сервера B в настоящее время? Обычно используемый метод заключается в использовании режима подписки Publish. Когда вам нужно удалить кэш, вы публикуете сообщение на фиксированном канале. Затем каждый бизнес -сервер подписывается на этот канал. Получив сообщение, вы удаляете или истекаете локальный кэш Ehcache (лучше всего использовать, но Redis в настоящее время поддерживает только операции с истекшим сроком действия на ключе. Невозможно управлять истечением срока действия участников в карте под ключом. Если вам нужно придумать срок действия, вы можете добавить временную метку, чтобы реализовать его. Однако, что может привести к тому, что все больше. пишет. Таким образом, процесс состоит в том, чтобы обновить определенную часть данных, сначала удалить соответствующий кэш в Redis, а затем опубликовать сообщение с недопустимым кешем в определенном канале Redis. Местная бизнес -служба подписывается на сообщение этого канала. Когда бизнес -служба получает это сообщение, он удаляет локальный кэш Ehcache. Различные конфигурации Redis заключаются в следующем.
Import com.fasterxml.jackson.annotation.jsonautodetect; Import com.fasterxml.jackson.annotation.propertyaccessor; Import com.fasterxml.jackson.databind.objectmapper; Импорт com.xuanwu.apaas.core.multicache.subscriber.messagesubscriber; Импорт org.springframework.cache.cachemanager; Импорт org.springframework.context.annotation.bean; Импорт org.springframework.context.annotation.configuration; Импорт org.springframework.data.redis.cache.rediscachemanager; Импорт org.springframework.data.redis.connection.redisconnectionFactory; Импорт org.springframework.data.redis.core.redistemplate; Импорт org.springframework.data.redis.core.stringredistemplate; Импорт org.springframework.data.redis.listener.patterntopic; Импорт org.springframework.data.redis.listener.redismessageListenerContainer; Import org.springframework.data.redis.listener.adapter.messageListenerAdapter; Import org.springframework.data.redis.serializer.jackson2jsonredisserializer; @Configuration public class redisconfig {@bean public cachemanager cachemanager (redistemplate redistemplate) {rediscachemanager rcm = new Rediscachemanager (Redistemplate); // Установить время истечения срока действия кэша (секунды) RCM.SetDefaultexpiration (600); вернуть RCM; } @Bean public redistemplate <string, string> redistemplate (redisconnectionFactory Factory) {stringRedistemplate template = new StringRedistemplate (Factory); Jackson2jsonredisserializer jackson2jsonredisserializer = new jackson2jsonredisserializer (object.class); ObjectMapper om = new ObjectMapper (); OM.SetVisibility (PropertyAccessor.All, JSONAutoDetect.visibuble.ONIY); om.enabledefaulttyping (objectmapper.defaulttyping.non_final); jackson2jsonredisserializer.setObjectMapper (OM); Template.SetValueSerializer (Jackson2JsonRedisserializer); Template.afterPropertiesset (); возвратный шаблон; } /*** redis message levesser container* Вы можете добавить несколько слушателей Redis, которые слушают разные темы. Вам нужно только привязать слушатель сообщений и соответствующий процессор подписки на сообщения, а также слушатель сообщений * вызывает соответствующие методы процессора подписки с помощью технологии отражения для некоторой бизнес -обработки * @param connectionfactory * @param helloarderAdapter * @return */ @bean public redismessagelistenercontainer (redisconcenectionFactory Connectory, MessageLeneenerApter ueardAraDapterArAtaRAPTERARATARATARAPTERAPTERAPTERAPTERAPTERAPTERAPTERAPTERAPTERAPTERAPTERAPTERERADAPTERERAPTER RedismessageListenerContainer Container = новый RedismessageListenerContainer (); Container.SetConnectionFactory (ConnectionFactory); // подписаться на канал контейнер. // Этот контейнер может добавить несколько контейнеров для возврата MessageListener; } /** * Адаптер прослушивателя сообщений, привязывает процессор сообщений и использует технологию отражения, чтобы вызвать методы процессора сообщений * @param приемник * @return * /@bean messagelisteneradapter helloaderAdapter (приемник сообщений -салон MessageListenerAdapter (приемник, «ручка»); }}Класс публикации сообщений выглядит следующим образом:
Импорт com.xuanwu.apaas.core.multicache.cachefactory; Импорт org.apache.commons.lang3.stringutils; Импорт org.slf4j.logger; Импорт org.slf4j.loggerfactory; Импорт org.springframework.beans.factory.annotation.autowired; Import org.springframework.stereotype.component; @Component public class messagesubscriber {private static final logger logger = loggerfactory.getLogger (messageBscriber.class); @Autowired private cachefactory cachefactory; / *** После получения сообщения подписки на Redis кэш ehcache недостаточно* @param формат сообщения - это name_key*/ public void handle (строковое сообщение) {logger.debug ("redis.ehcache:"+message); if (stringutils.isempty (message)) {return; } String [] strs = message.split ("#"); String name = strs [0]; String key = null; if (strs.length == 2) {key = strs [1]; } cachefactory.ehdel (имя, ключ); }}Конкретные классы кеша операции следующие:
Import com.xuanwu.apaas.core.multicache.publisher.messagepublisher; Импорт net.sf.ehcache.cache; Импорт net.sf.ehcache.cachemanager; Импорт net.sf.ehcache.element; Импорт org.apache.commons.lang3.stringutils; Импорт org.slf4j.logger; Импорт org.slf4j.loggerfactory; Импорт org.springframework.beans.factory.annotation.autowired; Импорт org.springframework.data.redis.redisconnectionFailureException; Импорт org.springframework.data.redis.core.hashoperations; Импорт org.springframework.data.redis.core.redistemplate; Import org.springframework.stereotype.component; импортировать java.io.inputstream; / *** Раздел многоуровневого кэша* @author rongdi*/ @component public class cachefactory {private Static final Logger logger = loggerFactory.getLogger (cachefactory.class); @Autowired частный Redistemplate Redistemplate; @Autowired Private MessagePublisher MessagePublisher; Частный Cachemanager Cachemanager; public cachefactory () {inputstream is = this.getClass (). getResourCeasStream ("/ehcache.xml"); if (is! = null) {cachemanager = cachemanager.create (is); }} public void cachedel (string name, string key) {// удалить кэш, соответствующий Redis; // Удалить локальный кэш ehcache, который не требуется, и подписчик удалит // ehdel (имя, ключ); if (cachemanager! = null) {// опубликовать сообщение, сообщающее о подписанной службе, что кэш является недействительным сообщением publisher.publish (name, key); }} public String ehget (string name, string key) {if (cachemanager == null) return null; Cache cache = cachemanager.getcache (name); if (cache == null) вернуть null; cache.acquirereadlockonkey (ключ); try {element ele = cache.get (key); if (ele == null) вернуть null; return (string) ele.getObjectValue (); } наконец {cache.releaseReadlockonkey (key); }} public String Redisget (string name, string key) {hashoperations <string, string, string> opera = redistemplate.opsforhash (); try {return opera.get (name, key); } catch (redisconnectionFailureException e) {// Соединение не сбои, ошибка не выбрасывается, и logger.Error ("Подключить ошибку Redis", E); вернуть ноль; }} public void ehput (string name, string key, string value) {if (cachemanager == null) return; if (! cachemanager.cacheexists (name)) {cachemanager.addcache (name); } Cache cache = cachemanager.getcache (name); // Получить блокировку записи на ключ, разные клавиши не влияют друг на друга, аналогично Synchronized (key.intern ()) {} cache.acquirewriteLockonkey (key); try {cache.put (новый элемент (ключ, значение)); } наконец {// Отпустите write Lock Cache.ReleaseWriteLockonkey (Key); }} public void Redisput (string name, string key, string value) {hashoperations <string, string, string> opera = redistemplate.opforhash (); try {operator.put (name, key, value); } catch (redisconnectionFailureException e) {// Соединение не удалось, ошибка не была добавлена, и logger.Error ("Connect Redis error", E); }} public void ehdel (string name, string key) {if (cachemanager == null) return; if (cachemanager.cacheexists (name)) {// Если ключ пуст, удалите напрямую в соответствии с именем кэша if (stringutils.isempty (key)) {cachemanager.removecache (name); } else {cache cache = cachemanager.getcache (name); cache.remove (key); }}} public void redisdel (string name, string key) {hashoperations <string, string, string> opera = redistemplate.opsforhash (); попробуйте {// Если ключ пуст, удалить if (stringutils.isempty (key)) {redistemplate.delete (name); } else {opera.delete (name, key); }} catch (redisconnectionfailureexception e) {// Соединение не удалось, ошибка не была выброшена, и logger.error ("Connect redis error", e); }}}Класс инструментов выглядит следующим образом
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; Импорт org.apache.commons.lang3.stringutils; Импорт org.json.jsonarray; Импорт org.json.jsonobject; Импорт java.util.*; открытый класс jsonutil {private Static ObjectMapper Mapper; static {mapper = new ObjectMapper (); mapper.configure (deserializationfeature.fail_on_unknown_properties, false); } / ** * Сериализуйте объект на json * * @param obj объект, чтобы быть сериализованным * @return * @throws exception * / public static strics serialize (объект obj) бросает исключение {if (obj == null) {throw new allogalArgumentException («obj не должен быть нулевым»); } return mapper.writevalueasString (obj); } / ** десериализация с дженеками, такими как десериализация jsonarray в список <пользователь>* / public static <t> t deserialize (String jsonster, class <?> CollectionClass, Class <?> ... elementClass). ElementClasses); return mapper.readvalue (jsonstr, javatype); } / *** Deserialize String json в объект* @param src Строка JSON, которая будет десериализована* @param t тип класса объекта, десериализованный в* @return* @throhs Exception* / public static <t> t Deserialize (String src, class <t> t) throhs exception {if (src == nullize) {trop newarg expection {if (src == nullaze) нулевой"); } if ("{}". equals (src.trim ())) {return null; } return mappper.readvalue (src, t); }}Чтобы использовать кэш, просто обратите внимание на аннотации @cachable и @cacheevict, а также поддерживайте Spring El Expressions. Более того, имя кэша, представленное атрибутом значения здесь, не имеет проблемы, упомянутой выше. Различные кэши могут быть изолированы с использованием значения. Примеры следующие
@Cachable (value = "bo", key = "#session.productVersionCode+''+#session.tenantCode+''+#ObjectCode")@cacheevict (value = "bo", key = "#session.productversioncode+''+#session.tenantcode+''+#objectCode")
Прикреплен основной пакет зависимостей
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.