各种配置文件总结

— 第二种方式: 通过对mapper包扫描;优点:1.可以对mapper包下的所有接口完成代理; 2.会自动注入 sqlSessionFact

1. Spring Data JPA 配置:

0.框架知识概述:

Spring Data JPA 是一个持久层的框架,而一个持久层框架所做的事情不过两件:

  1. 连接数据库(比如 JDBD连接数据库) 2.操作数据库(比如 sql 操作数据库);
  2. 连接数据库:
  3. 配置参数抽取: config.properties 配置:

# oracle jdbc properties

jdbc.url = jdbc:oracle:thin:@localhost:1521:XE

jdbc.driver= oracle.jdbc.driver.OracleDriver

jdbc.user = bos

jdbc.password = bos

  1. applicationContext-dataSource.xml 配置:
  2. 数据库连接池配置: config.properties

在主配置文件 applicationContext.xml 中通过配置加载属性文件:

<!– 加载properties文件 –>

<context:property-placeholder location=”classpath:config.properties” />

( ${jdbc.url} 这种类似EL表达式的是SpringEL 表达式 )

<bean id=”dataSource” class=”com.mchange.v2.c3p0.ComboPooledDataSource”>

<property name=”driverClass” value=”${jdbc.driver}” />

<property name=”jdbcUrl” value=”${jdbc.url}” />

<property name=”user” value=”${jdbc.user}” />

<property name=”password” value=”${jdbc.password}” />

</bean>

  1. Spring整合JPA配置:

(在 entityManagerFactory中配置了连接池 和 domain中的bean ,这就相当于是用连接把实体类与数据库表建立了联系, [这里说明一点:实体和表之间的准确对应关系是依赖实体类中的注解来准确定位的] 这种联系是非常紧密的,实体对象属性值的变化直接会反映到数据库表中, 而JPA作为接口规范,这里选择 hibernate作为持久化提供者; 然后就是一些数据库的基本配置了;)

<!– 整合JPA配置 –>

<bean id=”entityManagerFactory”

class=”org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean”>

<property name=”dataSource” ref=”dataSource” />

<property name=”packagesToScan” value=”cn.itcast.bos.domain” />

<property name=”persistenceProvider”>

<bean class=”org.hibernate.jpa.HibernatePersistenceProvider” />

</property>

<property name=”jpaVendorAdapter”>

<bean class=”org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter”>

<property name=”generateDdl” value=”true” />

<property name=”database” value=”ORACLE” />

<property name=”databasePlatform” value=”org.hibernate.dialect.Oracle10gDialect” />

<property name=”showSql” value=”true” />

</bean>

</property>

<property name=”jpaDialect”>

<bean class=”org.springframework.orm.jpa.vendor.HibernateJpaDialect” />

</property>

<!– <property name=”jpaPropertyMap”> –>

<!– <map> –>

<!– <entry key=”hibernate.query.substitutions” value=”true 1, false 0″ /> –>

<!– <entry key=”hibernate.default_batch_fetch_size” value=”16″ /> –>

<!– <entry key=”hibernate.max_fetch_depth” value=”2″ /> –>

<!– <entry key=”hibernate.generate_statistics” value=”true” /> –>

<!– <entry key=”hibernate.bytecode.use_reflection_optimizer” –>

<!– value=”true” /> –>

<!– <entry key=”hibernate.cache.use_second_level_cache” value=”false” /> –>

<!– <entry key=”hibernate.cache.use_query_cache” value=”false” /> –>

<!– </map> –>

<!– </property> –>

</bean>

2.操作数据库:

  1. 整合Spring Data JPA (相当于扫描了Dao, 这样dao的操作才能被识别;)

这里说明一点: dao的操作能被识别是CRUD的哪种操作, 至于操作哪个表, 是由在接口创建时传入的泛型参数确定的, 传入了一个泛型类,这个类代表着某张表; 简而言之:dao描述了两个问题: 1.我要执行什么操作, 2.我要操作那张表;

<!– 整合spring data jpa –>

<jpa:repositories base-package=”cn.itcast.bos.dao” />

3.声明式事务管理配置:

1. 配置事务管理器:

(JDBC事务管理的本质,是对Session的管理, 那么这里配置了事务管理器, 就相当于指明了由谁来管理事务, 而同时属性注入了 entityManagerFactory 实体管理者工厂,这就相当于将数据库操作中的session交给了事务管理器,也就容易实现事务的管理了)

<!– JPA事务管理器 –>

<bean id=”transactionManager” class=”org.springframework.orm.jpa.JpaTransactionManager” >

<property name=”entityManagerFactory” ref=”entityManagerFactory” />

</bean>

  1. 配置”事务注解驱动”

(这样就可以识别 Service 中的 @Transactional了)

<!– 注解管理事务 –>

<tx:annotation-driven transaction-manager=”transactionManager” proxy-target-class=”true”/>

4.Spring Data JPA 配置小结:

1. SpringDataJPA 的配置就是通过将基本数据库连接参数:比如driven , url , username, password通过SPEL读取到连接池DataSource, 这个时候连接池其实已经拿到了数据库的连接connection, 理论上已经可以通过sql操作数据库了,但是此时我们又将: dataSource, , domain , persistenceProvider 通过属性注入到 entityManagerFactory ,其实这个时候 ”实体管理器工厂” 已经利用 DataSource中的 connection 将 domain 下的实体类 entity 与数据库中的表 table 建立了紧密联系, 此时已经完成类与表的强相关; 至此,我们对数据库的操作对象不再是真真正正的表了,而是与之强相关的类了,操作的实现不再是sql语句,而是使用java代码来实现了; 至于哪个类对应哪个表,这就是 映射文件 hbm.xml 或者是类中的映射注解配置来细化了;

2. 在第1步获得类与数据库的连接以后,我们就要操作数据库了,我们是通过:

<jpa:repositories base-package=”cn.itcast.bos.dao” />

这个相当于是扫描指定包下的 repository 接口, (当然这些repository 继承了jpaRepository,或者JpaSpecificationExecutor接口), 这样其实底层也是利用了AOP 当我们在Service中 @autowired 这些接口实现时, AOP 会给我们注入 SpringDataJPA 默认实现,这些实现了本身已经封装了一些常见的CRUD操作,所以我们可以省去常见CRUD操作的sql的编写,但是对于一些复杂的CRUD操作, SpringDataJPA 也给我们留了一个口子:

  1. 比如一些标准的操作:
  2. findById() , findByProvinceAndCityAndDistrict 等
  3. 更复杂的操作:

@Query(value=”update Courier set deltag=’1′ where id=?”)

@Modifying

public void updateDeltag(int id);

或者是这个:

@Query(value=”update Courier set deltag=” where id=?”)

@Modifying

public void doRestore(int id);

3 . 至于事务管理: 原理是Spring 有IOC ,那么类的创建,准确来说是Service类的创建,也是由Spring来创建, 根据Spring创建bean的生命周期方法的执行流程,我们知道,后处理bean中的方法执行时机是在我们获得bean对象之前,那么,后处理bean会利用AOP思想,在我们获得的Service对象的方法执行开始都加上session的事务开启,在方法执行末尾加上session的事务提交,和事务回滚,那么在我们拿到IOC 容器给我们创建的bean时,这个bean就是在事务管理之中的bean了; 这就完成了事务管理;

===============以上SpringDataJPA == 以下 WebService====================

  1. WebService的配置:

0.框架知识概述:

(WebService 按目前使用来看就是提供了跨服务器远程数据获取的一种方式, 那我们只会关心两个问题: 1. 远程服务的创建与发布, 2,本地对远程服务的访问与数据接收; 其实远程服务的创建,就是一个service接口 中方法访问路径,访问方式,接收数据格式,返回数据格式的声明, 至于服务的发布其实是交给了配置文件; 至于本地客户端可能只需要WebClient 加上一个正确格式的url就可以了)我们这里说一下 JAX-RS 的配置:

1.首先说一下服务端配置:

  1. 作为一个服务提供端,它需要识别到对 它的所有WebService请求, 那么它就是通过在 web.xml中配置一个 CXFServlet 用来专门接收并处理WebService请求:

比如下面配置会拦截所有端口号后以 /services 开头的url请求:

<servlet>

<servlet-name>CXFService</servlet-name>

<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>CXFService</servlet-name>

<url-pattern>/services/*</url-pattern>

</servlet-mapping>

  1. 在第1步拦截到所有WebService请求的基础上,它会根据请求的url 去寻找对应的Service , 那么到哪里去寻找呢? 当然是到已经发布到服务器中的Service中去找, 那么怎么才算是发布到服务器中了呢? 这里我们是通过在 .xml配置文件来实现发布的:

比如下面配置 是将 PromotionServiceImpl 发布到服务器中

<jaxrs:server id=”promotionService” address=”/promotionService”>

<jaxrs:serviceBeans>

<bean class=”cn.itcast.bos.service.take_delivery.impl.PromotionServiceImpl” />

</jaxrs:serviceBeans>

<jaxrs:inInterceptors>

<bean class=”org.apache.cxf.interceptor.LoggingInInterceptor”></bean>

</jaxrs:inInterceptors>

<jaxrs:outInterceptors>

<bean class=”org.apache.cxf.interceptor.LoggingOutInterceptor”></bean>

</jaxrs:outInterceptors>

</jaxrs:server>

——————————–下面附上 Service接口中方法声明—————————————

// 根据page和rows 返回分页数据

@Path(“/pageQuery”)

@GET

@Produces({ “application/xml”, “application/json” })

PageBean<Promotion> findPageData(@QueryParam(“page”) int page,

@QueryParam(“rows”) int rows);

——————————–下面附上 Service实现编写示例—————————————

@Override

public PageBean<Promotion> findPageData(int page, int rows) {

Pageable pageable = new PageRequest(page – 1, rows);

Page<Promotion> pageData = promotionRepository.findAll(pageable);

// 封装到Page对象

PageBean<Promotion> pageBean = new PageBean<Promotion>();

pageBean.setTotalCount(pageData.getTotalElements());

pageBean.setPageData(pageData.getContent());

return pageBean;

}

2.服务端配置小结:

其实以上已经说明了Service 接口编写, Service接口实现编写, Service服务在服务器端发布, 以及服务器端对 WebService 请求的拦截;

3.客户端请求操作:

客户端作为服务的调用者,相对来说不需要特别的配置,只需要使用一个客户端请求对象WebClient 加上正确的url 就可以发送请求了:其中 WebClient.create 创建了请求; .accept 指明了是接收数据(接收用accept, 发送用 type) ; .get 指明了请求方式是get请求(get请求是查询操作, post请求是添加操作, put请求是更新操作, delete 请求是删除操作)

@Action(value = “promotion_pageQuery”, results = { @Result(name = “success”, type = “json”) })

public String pageQuery() {

// 基于WebService 获取 bos_management的 活动列表 数据信息

PageBean<Promotion> pageBean = WebClient

.create(Constants.BOS_MANAGEMENT_URL

+ “/bos_management/services/promotionService/pageQuery?page=”

+ page + “&rows=” + rows)

.accept(MediaType.APPLICATION_JSON).get(PageBean.class);

ActionContext.getContext().getValueStack().push(pageBean);

return SUCCESS;

}

4.WebService配置小结:

其实我们来理顺整个过程: 客户端 使用WebClient.create();发起请求——–>服务端 web.xml配置 CXFServlet 拦截所有 /services 开头请求——–> applicationContext-webService.xml 发布服务——-> Service接口中的具体方法, 声明服务访问方式,——-> Service实现类中 提供了服务真正的操作内容;

=================以上WebService == 以下 ActiveMQ ======================

3.ActiveMQ 的配置:

0.框架知识概述:

ActiveMQ 是JMS java消息服务的规范的实现: 个人感觉它是一个服务器,有着自己的访问端口,但是它的工作却是存储消息, 又很像一个存放消息的数据库,所以在和 Spring整合时特像一个数据库: 其实:ActiveMQ 的事情也是两件:1.给生产者提供存放消息的入口, 2.给消费者提供消费消息的入口:

  1. 基础连接配置:

无论是 “生产消息” 还是 “消费消息” :我们都要有基础的连接配置:

  1. ActiveMQ 本身给我们提供了一个连接工厂: ActiveMQConnectionFactory

<!– ActiveMQ 连接工厂 –>

<!– 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供–>

<!– 如果连接网络:tcp://ip:61616;未连接网络:tcp://localhost:61616 以及用户名,密码–>

下面这种连接方式需要是为了简化<bean>配置,但需要下载第三方的支持,开始运行时较慢:

<!– <amq:connectionFactory id=”amqConnectionFactory” –>

<!– brokerURL=”tcp://localhost:61616″ userName=”admin” password=”admin” /> –>

<bean id=”amqConnectionFactory”

class=”org.apache.activemq.ActiveMQConnectionFactory”>

<property name=”brokerURL” value=”tcp://localhost:61616″></property>

<property name=”userName” value=”admin”></property>

<property name=”password” value=”admin”></property>

</bean>

  1. Spring 对 ActiveMQ 连接工厂的封装 CachingConnectionFactory

Spring在 JMS服务供应商提供连接工厂的基础上,给我们进行了一次封装,这个封装可以理解成 Spring 提供的API 与 ActiveMQ 之间的适配器 ; 达到了解耦的作用, 这样Spring就可以整合各个JMS供应商提供的连接工厂了;

<!– Spring Caching连接工厂 –>

<!– Spring用于管理真正的ConnectionFactory的ConnectionFactory –>

<bean id=”mqConnectionFactory” class=”org.springframework.jms.connection.CachingConnectionFactory”>

<!– 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory –>

<property name=”targetConnectionFactory” ref=”amqConnectionFactory”></property>

<!– 同上,同理 –>

<!– <constructor-arg ref=”amqConnectionFactory” /> –>

<!– Session缓存数量 –>

<property name=”sessionCacheSize” value=”1″ />

</bean>

  1. 基础配置小结

不管我们用那种JMS产品,它都会提供一个连接工厂,而与Spring整合时,我们用 CachingConnectionFactory 将JMS服务厂商提供的连接工厂进行封装, 那么我们就可以不需要针对不同的 连接工厂操作了,只需要对同一个 CachingConnectionFactory 进行操作,达到了解耦的目的; 流程如下:

JMS厂商提供ActiveMQ 提供 ActiveMQConnectionFactory 连接工厂——>Spring 提供 CachingConnectionFactory 缓存连接工厂 封装厂商提供的连接工厂

  1. 生产者配置:
  2. 这个消息生产者配置,Spring使用了它惯用的模板思想,给你提供了一个模板对象,没有什么特别之处,这个模板对象封装了连接工厂,就相当于持有了对ActiveMQ的连接, 那么这个时候向ActiveMQ中添加消息应该是可以的; 下边配置了两种类型的jmsTemplate: 1. 点对点模式: 队列Queue, 2. 发布订阅模式的: 话题Topic ; 他们的配置大同小异,唯一不同之处在于 :

<property name=”pubSubDomain” value=”false” />

的配置的value 值是false 还是 true; (true:发布订阅模式 false:点对点模式;)

<!– 定义JmsTemplate的Queue类型 –>

<bean id=”jmsQueueTemplate” class=”org.springframework.jms.core.JmsTemplate”>

<!– 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 –>

<constructor-arg ref=”mqConnectionFactory” />

<!– 非pub/sub模型(发布/订阅),即队列模式 –>

<property name=”pubSubDomain” value=”false” />

</bean>

<!– 定义JmsTemplate的Topic类型 –>

<bean id=”jmsTopicTemplate” class=”org.springframework.jms.core.JmsTemplate”>

<!– 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 –>

<constructor-arg ref=”mqConnectionFactory” />

<!– pub/sub模型(发布/订阅) –>

<property name=”pubSubDomain” value=”true” />

</bean>

<!–Spring JmsTemplate 的消息生产者 end–>

3.生产者编程:

1.在消息生产者配置的基础上,配置了一个jmsTemplate模板,我们可以使用:

// 注入queue消息模板

@Autowired

@Qualifier(“jmsQueueTemplate”)

private JmsTemplate jmsQueueTemplate;

注入到我们的程序中,然后调用send方法,向activeMQ中生产消息:

// 调用MQ服务,发送短信验证码

jmsQueueTemplate.send(“bos_sms”, new MessageCreator() {

@Override

public Message createMessage(Session session) throws JMSException {

MapMessage mapMessage = session.createMapMessage();

mapMessage.setString(“telephone”, model.getTelephone());

mapMessage.setString(“checkCode”, checkCode);

return mapMessage;

}

});

简单解释一下: send(消息名称, 消息创建接口);

这个地方相当复杂:利用了两个不容易理解的知识点: 我们简单聊聊:

  1. 第一个问题: jmsQueueTemplate 的Send()方法需要 MessageCreator 的createMessage()方法创建的消息内容,但是 MessageCreator 创建消息时需要 session,但是它却没有; 然而 jmsQueueTemplate 的execute()方法 却可以获得session , 所以 jmsQueueTemplate 对 MessageCreator 说:你先过来,然后我给你弄个session , 然后你给我弄message, 这里边体现的是, 接口作为参数时,实现了主调方法的资源对接口中的方法的共享, 这里主调方法是 send ,它可以想办法拿到 session,然后 它把session共享给了 createMessage()方法;

如果说一般数据类型数据做为形参,主调方法是为了获取实参的值,

那么接口作为形参时,主调方法是为了获得接口的方法, 还有一点附带效果:传入的接口中的方法可以共享主调方法的参数访问权限(就是主调方法可以用的参数,接口中的方法也可以用;)

  1. 第二个问题: send 发送的消息内容是不固定的(有:1.MapMessage;2.ByteMessage;3.ObjectMessage;4.StreamMessage;5.TextMessage),也就是说 创建消息的方法体是不固定的,那么是如何实现的呢? 用接口作为参数传入,由用户动态定义方法的实现;

如果说我们在方法中调用类的静态方法,是将一段固定代码插入到主调方法中;

那么对接口方法的调用,就是将一段动态代码插入到主调方法中;

4.生产者总结:

ActiveMQ 的生产者:

厂商的 ActiveMQConnectionFactory 注入到 ——>Spring的 CachingConnectionFactory ——>注入到 Spring的 JmsTemplate ; 配置提供了这个模板对象之后,我们就可以在java代码中注入这个对象,进行消息的创建;

5.消费者配置:

消费者要从ActiveMQ中拿消息, 肯定是要连接ActiveMQ服务器的,所以基础连接配置,消费者配置中也需要一份,我们假设消费者配置中,已经配置好了基础连接; 那么这个时候已经可以拿到 CachingConnectionFactory , 这意味着我们已经可以通过这个连接工厂获得与ActiveMQ的连接了; 如何完成消费呢? 首先编写一个类实现 MessageListener 接口 ,然后将这个类注册到消息监听器容器中; 这里说明一点,类实现了 MessageListener 接口本质上是为了让这个类实现监听器应该具有的方法, 然而并没有真正监听ActiveMQ中的消息,这个时候需要配置到消息监听器的容器(listener-container)中才能生效; 而这个(listener-container) 到底如何工作的呢? 我们来看看这个,消费者配置:

<!– 定义Queue监听器 –>

<jms:listener-container destination-type=”queue” container-type=”default”

connection-factory=”connectionFactory” acknowledge=”auto”>

<!– 默认注册bean名称,应该是类名首字母小写 –>

<jms:listener destination=”bos_sms” ref=”smsConsumer”/>

</jms:listener-container>

<!– 定义Topic监听器 –>

<!– <jms:listener-container destination-type=”topic” container-type=”default”

connection-factory=”connectionFactory” acknowledge=”auto”>

<jms:listener destination=”spring_topic” ref=”topicConsumer1″/>

<jms:listener destination=”spring_topic” ref=”topicConsumer2″/>

</jms:listener-container> –>

我们可以看到:在消息监听容器中,注入了两个参数:

connection-factory=”connectionFactory”

这个是配置了连接工厂, 是获得与ActiveMQ服务器连接的;

<jms:listener destination=”bos_sms” ref=”smsConsumer”/>

这个是配置了监听的消息目标: destination=”bos_sms”

(监听者)消费者: ref=”smsConsumer”

6.消费者编程:

(监听者)消费者 本身是一个类,实现了 MessageListener 接口:

@Service(“smsConsumer”)

public class SmsConsumer implements MessageListener {

@Override

public void onMessage(Message message) {

MapMessage mapMessage = (MapMessage) message;

String telephone = null;

String checkCode = null;

try {

telephone = mapMessage.getString(“telephone”);

checkCode = mapMessage.getString(“checkCode”);

} catch (JMSException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

// 用输出语句代替短信发送

System.out.println(telephone+” 的验证码是 : “+checkCode);

}

}

7.消费者总结:

其实消费者无非就是从ActiveMQ中获得获得消息, 那么首先要获得与ActiveMQ的连接,这个是通过基础配置完成的, 连接以后要从里边拿消息呀,这个地方是在配置文件中配置一个监听器容器, 将连接工厂和监听器(其实就是一个实现了MessageListener的java类) 注入到这个监听器中,这个时候就相当于通过监听器容器,将监听器和ActiveMQ建立了联系;

=================以上ActiveMQ == 以下 Quartz=====================

4.Quartz的配置:

0.框架知识概述:

Quartz是一个定时任务调度框架, 它关心的只有两件事情:

  1. 要做什么事情—->jobDetail(即任务详情)
  2. 什么时候做,频率是什么—->SimpleTrigger(依据时间)/CronTriggerFactoryBean(依据日历) 即:任务执行策略;

Ps:

  1. 任务调度员scheduler,这个是将 jobDetail 和 SimpleTrigger 进行组合,生成一个日程,
  2. 另外还有一点要说明一下,在实际生产中,我们 jobDetail(即任务详情)可能并不在Spring的管理之中, 但是, jobDetail(即任务详情) 却需要@autowired 被Spring管理的Service 或者其他bean, 这就违背了Spring规定:一个对象要想被注入对象,必须自己在Spring 管理之中,这个时候该怎么解决呢, 我们知道, Struts2与Spring整合时, action 没有被Spring管理,却可以注入被Spring管理的Service, 这里我们使用同样的方法解决这个问题,那就是创建了一个可以使 我们的 jobDetail 具有可以自动注入功能的工厂类 JobFactory ;我们在实际配置中是将 jobDetail 注入 SimpleTrigger ,再将 SimpleTrigger 注入 SchedulerFactoryBean ,那么,此时我们将 JobFactory 也注入到 SchedulerFactoryBean 中,那么创建出来的 jobDetail 就具有了自动注入的功能; 问题得以解决;
  3. JobDetailFactoryBean配置:

JobDetailFactoryBean 是job的工厂,我们在里边注入我们的job类对象, 下面的配置中job并不在Spring管理中;

<bean id=”promotionJob” class=”org.springframework.scheduling.quartz.JobDetailFactoryBean”>

<property name=”jobClass” value=”cn.itcast.bos.quartz.PromotionJob”></property>

</bean>

  1. Job类编写:

public class PromotionJob implements Job{

@Autowired

private PromotionService promotionService;

@Override

public void execute(JobExecutionContext context)

throws JobExecutionException {

//每分钟执行一次,当前时间大于promotion数据表中的endDate ,活动已经过期,设置status=’2′

promotionService.updateStatus(new Date());

}

}

  1. SimpleTriggerFactoryBean 配置:

将JobDetailFactoryBean 注入到 SimpleTriggerFactoryBean中:

<bean id=”simpleTrigger” class=”org.springframework.scheduling.quartz.SimpleTriggerFactoryBean”>

<property name=”jobDetail” ref=”promotionJob”></property>

<property name=”startDelay” value=”0″></property>

<property name=”repeatInterval” value=”10000″></property>

</bean>

  1. SchedulerFactoryBean的配置:

将 SimpleTriggerFactoryBean 注入到 SchedulerFactoryBean中,同时还注入了一个 jobFactory这个jobFactory 注入的目的是是为了让我们的Job具有Spring自动注入功能;

<bean id=”” class=”org.springframework.scheduling.quartz.SchedulerFactoryBean”>

<property name=”jobFactory” ref=”jobFactory”></property>

<property name=”triggers”>

<list>

<ref bean=”simpleTrigger”/>

</list>

</property>

</bean>

  1. 附上 jobFactory的代码:

@Service(“jobFactory”)

public class JobFactory extends AdaptableJobFactory {

@Autowired

private AutowireCapableBeanFactory capableBeanFactory;

@Override

protected Object createJobInstance(TriggerFiredBundle bundle)

throws Exception {

Object jobInstance = super.createJobInstance(bundle);

capableBeanFactory.autowireBean(jobInstance);

return jobInstance;

}

}

5.redis配置:

Redis是一个内存数据库,由于存取速度非常快,往往用作缓存; 作为一个数据库, 无非就是考虑两个问题:

  1. 怎么连接这个数据库,
  2. 怎么操作这个数据库;
  3. 配置连接池基本参数:

<!– jedis 连接池配置 –>

<bean id=”poolConfig” class=”redis.clients.jedis.JedisPoolConfig”>

<property name=”maxIdle” value=”300″ />

<property name=”maxWaitMillis” value=”3000″ />

<property name=”testOnBorrow” value=”true” />

</bean>

  1. 配置连接工厂:

<!– jedis 连接工厂 –>

<bean id=”redisConnectionFactory”

class=”org.springframework.data.redis.connection.jedis.JedisConnectionFactory”

p:host-name=”localhost” p:port=”6379″ p:pool-config-ref=”poolConfig”

p:database=”0″ />

  1. 配置模板:

<!– spring data 提供 redis模板 –>

<bean id=”redisTemplate” class=”org.springframework.data.redis.core.RedisTemplate”>

<property name=”connectionFactory” ref=”redisConnectionFactory” />

<!– 如果不指定 Serializer –>

<property name=”keySerializer”>

<bean class=”org.springframework.data.redis.serializer.StringRedisSerializer” />

</property>

<property name=”valueSerializer”>

<bean class=”org.springframework.data.redis.serializer.StringRedisSerializer”>

</bean>

</property>

</bean>

  1. Java代码编写示例:

// 2.将激活码保存到redis中 可以设置存货时间;

redisTemplate.opsForValue().set(model.getTelephone(), activeCode, 24, TimeUnit.HOURS);

6.SpringDataElasticSearch配置:

0.框架知识概述:

简化操作ElasticSearch的技术: 是SpringData 项目下的一个子项目,为了整合 ElasticSearch ; 经过一系列配置,提供了类似SpringDataJPA的操作模式;

ElasticSearch是一个全文搜索服务器 , 但本质上是一个索引库: 它的一些基本概念和关系型数据库概念对应关系如下:

索引(index)——->表(table)

文档(document)——->一条数据实体(entity)

域(filed) ——–> 字段(field)

作为一个数据库,我们还是关心两个老问题:1.怎么连接上这个数据库, 2.怎么操作这个数据库;

  1. 连接ElasticSearch:

1.配置客户端 client

<!– 配置elasticsearch 连接 其实这个地方配置的是客户端 :transportclient –>

<elasticsearch:transport-client id=”client” cluster-nodes=”localhost:9300″ />

2.配置模板对象: 注入client对象

<!– spring data elasticsearch DAO 必须依赖 elasticsearchTemplate –>

<bean id=”elasticsearchTemplate”

class=”org.springframework.data.elasticsearch.core.ElasticsearchTemplate”>

<constructor-arg name=”client” ref=”client” />

</bean>

3.实体类要配置映射

这个映射与hibernate中的对象映射有些类似,但是还有不同,这个映射配置, 其实配置了实体的存储策略(就想当于指明关系数据库的表结构怎么建),检索策略,分词策略,等;

4.连接配置小结:

配置客户端:client ——>注入 elasticsearchTemplate —–>实体配置映射注解;

这个地方实体映射配置某种程度上说是一个 单纯的映射配置的载体; 模板对象读取这个类时,可以读取这个对象中的映射文件;

  1. 操作ElasticSearch:

配置扫描dao , 并且我们的dao继承了 ElasticSearchRepository 接口,这样我们在

@autowired dao实现时,就会注入框架默认的实现;

<!– 扫描DAO包 自动创建实现 –>

<elasticsearch:repositories base-package=”cn.itcast.dao” />

3.ElasticSearch 配置小结:

我们连接ElasticSearch 是通过配置客户端操作对象 transportclient , 其实拿着这个client已经可以操作 ElasticSearch 了,但是 我们又对这个client进行了一次封装,封装成了一个 template (模板), 这个模板已经可以完成了一些基本操作了, 然而在此时,我们没有止步,而是又做了一层封装, 封装成了一个默认实现,我们编写一个dao 继承 ElasticsearchRepository 这个接口,然后AOP就会给我们返回一个默认实现的 repository,这个repository 实现了基本增删改, 规范的find查询方法,等等 这点有点儿类似SpringDataJPA ;

7.shiro配置:

Shiro是一个权限控制框架:

权限控制流程: applicationCode —–> Subject——> ShiroSecurityManager—–>Realm ;

其实权限控制问题简单来说就是: 哪些user 可以访问哪些 url的问题;

1.粗粒度url权限验证流程:

request 请求发出—>web.xml中的DelegatingFilterProxy 代理过滤器过滤到请求—-> 交给applicationContext.xml 中的真正的过滤器 ShiroFilterFactoryBean—-> 过滤器将请求交给 “安全管理器” securityManager —–>”安管”读取过滤器中的 “资源与访问权限对应清单” filterChainDefinitions —–>”安管”找到request要访问的资源需要的权限:permission1 —->

“安管” 调用 “小弟” realm 的 doGetAuthorizationInfo()方法,获得当前用户所持有的权限: permission2 —->此时:”安管”已经从过滤器中拿到 1. 要访问的资源需要的权限:permission1 2. 前用户所持有的权限: permission2 ,所以”安管”可以很轻松地将两个权限做比较,如果匹配,那么就允许用户访问资源,如果不匹配,则拒绝访问该资源,并跳转到没有权限访问的页面;

2.细粒度方法级别权限验证流程:

request请求发出—-> 调用 save()方法—-> Spring 尝试利用 IOC 创建cglib 动态代理对象,却发现目标类 的save()方法上有shiro注解,然后就利用AOP 在代理类的save方法上加上前置通知: AuthorizationAttributeSourceAdvisor 来读取save()方法上的 shiro注解,并将结果交给”安管”—–>securityManager 获得 save()方法访问应具备的权限 —–> 调用realm的doGetAuthorizationInfo()方法,来获得当前用户所持有的权限, 然后安管自己会对两个权限作对比,判定是否可以访问资源…;

3.两种权限验证方式对比分析:

粗粒度url权限控制 与 细粒度方法级别权限控制 对比分析:

粗粒度url权限控制

细粒度方法级别权限控制

资源控制者

securityManager

securityManager

资源访问所需权限获取来源

过滤器 shiroFilter 中的属性配置

Spring框架 (IOC和AOP共同作用)从方法的注解读取

当前用户访问权限获取来源

小弟: realm

小弟:realm

权限不足时处理方式

过滤器配置相应友好提示页面

抛出相应异常给方法调用者;

  1. 缓存框架Ehcache 的配置

0.框架知识概述:

当我们用Shiro做权限管理的时候,我们每一次对资源进行访问时我们的 “安全管理器” securityManager 会对当前用户进行权限的授权信息获取, 如果每次获取当前用户的授权信息都到数据库中查询,这样无疑是降低了资源获取的速度,用户体验不好,所以这种情况,我们必然要对用户权限信息进行缓存处理;

Flag:缓存技术不一定用在权限管理,但权限管理一定会用到缓存;

  1. 复制自带配置文件 ehcache-failsafe.xml

Ehcache 的jar包下有默认配置文件,我们将它复制到与applicationContext.xml 同级目录下;

我们在这个配置文件里边创建自定义的缓存空间;

  1. Spring整合(管理)Ehcache

Ehcache 的 EhCacheManager “缓存管理器” 交给Spring的EhCacheManagerFactoryBean “缓存管理器工厂bean” 来管理;

至此, ehcache已经在Spring的管理之中了;

<!– Ehcache配置 (ehcache的 “缓存管理器” 纳入Spring管理) –>

<bean id=”ehCacheManager” class=”org.springframework.cache.ehcache.EhCacheManagerFactoryBean”>

<property name=”configLocation” value=”classpath:ehcache.xml”></property>

</bean>

  1. Ehcache 对Shiro 的权限数据进行数据缓存
  2. Shiro 整合 Ehcache

Ehcache 底层是Map结构实现,所以Shiro要对Ehcache 的缓存管理器再做一层封装,指明什么作为key,什么作为value ,这个封装类就是: EhCacheManager ,我们将ehcache的 “缓存管理器” 属性注入到shiro 的 EhCacheManager :

<!– shiro配置 (将ehcache的 “缓存管理器” 属性注入到shiro) –>

<!– ehcache 底层是Map实现,所以Shiro要对ehcache的缓存管理器在做一层封装,指明什么作为key,什么作为value –>

<bean id=”shiroEhCacheManager” class=”org.apache.shiro.cache.ehcache.EhCacheManager”>

<property name=”cacheManager” ref=”ehCacheManager”></property>

</bean>

  1. Shiro “安全管理器” 获得 “Ehcache缓存管理器”

根据Shiro权限验证执行流程: 应用程序代码——> subject ——> securityManager—–>

Realm ——> 访问安全数据 ;

<!– 安全管理器 –>

<bean id=”securityManager” class=”org.apache.shiro.web.mgt.DefaultWebSecurityManager”>

<property name=”realm” ref=”bosRealm”></property>

<property name=”cacheManager” ref=”shiroEhCacheManager”></property>

</bean>

上边配置的结果是: “安全管理器” 获得了缓存操作权限; 这样它的小弟 realm 就可以向 “安全管理器” 申请一个缓存空间, 什么意思呢? 就是小弟realm说:你把 “bos” 这个缓存区给我,以后你找我要数据,你先到这个缓存区看看有没有,如果没有,我再去数据库帮你找到,然后你要把数据放进去,下次不要找我要了;

  1. Reaml 指定将 哪些 数据存入 哪个 缓存区

<!– Realm配置, 指明将 “授权数据” 缓存到 “bos” 缓冲区–>

<bean id=”bosRealm” class=”cn.itcast.bos.realm.BosRealm”>

<property name=”authorizationCacheName” value=”bos”></property>

</bean>

  1. Ehcache对权限数据缓存的 配置流程总结:

在Ehcache的 ehcache-failsafe.xml中自定义缓存区 ——> Spring与Ehcache的整合类(EhCacheManagerFactoryBean)读取配置文件 得到 ehCacheManager——> ehCacheManager

注入到 Shiro的 shiroEhCacheManager ——> shiroEhCacheManager 注入到 “安全管理器” securityManager ——> Reaml 通过属性配置 来 声明要缓存数据,以及要缓存的区域 (比如 bos) ;

4. Ehcache对一般数据的缓存配置:

1. Spring “缓存管理器” 封装 Ehcache的 “缓存管理器”

在Shiro 整合Ehcache时,有一个 shiroEhCacheManager 来管理 key-value的生成策略, 那么在对一般数据的缓存配置时,肯定是不能用这个缓存管理器了, 这个时候Spring 就出来说话了,他说 我定义一个 “缓存管理器” 来管理 一般数据的key-value的生成策略;

<!– Spring 封装Ehcache的缓存管理器,它规范了普通数据缓存时的key-value 生成策略 –>

<bean id=”springCacheManager” class=”org.springframework.cache.ehcache.EhCacheCacheManager”>

<property name=”cacheManager” ref=”ehCacheManager”/>

</bean>

这个缓存管理器一方面是获得 Ehcache 的缓存管理器,拥有了缓存操作权限 ; 另一方面是提供了一些标签,它可以解释这些标签,来执行缓存操作 ;

2.在Spring的配置文件中 激活spring 缓存注解

目的就是让Spring 能够识别它管理的类中的缓存注解;

<!– 激活spring 缓存注解 –>

<cache:annotation-driven cache-manager=”springCacheManager”/>

3.在被Spring管理的bean的方法上使用 @Cacheable() , @CacheEvict() :

@Cacheable(value=”缓存区”) : 应用缓存, 对方法返回结果进行缓存, 这个注解多用在查询方法上; 至于谁是key ,谁是value 由Spring的缓存管理器来负责; 如果被注解的方法有不同的 传入参数, 可在注解中使用SpEL (Spring表达式) 自定义key值;

@CacheEvict (value=”缓存区”,allEntries=”true”): 清除缓存区的数据, allEntries代表所有数据;

  1. 缓存配置中的注意点:

被缓存的对象数据对应的类要序列化 : implements Serializable ;

  1. Freemarker 模板引擎

Struts2 默认是使用 Freemarker作为自定义标签模板,所以项目导入了Struts2 就已经导入了Freemarker的jar包;

0.技术概述:

Freemarker就是一个模板引擎,它类似我们之前学习的jsp; FreeMarker 它通过编写一个模板文件,结合我们程序中的动态数据,输出标准的文本内容,一般情况下我们用它来生成html文件比较多;

简单来说: 模板文件 + java数据对象 == 输出 (任何格式文本)

FreeMarker 模板文件,通常扩展名 .ftl (当然也可以用 .html / .jsp)

我们编写前,要安装一个Freemarker的插件,安装到eclipse ,这样我们编辑时就会有提示; Freemarker 模板文件中的变量 跟EL 表达式一样,都是 ${变量名}

  1. 编写模板文件 promotion_detail.ftl

<link rel=”stylesheet” type=”text/css” href=”css/promotion_detail.css”>

<div class=”container promotions” >

<div class=”col-md-2 prolist”>

<h5 class=”title”><a href=”#/promotion”><strong>返回促销列表</strong></a></h5>

<img src=”images/pro.jpg” class=”img-responsive”>

</div>

<div class=”col-md-10 procontent”>

<h5 class=”title”>${promotion.title}</h5>

<div class=”intro”>

<p>活动范围: ${promotion.activeScope}</p>

<p>活动时间: ${promotion.startDate?string(“yyyy-MM-dd”)} –

${promotion.endDate?string(“yyyy-MM-dd”)}</p>

</div>

<div class=”partline clearfix”></div>

<div class=”promotionbox”>

${promotion.description}

</div>

</div>

</div>

  1. 编写java代码

// 用面向对象思想,将配置文件目录封装为configuration对象

Configuration configuration = new Configuration(

Configuration.VERSION_2_3_22);

configuration.setDirectoryForTemplateLoading(new File(

ServletActionContext.getServletContext().getRealPath(

“/WEB-INF/freemarker_templates”)));

// 用 configuration 获取模板对象 template

Template template = configuration

.getTemplate(“promotion_detail.ftl”);

// 动态数据

Promotion promotion = WebClient

.create(Constants.BOS_MANAGEMENT_URL

+ “/bos_management/services/promotionService/promotion/”

+ model.getId()).accept(MediaType.APPLICATION_JSON)

.get(Promotion.class);

//构造动态数据 ()

Map<String, Object> parameterMap = new HashMap<String, Object>();

parameterMap.put(“promotion”, promotion);

//使用模对象合并数据 template 合并输出

/* @param dataModel the holder of the variables visible from the template (name-value pairs); usually a {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables) */

template.process(parameterMap, new OutputStreamWriter(

new FileOutputStream(htmlFile), “utf-8”));

模板对象 template 合并数据时,将数据模型数据dataModel 输出到writer流中; 而这个dataModel 的数据类型一般是 Map<String, Object> , 当然也可以是 JavaBean ,这个 JavaBean 的属性是模板中的变量; 另外要说一点,如果模板中有变量必须全部赋值, 如果存在一个没有赋值, 好像会报错;

  1. Spring与Mybatis整合配置

PS: mybatis框架的特别之处是,它实现了sql语句与java代码的分离,所以它要解决的核心问题是如何让mapper.xml中的sql语句在数据库操作时起作用;

另外说明一点:

在mybatis单独使用时: mapper.xml—–> SqlMapConfig.xml ——> sqlSessionFactory —–>sqlSession , 所以这个时候 mapper.xml的引用在sqlSession中,因此 外置sql语句的调用是由sqlSession来完成的;

在mybatis与spring整合时: SqlMapConfig.xml —-> sqlSessionFactory —-> MapperFactoryBean; 由于在mybatis与spring整合时,SqlMapConfig.xml是一个空的文件,所以里边没有mapper.xml的引用,所以没有办法使用里边的sql,这个时候怎么解决这个问题呢? 通过一个强制要求: 映射文件(mapper.xml)必须与相应的接口同名且在同一个目录下,这样在创建代理时,在读取接口的同时会在相同目录下去寻找映射文件,这样就同时利用接口和映射文件生成了代理对象;

  1. 连接数据库
  2. 数据库连接参数抽取: db.properties

jdbc.driver=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8

jdbc.username=root

jdbc.password=123

  1. Spring核心配置文件applicationContext.xml 中加载db.properties并配置连接池BasicDataSource

1.简单说明:

连接池的作用是提供连接,并且不用反复创建销毁数据库连接;

2.配置内容:

<!– 加载属性配置文件 –>

<context:property-placeholder location=”classpath:db.properties”/>

<!– 配置连接池 –>

<bean id=”dataSource” class=”org.apache.commons.dbcp.BasicDataSource”

destroy-method=”close”>

<property name=”driverClassName” value=”${jdbc.driver}” />

<property name=”url” value=”${jdbc.url}” />

<property name=”username” value=”${jdbc.username}” />

<property name=”password” value=”${jdbc.password}” />

<property name=”maxActive” value=”10″ />

<property name=”maxIdle” value=”5″ />

</bean>

  1. 配置Spring与Mybatis整合类 org.mybatis.spring.SqlSessionFactoryBean

1.简单说明:

sqlSessionFactory 跟hibernate的SessionFactory , SpringDataJPA的EntityManagerFactory 的功能非常类似:

1.注入连接池,获得了连接数据库的能力;

2. 注入了映射配置文件位置; 区别在于:hibernate的映射配置文件是配置了实体与表之间的映射,最后干脆通过注解,将映射配置放到了实体类中,最后sql语句(或者hql)的编写就写到dao中了; 然后就是SpringDataJPA, 它是在Hibernate基础之上,将一部分sql语句做了封装实现,这样我们在对数据库进行一些简单操作的时候,我们可以直接通过调用一些方法就可以了,等于是帮我们已经完成了一些简单的CRUD操作; 但是一些复杂的查询操作,我们还是需要自己在 Dao中编写查询语句(hql或者是sql),不论是hibernate还是 SpringDataJPA 都没有将Sql语句与java 代码分离, 而mybatis却做了这一点: mybatis的映射配置文件中有三部分: 1.输入映射; 2.输出映射;3. sql语句映射; 其中输入输出映射完成了类似orm映射的功能,完成类与表的对应; 而sql语句映射其实是提供了一些固定的sql操作,然后我们通过:

1. sql映射的namespace 使用Dao接口的 全限定名

2.sql映射的 statementId 与Dao接口的方法名一致

3.sql映射的parameterType与Dao中方法的参数类型一致

4.sql映射的resultType与Dao中方法的返回值类型一致

以上四点约定,我们的sql映射实际上已经成为以后Mybatis使用Mapper代理方式时,Spring创建代理的依据; 所以说某种程度上说mapper映射文件中的sql映射部分可以视作是Dao接口的实现类; 这样其实实现了 “实现类”到”映射文件”的转变( java代码—–>xml配置的转变), 将sql语句从java 代码中抽离出来了;

2.配置内容:

<!– 配置sqlSessionFactoryBean –>

<bean id=”sqlSessionFactory” class=”org.mybatis.spring.SqlSessionFactoryBean”>

<!– 注入连接池 –>

<property name=”dataSource” ref=”dataSource”></property>

<!– 加载mybatis 核心配置文件 –>

<property name=”configLocation” value=”classpath:mybatis/SqlMapConfig.xml”/>

</bean>

  1. 操作数据库
  2. 简单说明:

我在前面总结持久层框架的时候喜欢将配置分为两部分:

  1. 连接数据库 : 得到SessionFactory
  2. 操作数据库 : 将SessionFactory 与dao融合;

2.配置内容:

<!– 第一种方式: 通过设置mapper接口 ; 缺点:一次只能配置一个接口–>

<!– <bean class=”org.mybatis.spring.mapper.MapperFactoryBean<T>”>

<property name=”sqlSessionFactory” ref=”sqlSessionFactory”></property>

<property name=”mapperInterface” value=”cn.itheima.mybatis.mapper.UserMapper”></property>

</bean> –>

<!– 第二种方式: 通过对mapper包扫描;

优点:1.可以对mapper包下的所有接口完成代理; 2.会自动注入 sqlSessionFactory –>

<bean class=”org.mybatis.spring.mapper.MapperScannerConfigurer”>

<property name=”basePackage” value=”cn.itheima.mybatis.mapper”/>

</bean>

上边两种配置都可以完成 sqlSessionFactory 与Dao的融合,但是第二种更有优势: 我们简单分析一下: 它是通过扫描包,然后找到包下的接口,然后读取到包下同名的mapper.xml映射文件,然后根据 接口和映射文件创建 接口实现类对象;

8.持久层框架配置套路总结:

其实:持久层无非做了两件事: 1.连接数据库, 2操作数据库 ;围绕这两件事每个框架都各有千秋,但是它们与Spring整合时的配置应该是有一些套路的:

1 . Spring 的JdbcTemplate

这个类简化了JDBC操作,

它的基本操作: update(sql语句, 参数); query(sql语句, 参数);

操作流程如下:

  1. 配置驱动连接池 (可以用默认的,也可以单独配比如C3P0连接池),属性注入连接参数: driven, url , username , password
  2. 配置 JdbcTemplate
  3. 我们可以在java类中直接注入 JdbcTemplate 进行CRUD操作;

2.hibernate:

这是一个orm框架,通过映射文件,实现了对数据库表的操作 到 对类的对象操作的转化

操作流程如下:

  1. 将基本连接参数分离成一个.properties文件
  2. 在application.xml中引入.properties文件
  3. 配置连接池 (注入连接参数)
  4. 配置 LocalSessionFactory (注入1.连接池; 2.数据库基本配置; 3.映射文件位置 )
  5. 编写dao 继承 HibernateDaoSupport类 并在
  6. 配置dao (注入sessionFactory, 本质上是注入到HibernateDaoSupport 中) 这样在dao中就可以获得: hibernateTemplate 模板对象;
  7. 利用模板对象进行CRUD操作,这个时候操作的,这个模板对象也是简化hibernate数据访问操作;

3.SpringDataJPA

1. 将基本连接参数分离成一个.properties文件

2. 在application.xml中引入.properties文件

3. 配置连接池 (注入连接参数)

4. 配置LocalContainerEntityManagerFactoryBean

注入 : 1.连接池; 2.domain扫描包路径(相当于映射文件配置); 3.持久化提供者; 4.其他数据库基本配置;

<property name=”dataSource” ref=”dataSource” />

<property name=”packagesToScan” value=”cn.itcast.bos.domain” />

<property name=”persistenceProvider”>

<bean class=”org.hibernate.jpa.HibernatePersistenceProvider” />

</property>

5. Jpa扫描dao; 这一步等价于hibernate中的 dao配置,hibernate中是给dao中注入sessionFactory,使到可以获得hibernateTemplate模板, 这个地方 通过扫描dao的方式,在获得dao对象时通过AOP技术提供给我们默认的dao实现;

<jpa:repositories base-package=”cn.itcast.bos.dao” />

4.redis

1.配置连接池 JedisPoolConfig

2.配置连接工厂 JedisConnectionFactory

3.配置模板 RedisTemplate

4.java代码中操作:

redisTemplate.opsForValue().set(model.getTelephone(), activeCode, 24, TimeUnit.HOURS);

5.小结:

通过比较持久层这个几个配置,我们可以看出来,Spring在整个各种框架是,基本套路也就是:

  1. 配置连接参数, 2.配置连接池, 3.配置连接工厂 4.配置模板;

相比之下: SpringDataJPA做的事情更多一些,它没有直接提供给你一个模板,而是编写了一个默认实现,你只要让你的dao实现了JPARepository / JpaSpecificationExecutor , 在配上 jpa扫描:

<jpa:repositories base-package=”cn.itcast.bos.dao” />

在你注入这个包下的对象时,它就会判断你是否继承了 JPARepository / JpaSpecificationExecutor 这些接口,如果继承了,那么它会利用AOP返给你一个默认实现的类,而在这个类中,已经给我们默认实现了一些CRUD常用操作,所以避免了常用CRUD操作的方法编写;

各种配置文件总结

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/9948.html

(0)
上一篇 2023年 4月 22日 下午11:54
下一篇 2023年 4月 22日 下午11:54

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信