> Java > java지도 시간 > 캐싱 메커니즘에서 Redis와 Spring Boot를 통합하는 방법 소개

캐싱 메커니즘에서 Redis와 Spring Boot를 통합하는 방법 소개

Y2J
풀어 주다: 2017-04-21 16:40:33
원래의
1703명이 탐색했습니다.

이 글에는 spring Data JPA, Redis, Spring MVC, Spirng Cache 등 많은 기술적인 포인트가 포함되어 있으므로, 이 글을 읽을 때 위의 기술적인 포인트에 대해 어느 정도 이해가 필요하거나 먼저 읽어볼 수도 있습니다. 기사에서 실제 기술적인 사항에 대해 자세히 알아볼 것입니다. (참고로 Redis 서버를 해당 지역에 다운로드해야 하므로 로컬 Redis를 사용할 수 있는지 확인하십시오. 여기에서는 MySQL 데이터베이스도 사용됩니다. 물론 메모리 내 데이터베이스 테스트). 이 기사에서는 대략 다음 단계로 구분된 해당 Eclipse 코드 예제를 제공합니다.

(1) 새 Java Maven 프로젝트 생성
(2) pom에 해당 코드를 추가합니다. xml 종속성 패키지
(3) Spring Boot 시작 클래스 작성
(4) application.properties 구성
(5) RedisCacheConfig 구성 클래스 작성
(6) DemoInfo 테스트 엔터티 클래스 작성;
(7) DemoInfoRepository 지속성 클래스 작성
(8) DemoInfoService 클래스 작성
(9) DemoInfoController 클래스 작성
(10) 코드가 정상적으로 실행되는지 테스트합니다. 11) 사용자 정의 캐시 키;

(1) 새 Java Maven 프로젝트 생성;

이 단계에 대해서는 자세히 설명하지 않고 새 spring-boot를 생성합니다. redis Java maven 프로젝트; 🎜>

(2) pom.xml에 해당 종속성 패키지를 추가합니다.

Maven에 해당 종속성 패키지를 추가합니다. 여기에는 주로 다음이 포함됩니다. spring boot spring boot web support; ; 캐시 서비스 spring-context-support; redis 지원 추가; mysql 데이터베이스 드라이버, 특정 pom.xml 파일은 다음과 같습니다.

<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.kfit</groupId>
 <artifactId>spring-boot-redis</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>
 <name>spring-boot-redis</name>
 <url>http://maven.apache.org</url>
 <properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <!-- 配置JDK编译版本. -->
 <java.version>1.8</java.version>
 </properties>
 <!-- spring boot 父节点依赖,
  引入这个之后相关的引入就不需要添加version配置,
  spring boot会自动选择最合适的版本进行添加。
 -->
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.3.3.RELEASE</version>
 </parent>
 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <scope>test</scope>
  </dependency>
  <!-- spring boot web支持:mvc,aop... -->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!--
   包含支持UI模版(Velocity,FreeMarker,JasperReports),
   邮件服务,
   脚本服务(JRuby),
   缓存Cache(EHCache),
   任务计划Scheduling(uartz)。
  -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
  </dependency>
  <!-- 添加redis支持-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-redis</artifactId>
  </dependency>
  <!-- JPA操作数据库. -->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <!-- mysql 数据库驱动. -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <!-- 单元测试. -->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>
</project>
로그인 후 복사

위는 각각 다음과 같은 전체 pom.xml 파일입니다. 간단한 코멘트 .

(3) Spring Boot 시작 클래스(com.kfit.App) 작성

package com.kfit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * Spring Boot启动类;
 *
 * @author Angel(QQ:*********)
 * @version v.0.1
 */
@SpringBootApplication
public class App {
  /**
  * -javaagent:.\lib\springloaded-1.2.4.RELEASE.jar -noverify
  * @param args
  */
  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }
}
로그인 후 복사

(4) application.properties 구성

구성 두 개의 리소스 중 첫 번째는 데이터베이스의 기본 정보이고, 세 번째는 JPA 구성입니다.

Src/main/resouces/application.properties:
########################################################
###datasource 配置MySQL数据源;
########################################################
spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=20
spring.datasource.max-idle=8
spring.datasource.min-idle=8
spring.datasource.initial-size=10
########################################################
###REDIS (RedisProperties) redis基本配置;
########################################################
# database name
spring.redis.database=0
# server host1
spring.redis.host=127.0.0.1 
# server password
#spring.redis.password=
#connection port
spring.redis.port=6379
# pool settings ...
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
# name of Redis server
#spring.redis.sentinel.master=
# comma-separated list of host:port pairs
#spring.redis.sentinel.nodes=
########################################################
### Java Persistence Api 自动进行建表
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
로그인 후 복사

(5) RedisCacheConfig 구성 클래스를 작성합니다. 구현될 클래스는 여러 가지가 있습니다. 첫 번째는 CacheManager 캐시 관리자이고, 두 번째는 특정 작업 구현 클래스입니다. 세 번째는 CacheManager 팩토리 클래스입니다(이것은 구성 파일 구성을 사용하여 주입할 수 있거나 구현할 수 있습니다). 네 번째는 캐시 키 생성 전략입니다(물론 Spring에는 자체 생성 전략이 있지만 Redis 클라이언트에서 보면 육안으로는 왜곡되어 보이는 직렬화된 키입니다. 여기서는 내장 캐시 전략 먼저).

com.kfit.config/RedisCacheConfig:
package com.kfit.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
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;
/**
 * redis 缓存配置;
 *
 * 注意:RedisCacheConfig这里也可以不用继承:CachingConfigurerSupport,也就是直接一个普通的Class就好了;
 *
 * 这里主要我们之后要重新实现 key的生成策略,只要这里修改KeyGenerator,其它位置不用修改就生效了。
 *
 * 普通使用普通类的方式的话,那么在使用@Cacheable的时候还需要指定KeyGenerator的名称;这样编码的时候比较麻烦。
 *
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Configuration
@EnableCaching//启用缓存,这个注解很重要;
publicclass RedisCacheConfig extends CachingConfigurerSupport {
 /**
  * 缓存管理器.
  * @param redisTemplate
  * @return
  */
 @Bean
 public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {
  CacheManager cacheManager = new RedisCacheManager(redisTemplate);
  returncacheManager;
 }
 /**
  * redis模板操作类,类似于jdbcTemplate的一个类;
  *
  * 虽然CacheManager也能获取到Cache对象,但是操作起来没有那么灵活;
  *
  * 这里在扩展下:RedisTemplate这个类不见得很好操作,我们可以在进行扩展一个我们
  *
  * 自己的缓存类,比如:RedisStorage类;
  *
  * @param factory : 通过Spring进行注入,参数在application.properties进行配置;
  * @return
  */
 @Bean
 public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
  RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
  redisTemplate.setConnectionFactory(factory);
  //key序列化方式;(不然会出现乱码;),但是如果方法上有Long等非String类型的话,会报类型转换错误;
  //所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer
  //或者JdkSerializationRedisSerializer序列化方式;
//  RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;
//  redisTemplate.setKeySerializer(redisSerializer);
//  redisTemplate.setHashKeySerializer(redisSerializer);
  returnredisTemplate;
 }
}
로그인 후 복사

위 코드에는 매우 자세한 설명이 있지만 여기서 간단히 언급하겠습니다.

RedisCacheConfig는 여기서 상속할 필요가 없습니다. CachingConfigurerSupport는 일반적인 클래스입니다. 여기서 가장 중요한 것은 나중에 키 생성 전략을 다시 구현해야 한다는 것입니다. 여기서 KeyGenerator를 수정하면 다른 위치에서도 수정 없이 적용됩니다. 일반 클래스 메서드를 사용하는 경우 @Cacheable을 사용할 때 KeyGenerator의 이름도 지정해야 합니다. 이는 코딩할 때 더 번거롭습니다.

(6) DemoInfo 테스트 엔터티 클래스 작성

테스트 엔터티 클래스 작성: com.kfit.bean.DemoInfo:

package com.kfit.bean;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
 * 测试实体类,这个随便;
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Entity
publicclass DemoInfo implements Serializable{
 privatestaticfinallongserialVersionUID = 1L;
 @Id@GeneratedValue
 privatelongid;
 private String name;
 private String pwd;
 publiclong getId() {
  returnid;
 }
 publicvoid setId(longid) {
  this.id = id;
 }
 public String getName() {
  returnname;
 }
 publicvoid setName(String name) {
  this.name = name;
 }
 public String getPwd() {
  returnpwd;
 }
 publicvoid setPwd(String pwd) {
  this.pwd = pwd;
 }
 @Override
 public String toString() {
  return"DemoInfo [id=" + id + ", name=" + name + ", pwd=" + pwd + "]";
 }
}
로그인 후 복사

(7) DemoInfoRepository 작성 지속성 클래스;

DemoInfoRepository는 Spirng Data JPA를 사용하여 구현됩니다.

com.kfit.repository.DemoInfoRepository:
package com.kfit.repository;
import org.springframework.data.repository.CrudRepository;
import com.kfit.bean.DemoInfo;
/**
 * DemoInfo持久化类
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
publicinterface DemoInfoRepository extends CrudRepository<DemoInfo,Long> {
}
로그인 후 복사

(8) DemoInfoService 클래스 작성

여기에는 두 가지 기술적 측면이 있습니다. Spring @Cacheable Annotation과 RedisTemplate 객체를 사용하여 구체적인 코드는 다음과 같습니다.

com.kfit.service.DemoInfoService:

package com.kfit.service;
import com.kfit.bean.DemoInfo;
/**
 * demoInfo 服务接口
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
publicinterface DemoInfoService {
 public DemoInfo findById(longid);
 publicvoid deleteFromCache(longid);
 void test();
}
com.kfit.service.impl.DemoInfoServiceImpl:
package com.kfit.service.impl;
import javax.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import com.kfit.bean.DemoInfo;
import com.kfit.repository.DemoInfoRepository;
import com.kfit.service.DemoInfoService;
/**
 *
 *DemoInfo数据处理类
 *
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Service
publicclass DemoInfoServiceImpl implements DemoInfoService {
 @Resource
 private DemoInfoRepository demoInfoRepository;
 @Resource
 private RedisTemplate<String,String> redisTemplate;
 @Override
 publicvoid test(){
  ValueOperations<String,String> valueOperations = redisTemplate.opsForValue();
  valueOperations.set("mykey4", "random1="+Math.random());
  System.out.println(valueOperations.get("mykey4"));
 }
 //keyGenerator="myKeyGenerator"
 @Cacheable(value="demoInfo") //缓存,这里没有指定key.
 @Override
 public DemoInfo findById(longid) {
  System.err.println("DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id="+id);
  returndemoInfoRepository.findOne(id);
 }
 @CacheEvict(value="demoInfo")
 @Override
 publicvoid deleteFromCache(longid) {
  System.out.println("DemoInfoServiceImpl.delete().从缓存中删除.");
 }
}
로그인 후 복사

(9) DemoInfoController 클래스를 작성합니다.
package com.kfit.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.kfit.bean.DemoInfo;
import com.kfit.service.DemoInfoService;
/**
 * 测试类.
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Controller
publicclass DemoInfoController {
 @Autowired
  DemoInfoService demoInfoService;
 @RequestMapping("/test")
 public@ResponseBody String test(){
  DemoInfo loaded = demoInfoService.findById(1);
System.out.println("loaded="+loaded);
DemoInfo cached = demoInfoService.findById(1);
  System.out.println("cached="+cached);
  loaded = demoInfoService.findById(2);
  System.out.println("loaded2="+loaded);
  return"ok";
 }
 @RequestMapping("/delete")
 public@ResponseBody String delete(longid){
  demoInfoService.deleteFromCache(id);
  return"ok";
 }
 @RequestMapping("/test1")
 public@ResponseBody String test1(){
  demoInfoService.test();
  System.out.println("DemoInfoController.test1()");
  return"ok";
 }
}
로그인 후 복사

(10) 코드가 정상적으로 실행되는지 테스트

애플리케이션을 시작하고 주소: 127.0.0.1:8080/test

콘솔에서 다음을 확인하세요.


DemoInfoServiceImpl.findById()=========== 데이터베이스에서 가져옴....id=1

loaded=DemoInfo [id=1, name=Zhang San, pwd= 123456]

cached=DemoInfo [id=1, name=Zhang San, pwd=123456]

DemoInfoServiceImpl.findById()========== 데이터베이스에서 가져옴....id= 2

loaded2=DemoInfo [id=2, name=Zhang San, pwd=123456]

위의 인쇄 정보가 보이면 캐시에 성공한 것입니다.


액세스 주소: 127.0.0.1:8080/test1

random1=0.9985031320746356

DemoInfoController.test1()

두 번째 방문: http : //127.0.0.1:8080/test


loaded=DemoInfo [id=1, name=Zhang San, pwd=123456]
cached=DemoInfo [id=1, name=Zhang Three , pwd=123456]

loaded2=DemoInfo [id=2, name=Zhang San, pwd=123456]


이때 모든 데이터가 캐시됩니다.



이때 삭제 작업을 수행합니다: 127.0.0.1:8080/delete?id=1

그런 다음 방문: 127.0.0.1:8080/test


DemoInfoServiceImpl .findById()========== 데이터베이스에서 가져옴....id=1

loaded=DemoInfo [id=1, name=Zhang San, pwd=123456]

캐시 =DemoInfo [id=1, name=张三, pwd=123456]

loaded2=DemoInfo [id=2, name=张三, pwd=123456]

(11) 사용자 정의 캐시 키;

在com.kfit.config.RedisCacheConfig类中重写CachingConfigurerSupport中的keyGenerator ,具体实现代码如下:

/**
  * 自定义key.
  * 此方法将会根据类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。
  */
 @Override
 public KeyGenerator keyGenerator() {
  System.out.println("RedisCacheConfig.keyGenerator()");
  returnnew KeyGenerator() {
   @Override
   public Object generate(Object o, Method method, Object... objects) {
    // This will generate a unique key of the class name, the method name
    //and all method parameters appended.
    StringBuilder sb = new StringBuilder();
    sb.append(o.getClass().getName());
    sb.append(method.getName());
    for (Object obj : objects) {
     sb.append(obj.toString());
    }
    System.out.println("keyGenerator=" + sb.toString());
    returnsb.toString();
   }
  };
 }
로그인 후 복사

这时候在redis的客户端查看key的话还是序列化的肉眼看到就是乱码了,那么我改变key的序列方式,这个很简单,redis底层已经有具体的实现类了,我们只需要配置下:

//key序列化方式;(不然会出现乱码;),但是如果方法上有Long等非String类型的话,会报类型转换错误;
//所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer
//或者JdkSerializationRedisSerializer序列化方式;
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;
    redisTemplate.setKeySerializer(redisSerializer);
    redisTemplate.setHashKeySerializer(redisSerializer);
로그인 후 복사

综上以上分析:RedisCacheConfig类的方法调整为:


package com.kfit.config;
import java.lang.reflect.Method;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
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.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 * redis 缓存配置;
 *
 * 注意:RedisCacheConfig这里也可以不用继承:CachingConfigurerSupport,也就是直接一个普通的Class就好了;
 *
 * 这里主要我们之后要重新实现 key的生成策略,只要这里修改KeyGenerator,其它位置不用修改就生效了。
 *
 * 普通使用普通类的方式的话,那么在使用@Cacheable的时候还需要指定KeyGenerator的名称;这样编码的时候比较麻烦。
 *
 * @author Angel(QQ:412887952)
 * @version v.0.1
 */
@Configuration
@EnableCaching//启用缓存,这个注解很重要;
publicclass RedisCacheConfig extends CachingConfigurerSupport {
  /**
   * 缓存管理器.
   * @param redisTemplate
   * @return
   */
  @Bean
  public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {
    CacheManager cacheManager = new RedisCacheManager(redisTemplate);
    returncacheManager;
  }
  /**
   * RedisTemplate缓存操作类,类似于jdbcTemplate的一个类;
   *
   * 虽然CacheManager也能获取到Cache对象,但是操作起来没有那么灵活;
   *
   * 这里在扩展下:RedisTemplate这个类不见得很好操作,我们可以在进行扩展一个我们
   *
   * 自己的缓存类,比如:RedisStorage类;
   *
   * @param factory : 通过Spring进行注入,参数在application.properties进行配置;
   * @return
   */
  @Bean
  public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
    redisTemplate.setConnectionFactory(factory);
    //key序列化方式;(不然会出现乱码;),但是如果方法上有Long等非String类型的话,会报类型转换错误;
    //所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer
    //或者JdkSerializationRedisSerializer序列化方式;
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;
    redisTemplate.setKeySerializer(redisSerializer);
    redisTemplate.setHashKeySerializer(redisSerializer);
    returnredisTemplate;
  }
  /**
   * 自定义key.
   * 此方法将会根据类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。
   */
  @Override
  public KeyGenerator keyGenerator() {
    System.out.println("RedisCacheConfig.keyGenerator()");
    returnnew KeyGenerator() {
      @Override
      public Object generate(Object o, Method method, Object... objects) {
       // This will generate a unique key of the class name, the method name
       //and all method parameters appended.
       StringBuilder sb = new StringBuilder();
       sb.append(o.getClass().getName());
       sb.append(method.getName());
       for (Object obj : objects) {
         sb.append(obj.toString());
       }
       System.out.println("keyGenerator=" + sb.toString());
       returnsb.toString();
      }
    };
  }
}
로그인 후 복사

这时候在访问地址:127.0.0.1:8080/test

这时候看到的Key就是:com.kfit.service.impl.DemoInfoServiceImplfindById1

在控制台打印信息是:

(1)keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById1
(2)DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id=1
(3)keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById1
(4)loaded=DemoInfo [id=1, name=张三, pwd=123456]
(5)keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById1
(6)cached=DemoInfo [id=1, name=张三, pwd=123456]
(7)keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById2
(8)keyGenerator=com.kfit.service.impl.DemoInfoServiceImplfindById2
(10)DemoInfoServiceImpl.findById()=========从数据库中进行获取的....id=2
(11)loaded2=DemoInfo [id=2, name=张三, pwd=123456]

其中@Cacheable,@CacheEvict下节进行简单的介绍,这节的东西实在是太多了,到这里就打住吧,剩下的就需要靠你们自己进行扩展了。

위 내용은 캐싱 메커니즘에서 Redis와 Spring Boot를 통합하는 방법 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿