spring 学习笔记


spring 2.0学习杂记
 spring环境搭配
1、spring依赖库
 * SPRING_HOME/dist/spring.jar     <--  核心jar包-->
 * SPRING_HOME/lib/jakarta-commons/commons-logging.jar   <--  日志jar包-->
 * SPRING_HOME/lib/log4j/log4j-1.2.14.jar    <--  日志jar包-->
 
2、拷贝spring配置文件到src下  web项目可以放到 WEB―INF下
3、拷贝log4j配置文件到src下
4、在UserManagerImpl中提供构造函数或setter方法,spring将实例化好的UserDao实现注入给我们
5、让spring管理我们的对象创建和依赖,必须在spring配置中进行定义
具体配置参照;
        <bean id="userDao4MySqlImpl" class="com.bjsxt.spring.dao.UserDao4MySqlImpl"/>
 <bean id="userDao4OracleImpl" class="com.bjsxt.spring.dao.UserDao4OracleImpl"/>
 
 <bean id="userManager" class="com.bjsxt.spring.manager.UserManagerImpl">
  
  <!-- 构造方法注入
  <constructor-arg ref="userDao4OracleImpl"/>
   -->
                  <--  set方法注入-->
    <property name="userDao" ref="userDao4OracleImpl"/>
 </bean>
说明:此配置完成了把com.bjsxt.spring.dao.UserDao4OracleImpl对象创建好后交给com.bjsxt.spring.manager.UserManagerImpl的功能;
6、编写客户端
                //通过BeanFactory 来读取("applicationContext.xml");文件
                //获得UserManager 类 同时在解析("applicationContext.xml");文件时它会自动把UserManager 所依赖的类new                //好注入到UserManager 中
                BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
  
  UserManager userManager = (UserManager)factory.getBean("userManager");
  userManager.save("张三", "123");

spring Ioc容器的关键点:
 * 必须将被管理的对象定义到spring配置文件中
 * 必须定义构造函数或setter方法,让spring将对象注入过来
*******************************************************************************************************************
spring的普通属性注入 
 参见:spring文档3.3章节
 
什么是属性编辑器,作用?
 * 自定义属性编辑器,spring配置文件中的字符串转换成相应的对象进行注入
 spring已经有内置的属性编辑器,我们可以根据需求自己定义属性编辑器
 
 * 如何定义属性编辑器?
  * 继承PropertyEditorSupport类,覆写setAsText()方法,参见:UtilDatePropertyEditor.java
  * 将属性编辑器注册到spring中,参见:
  <!-- 定义属性编辑器 -->     
 <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
  <property name="customEditors">
   <map>
    <entry key="java.util.Date">
     <bean class="com.bjsxt.spring.UtilDatePropertyEditor">
      <property name="format" value="yyyy-MM-dd"/>
     </bean>
    </entry>
   </map>
  </property>
 </bean> 
 
 <!--
 <bean id="utilDatePropertyEditor" class="com.bjsxt.spring.UtilDatePropertyEditor"></bean>
  -->
</beans>

日期转化属性编辑器
/**
 * java.util.Date属性编辑器
 * @author Administrator
 *
 */
public class UtilDatePropertyEditor extends PropertyEditorSupport {
 private String format="yyyy-MM-dd";
 
 @Override
 public void setAsText(String text) throws IllegalArgumentException {
  System.out.println("UtilDatePropertyEditor.saveAsText() -- text=" + text);
  
  SimpleDateFormat sdf = new SimpleDateFormat(format);
  try {
   Date d = sdf.parse(text);
   this.setValue(d);
  } catch (ParseException e) {
   e.printStackTrace();
  }
 }
 public void setFormat(String format) {
  this.format = format;
 }
}
 

  
依赖对象的注入方式,可以采用:
 * ref属性
 * <ref>标签
 * 内部<bean>来定义
 
如何将公共的注入定义描述出来?
 * 通过<bean>标签定义公共的属性,指定abstract=true
 * 具有相同属性的类在<bean>标签中指定其parent属性
 
 参见:
<bean id="beanAbstract" abstract="true">
     <property name="id" value="1000"/>
     <property name="name" value="Jack"/>
   </bean>        
  
   <bean id="bean3" class="com.bjsxt.spring.Bean3" parent="beanAbstract">
     <property name="name" value="Tom"/>           这里可以覆盖公共部分的值 把jack  替换成Tom
     <property name="password" value="123"/>
   </bean>       
  
   <bean id="bean4" class="com.bjsxt.spring.Bean4" parent="beanAbstract"/>
</beans>
************************************************************************************************************
spring Bean的作用域:
 
scope可以取值: 
 * singleton:每次调用getBean的时候返回相同的实例  配置文件为:<bean id="bean1" class="com.bjsxt.spring.Bean1" scope="singleton"/>
 * prototype:每次调用getBean的时候返回不同的实例  配置文件为:<bean id="bean1" class="com.bjsxt.spring.Bean1" scope="prototype"/>
************************************************************************************************************
根据名称自动装配
*在配置文件中加入:default-autowire="byName"
如下所示:
           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"
      (加在这里)     default-autowire="byName"
           >
*在配置文件中所要依赖的bean的id必须和类对象中的属性名一样、如:
类bean2
        public class Bean2 {
 private Bean3 bean3;
 
 private Bean4 bean4;
 
 private Bean5 bean5;
则在配置文件中:
      
       <bean id="bean3" class="com.bjsxt.spring.Bean3" parent="beanAbstract">
     
  
       <bean id="bean4" class="com.bjsxt.spring.Bean4" parent="beanAbstract"/>
 
       <bean id="bean5" class="com.bjsxt.spring.Bean5">
则:bean3; bean4; bean5;的id都必须为bean3; bean4; bean5;
**********************************************************************************************************
根据类型自动装配
*在配置文件中加入:default-autowire="byName"
它会自动根据类中所引用的类自动去查找然后给你注入过来
跟配置文件中的name属性没有关系了

这样比根据名称自动装配来的简单一般建议使用根据类型自动装配
**********************************************************************************************************
*                                            IOC内容学习到                                               *
*                                               此结束                                                   *
**********************************************************************************************************
spring 静态代理
代理要跟目标对象的接口一致
说白了就是从新写一个对象的实现类
然后再调用的时候直接调用的是代理就行了这样没有实质性的解决问题   只是没有打乱原来程序的结构而已
***********************************************************************************************************
spring动态代理
*代理要实现一个接口implements InvocationHandler
*目标对象要实现接口才能实现代理
Object targetObject  目标对象
返回代理对象Object newProxy(Object targetObject)  把目标对象传过来产生呢过一个代理对象
       public Object newProxy(Object targetObject) {
  this.targetObject = targetObject;
  return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),   //取得ClassLoader()
            targetObject.getClass().getInterfaces(), //得到接口
      this);//传入一个实现了InvocationHandler的类
     
 }
********
参见动态代理实例:
public class SecurityHandler implements InvocationHandler {
 private Object targetObject;
 
 public Object newProxy(Object targetObject) {
  this.targetObject = targetObject;
  return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
           targetObject.getClass().getInterfaces(),
           this);
     
 }
 
 public Object invoke(Object proxy, Method method, Object[] args)
   throws Throwable {
  checkSecurity();
  Object ret = null;
  try {
   ret = method.invoke(this.targetObject, args);
  }catch(Exception e) {
   e.printStackTrace();
   throw new java.lang.RuntimeException(e);
  }
  return ret;
 }
 private void checkSecurity() {
  System.out.println("----------checkSecurity()---------------");
 }
 
}
************************************
客户端调用:
public class Client {
 public static void main(String[] args) {
  
  
  SecurityHandler handler = new SecurityHandler();   创建一个代理类
  UserManager userManager = (UserManager)handler.newProxy(new UserManagerImpl());  创建一个代理吧目标代理实现类传入
  
  //userManager.addUser("张三", "123");
  userManager.deleteUser(1);
}
**************************************************************************************************************8
AOP属于:
cross cuting concern 横切性关注点
对横切性关注点模块化叫aspects切面
横切性关注点的具体实现叫advice(分为after advic   ;beforeadvice;excption advice)
advice 具体应用到那里叫pointcut如:增加方法 删除方法等等
应用到具体类的过程称为置入weave
方法叫joinpoint 切入点
target 代理对象
proxy 代理
*******************************************************************************
spring AOP实现
 
采用Annotation的方式
1、spring依赖库
 * SPRING_HOME/dist/spring.jar
 * SPRING_HOME/lib/jakarta-commons/commons-logging.jar
 * SPRING_HOME/lib/log4j/log4j-1.2.14.jar
 * SPRING_HOME/lib/aspectj/*.jar
 
2、采用Aspect定义切面  方法:@aspect
3、定义切面应用范围  在Aspect定义Pointcut和Advice  pointcut支持的表达式 表有没返回值 那些方法  有没有参数还是没参数
   如:@Pointcut("execution(* add*(..)) || execution(* del*(..))")

参见如下配置实例:
@Aspect
public class SecurityHandler {
 
 /**
  * 定义Pointcut,Pointcut的名称就是allAddMethod,此方法不能有返回值和参数,该方法只是一个
  * 标识
  *
  * Pointcut的内容是一个表达式,描述那些对象的那些方法(订阅Joinpoint)
  */
 @Pointcut("execution(* add*(..)) || execution(* del*(..))")
 private void allAddMethod(){};   
 
 /**
  * 定义Advice,标识在那个切入点何时织入此方法
  */
 @Before("allAddMethod()")
 private void checkSecurity() {
  System.out.println("----------checkSecurity()---------------");
 }
 
}
 

4、启用AspectJ对Annotation的支持并且将Aspect类和目标对象配置到Ioc容器中   在配置文件中加入
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">   在此加入  <aop:aspectj-autoproxy/>

另外还要把需要spring管理的对象配置到配置文件中
如:
<bean id="securityHandler" class="com.bjsxt.spring.SecurityHandler"/>          
<bean id="userManager" class="com.bjsxt.spring.UserManagerImpl"/>
客户端的调用:
               从spring beanfactory中拿出被管理的对象(注意这里拿到的是一个代理aop默认的就是动态代理的实现
) 如本例中的:UserManager
                BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
  
  UserManager userManager = (UserManager)factory.getBean("userManager");
  
  userManager.addUser("张三", "123");
  userManager.deleteUser(1);
 
注意:在这种方法定义中,切入点的方法是不被执行的,它存在的目的仅仅是为了重用切入点
即Advice中通过方法名引用这个切人点
AOP:属于
 * Cross cutting concern    横切性问题
 * Aspect                   横切性问题组成的类
 * Advice                   对横切性问题的具体实现
 * Pointcut                 圈定的一个范围 在spring中只支持方法及的jointcut
 * Joinpoint
 * Weave                    织入 advice应用到jointcut上的过程
 * Target Object           
 * Proxy
 * Introduction            动态的为某个类增加方法
*******************************************************************************************************************
spring AOP实现
 
采用配置文件的方式
配置参见
 <bean id="securityHandler" class="com.bjsxt.spring.SecurityHandler"/>       切面类里面有   
 
 <bean id="userManager" class="com.bjsxt.spring.UserManagerImpl"/>           需要检查的类
 
 <aop:config>
  <aop:aspect id="security" ref="securityHandler">         定义一个切面  指向定义好的切面类
   <aop:pointcut id="allAddMethod" expression="execution(* com.bjsxt.spring.UserManagerImpl.add*(..))"/>  定义切入点 支持表达式
   <aop:before method="checkSecurity" pointcut-ref="allAddMethod"/>   定义何时调用安全性检查  用哪个方法去检查   检查哪些方法
  </aop:aspect>
 </aop:config> 
</beans>
 
 

*******************************************************************************************************************
我们可以通过Advice中添加一个JoinPoint参数,这个值会由spring自动传入,从JoinPoint中可以取得
参数值、方法名等等
在切面类中获得 方法的参数 则要在切面类的方法中加入参数JoinPoint如:private void checkSecurity(JoinPoint joinPoint)
可以通过获得对象数组来得到传入的参数如:
Object[] args = joinPoint.getArgs();
  for (int i=0; i<args.length; i++) {
   System.out.println(args[i]);
  }
另外可以通过:System.out.println(joinPoint.getSignature().getName()获得方法名

spring对AOP的支持
Aspect默认情况下不用实现接口,但对于目标对象(UserManagerImpl.java),在默认情况下必须实现接口
如果没有实现接口必须引入CGLIB库

******************************************************************************************************************
jdk 动态代理和CGLIB
Aspect默认情况下不用实现接口,但对于目标对象(UserManagerImpl.java),在默认情况下必须实现接口
如果没有实现接口必须引入CGLIB库
我们可以通过Advice中添加一个JoinPoint参数,这个值会由spring自动传入,从JoinPoint中可以取得
参数值、方法名等等
*******************************************************************************************************************
spring对AOP的支持
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

如何强制使用CGLIB实现AOP?
 * 添加CGLIB库,SPRING_HOME/cglib/*.jar
 * 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
 
JDK动态代理和CGLIB字节码生成的区别?
 * JDK动态代理只能对实现了接口的类生成代理,而不能针对类
 * CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
   因为是继承,所以该类或方法最好不要声明成final 
****************************************************************************************************************** 采用编程式事务
1、getCurrentSession()与openSession()的区别?
 * 采用getCurrentSession()创建的session会绑定到当前线程中,而采用openSession()
   创建的session则不会
 * 采用getCurrentSession()创建的session在commit或rollback时会自动关闭,而采用openSession()
   创建的session必须手动关闭
参见:
保存用户:public void addUser(User user) {
  Session session = null;
  try {
session = HibernateUtils.getSessionFactory().getCurrentSession();
这里调用了addLog(log) 方法
保存日志:public void addLog(Log log) {
  HibernateUtils.getSessionFactory().getCurrentSession().save(log);
 }
此时两个地方得到的是同一个session
客户端调用参见:public class Client {
 public static void main(String[] args) {
  User user = new User();
  user.setName("张三");
  
  UserManager userManager = new UserManagerImpl();
  userManager.addUser(user);
 }
 
  
2、使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置:
 * 如果使用的是本地事务(jdbc事务)
 <property name="hibernate.current_session_context_class">thread</property>
 * 如果使用的是全局事务(jta事务)
 <property name="hibernate.current_session_context_class">jta</property>   
******************************************************************************************************************
采用声明式事务
1、声明式事务配置
 * 配置SessionFactory
 * 配置事务管理器
 * 事务的传播特性
 * 那些类那些方法使用事务

参见如下配置:
         <!-- 配置sessionFactory  把hibernate中的classpath:hibernate.cfg.xml文件注入到spring中号让它获得sessionFactory-->
         <!--sessionFactory注入到spring的 org.springframework.orm.hibernate3.LocalSessionFactoryBean类中的configLocation方法上-->
  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="configLocation">
   <value>classpath:hibernate.cfg.xml</value>  注意这里个协议:classpath让spring到classpath找hibernate.cfg.xml文件
  </property> 
 </bean>          
 
 <!-- 配置事务管理器  把sessionFactory注入到事务管理器中 注入到org.springframework.orm.hibernate3.HibernateTransactionManager中的sessionFactory属性上-->
 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory">
   <ref bean="sessionFactory"/>
  </property> 
 </bean>
 
 <!-- 配置事务的传播特性 -->
 <tx:advice id="txAdvice" transaction-manager="transactionManager">
  <tx:attributes>
   <tx:method name="add*" propagation="REQUIRED"/>
   <tx:method name="del*" propagation="REQUIRED"/>
   <tx:method name="modify*" propagation="REQUIRED"/>
   <tx:method name="*" read-only="true"/>
  </tx:attributes>
 </tx:advice>
 
 <!-- 那些类的哪些方法参与事务 -->
 <aop:config>
  <aop:pointcut id="allManagerMethod" expression="execution(* com.bjsxt.usermgr.manager.*.*(..))"/>  配置pointcut的范围
  <aop:advisor pointcut-ref="allManagerMethod" advice-ref="txAdvice"/>  此处相当于是一个切面 要指定切入点pointcut 和advice 上面的事务传播特性就是advice
 </aop:config>
</beans>
*************
<bean id="userManager" class="com.bjsxt.usermgr.manager.UserManagerImpl">
 <property name="sessionFactory" ref="sessionFactory"/>  每个交给spring管理的类都必须手动加入sessionFactory
  <property name="logManager" ref="logManager"/>
 </bean>
 
 <bean id="logManager" class="com.bjsxt.usermgr.manager.LogManagerImpl">
  <property name="sessionFactory" ref="sessionFactory"/>
 </bean>
***
2、编写业务逻辑方法
 * 继承HibernateDaoSupport类,使用HibernateTemplate来持久化,HibernateTemplate是
   Hibernate Session的轻量级封装
 * 默认情况下运行期异常才会回滚(包括继承了RuntimeException子类),普通异常是不会滚的
 * 编写业务逻辑方法时,最好将异常一直向上抛出,在表示层(struts)处理
 * 关于事务边界的设置,通常设置到业务层,不要添加到Dao上 
3、了解事务的几种传播特性
 1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启
 2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
 3. PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
 4. PROPAGATION_REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
 5. PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。
 6. PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常
 7. PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务,
      则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行
4、Spring事务的隔离级别
 1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
      另外四个与JDBC的隔离级别相对应
 2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
      这种隔离级别会产生脏读,不可重复读和幻像读。
 3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
 4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
      它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
 5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
      除了防止脏读,不可重复读外,还避免了幻像读。     
*******************************************************************************************************************
spring+struts的集成(第一种集成方案)
原理:在Action中取得BeanFactory对象,然后通过BeanFactory获取业务逻辑对象
1、spring和struts依赖库配置
 * 配置struts
  --拷贝struts类库和jstl类库     (struts-1.2.9-bin\lib 下面的所有jar包和jstl_install\lib下两个jar包
  --修改web.xml文件来配置ActionServlet
  --提供struts-config.xml文件
  --提供国际化资源文件
 * 配置spring
  --拷贝spring类库
                 1、spring-framework-2.0\dist下的spring.jar 核心jar
                 2、spring-framework-2.0\lib\jakarta-commons下commons-logging.jar 日志记录
                 3、spring-framework-2.0\lib\log4j下log4j-1.2.14.jar
                 4、spring-framework-2.0\lib\aspectj下的两个jar
  --提供spring配置文件
  
2、在struts的Action中调用如下代码取得BeanFactory
在action中部建议这样来得到BeanFactory 因为这样每次初始化到要创建BeanFactory 而BeanFactory 是重量级的浪费资源
                // BeanFactory factory= new
  // ClassPathXmlApplicationContext("applicationContext-beans.xml");
  // Usermanager Usermanager=(Usermanager)factory.getBean("usermanager");
  // Usermanager.login(username, password);
建议这样做:
在web.xml文件中配置
        <context-param>  这个标签是全局的只要能得到servlet的地方都能得到它
   <param-name>contextConfigLocation</param-name>   spring会根据contextConfigLocation(此名不能变)得到下面的配置文件生成beanfactory
   <param-value>classpath*:applicationContext-*.xml</param-value>   在src下这样配置  注意这里个协议
  </context-param>
  
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
  

然后在action 中这样获得BeanFactory  
BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
3、通过BeanFactory取得业务对象,调用业务逻辑方法 
此方案的缺点:依赖了spring因为在action中要用到spring的BeanFactory  
*******************************************************************************************************************
spring+struts的集成(第二种集成方案)
原理:将业务逻辑对象通过spring注入到Action中,从而避免了在Action类中的直接代码查询
1、spring和struts依赖库配置
 * 配置struts
  --拷贝struts类库和jstl类库
  --修改web.xml文件来配置ActionServlet
  --提供struts-config.xml文件
  --提供国际化资源文件
 * 配置spring
  --拷贝spring类库
  --提供spring配置文件
2、因为Action需要调用业务逻辑方法,所以需要在Action中提供setter方法,让spring将业务逻辑对象注入过来

在spring配置文件中配置action;把action交给spring来管理
参加如下配置:(注意这里的bean标签中不能用原来的id了而该成name 而且它的值要和action的paths值一样)
        <bean name="/login" class="com.bjsxt.usermgr.actions.LoginAction" scope="prototype" scope="prototype">
  <property name="userManager" ref="userManager"/>
 </bean>
 
3、在struts-config.xml文件中配置Action
  * <action>标签中的type属性需要修改为org.springframework.web.struts.DelegatingActionProxy
   DelegatingActionProxy是一个Action,主要作用是取得BeanFactory,然后根据<action>中的path属性值
   到IoC容器中取得本次请求对应的Action
参见如下配置:
     <action-mappings>
  
  <action path="/logininput"
    forward="/login.jsp"
  ></action>
 
  <action path="/login"        这里的值和spring配置文件中的name值一样
    type="org.springframework.web.struts.DelegatingActionProxy"   这里配置的是spring的代理action而不是原来的action
    name="loginForm"
    scope="request" 
  >
   <forward name="success" path="/success.jsp"/>
  </action>
 </action-mappings>
  
4、在spring配置文件中需要定义struts的Action,如:
 <bean name="/login" class="com.bjsxt.usermgr.actions.LoginAction" scope="prototype">
  <property name="userManager" ref="userManager"/>
 </bean>
 * 必须使用name属性,name属性值必须和struts-config.xml文件中<action>标签的path属性值一致
 * 必须注入业务逻辑对象
 * 建议将scope设置为prototype,这样就避免了struts Action的线程安全问题
      
*******************************************************************************************************************
 
 
 
 
 
 
 
 

 

你可能感兴趣的:(spring,职场,休闲)