Spring容器Spring快速入门

​​文件头约束:

D:\spring3_day1\spring-framework-3.2.0.RELEASE\docs\spring-framework-reference\html\xsd-config.html

  1. 使用反转控制注入值。

uploading.4e448015.gif正在上传…重新上传取消

 

头文件位置:

\spring-framework-3.2.0.RELEASE\docs\spring-framework-reference\html

下面的:xsd-config.xml.

uploading.4e448015.gif正在上传…重新上传取消

ApplicationContext application =

            new ClassPathXmlApplicationContext("beans.xml");

HelloService hello = (HelloService) application.getBean("helloSpring");

      System.out.println(hello);

Property标签里面name属性就是javabean里面需要注入属性值的属性。

Value就是要注入的值。

HelloBean依赖info进行注入。

  1. 使用类构造器注入

uploading.4e448015.gif正在上传…重新上传取消

使用默认的默认的构造函数。

uploading.4e448015.gif正在上传…重新上传取消

ApplicationContext application =

            new ClassPathXmlApplicationContext("beans.xml");

      Bean1 bean1 = (Bean1) application.getBean("bean1");

      System.out.println(bean1);

运行结果:cn.itcast.springdayone.Bean1@1fcc0a2

结果分析:从结果可以看出,调用了Bean1的构造函数,获取了bean1的实例,类构造器进行注入实例成功。

  1. 三种实例化Bean的方式。
    • 第一种就是使用类构造器方式进行注入,就是上面这种方式。
    • 第二种:静态工厂实例化。

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

ApplicationContext application =

            new ClassPathXmlApplicationContext("beans.xml");

         Bean2 bean2 = (Bean2) application.getBean("bean2");

    System.out.println(bean2);

uploading.4e448015.gif正在上传…重新上传取消

运行结果:cn.itcast.springdayone.Bean2@9a9b65

结果分析:通过实例化静态工厂,调用工厂静态方法获取对象实例。

    • 使用实例工厂方式实例化。

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

  1. Spring的作用域。
    • Bean的id属性和name属性。
  1. id属性在Ioc容器中必须唯一。
  2. name属性在Ioc容器中可以不唯一,一般情况下就使用id属性即可。

但是如果含有特殊字符时,就需要使用name属性。

如果name属性相同,那么后面的name属性将会覆盖前面的name属性。

                     ②.Singleton单例Bean

                            测试:

                     uploading.4e448015.gif正在上传…重新上传取消                    

uploading.4e448015.gif正在上传…重新上传取消

结果:

cn.spring.bean.Bean1@d4d66b

cn.spring.bean.Bean1@d4d66b

这两个对象的地址一模一样。单例模式证明完毕。

           ③.原型模式:prototype.

                     uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

结果:

cn.spring.bean.Bean1@c2ee15

cn.spring.bean.Bean1@19cd75a

发现其地址完全不一样。这就是原型(多例)模式。

5.Spring中Bean的生命周期.

    .定义Bean时,可以Init-methoddestroy-method.

    uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

运行结果:

LifeCycle初始化。。。。。

cn.spring.bean.lifecycle.LifeCycle@149105b

发现:运行完程序,销毁方法没有执行。

解析:这个程序运行了,但是Spring容器并不知道何时销毁。

举个例子:例如把一个Spring容器交给tomcat管理时,tomcat停止时,他就会自动调用destroy方法。

那么我们就自己来调用这个方法:app.close();

这时就会调用销毁方法:

LifeCycle初始化。。。。。

cn.spring.bean.lifecycle.LifeCycle@149105b

LifeCycle销毁。。。。。

.Spring生命周期11.

  1. instantiate bean对象实例化
  2. populate(移植) properties(属性) 封装属性
  3. 如果Bean实现BeanNameAware 执行 setBeanName
  4. 如果Bean实现BeanFactoryAware 或者 ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext
  5. 如果存在类实现 BeanPostProcessor(后处理Bean),执行postProcessBeforeInitialization,BeanPostProcessor接口提供钩子函数,用来动态扩展修改Bean。(程序自动调用后处理Bean)

package cn.spring.bean.lifecycle;

import org.springframework.beans.BeansException;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessAfterInitialization(Object bean, String beanName)

           throws BeansException {

       System.out.println("第八步:后处理Bean,after初始化。");

       //后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。

       return bean;//返回bean,表示没有修改,如果使用动态代理,返回代理对象,那么就修改了。

    }

    public Object postProcessBeforeInitialization(Object bean, String beanName)

           throws BeansException {

       System.out.println("第五步:后处理Bean的:before初始化!!");

       //后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。

       return bean;//返回bean本身,表示没有修改。

    }

}

注意:这个前处理Bean和后处理Bean会对所有的Bean进行拦截。

  1. 如果Bean实现InitializingBean 执行 afterPropertiesSet
  2. 调用<bean init-method="init"> 指定初始化方法 init
  3. 如果存在类实现 BeanPostProcessor(处理Bean) ,执行postProcessAfterInitialization
  4. 执行业务处理
  5. 如果Bean实现 DisposableBean 执行 destroy
  6. 调用<bean destroy-method="customerDestroy">指定销毁方法 customerDestroy

代码实现:

package cn.spring.bean.lifecycle;

 

import org.springframework.beans.BeansException;

import org.springframework.beans.factory.BeanNameAware;

import org.springframework.beans.factory.DisposableBean;

import org.springframework.beans.factory.InitializingBean;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

 

public class LifeCycleBean1 implements BeanNameAware,ApplicationContextAware,InitializingBean,DisposableBean{

    private String info;

    public LifeCycleBean1()

    {

       System.out.println("第一步:构造Bean实例!");

    }

    public String getInfo() {

       return info;

    }

    public void setInfo(String info) {

       System.out.println("第二步:依赖注入属性。");

       this.info = info;

    }

    public void setBeanName(String beanId) {

       // TODO Auto-generated method stub

       System.out.println("第三步:实现BeanNameAware接口,会注入Bean id:"+beanId);//实现BeanNameAware接口,通过setBeanName注入beans.xml文件中的id属性,配置文件在下面:参考输出lifeCycleBean1,即输出配置文件中的id属性。

    }

    public void setApplicationContext(ApplicationContext applicationContext)

           throws BeansException {

       // TODO Auto-generated method stub

       System.out.println("第四步:实现ApplicationContextAware接口,会注入ApplicationContext对象:"+applicationContext);

    }

    public void afterPropertiesSet() throws Exception {

       // TODO Auto-generated method stub

       System.out.println("第六步:属性设置完成!"); 

    }

    public void myinit()

    {

       System.out.println("第七步:配置文件配置的初始化方法!init-method='myinit' ");

    }

    //Bean初始化完毕,如果有业务方法,那么就开始执行,以下方法模拟业务方法。

    @Override

    public String toString() {

       System.out.println("第九步:业务处理!!");

       return "LifeCycleBean1 [info=" + info + "]";

    }  

    //destroy方法必须自己调用closed方法后才会执行。

    public void destroy() throws Exception {

       // 这个destroy无需配置,实现这个接口,就会自动的去调用destroy方法。

       System.out.println("第十步:执行销毁");

    }

    public void mydestroy()

    {

       System.out.println("第十一步:执行自定义销毁");

    }

}

配置文件:

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

运行结果:

第五步:后处理Bean的:before初始化!!

第八步:后处理Bean,after初始化。

第五步:后处理Bean的:before初始化!!

第八步:后处理Bean,after初始化。

第五步:后处理Bean的:before初始化!!

第八步:后处理Bean,after初始化。

第五步:后处理Bean的:before初始化!!

第八步:后处理Bean,after初始化。

第五步:后处理Bean的:before初始化!!

LifeCycle初始化。。。。。

第八步:后处理Bean,after初始化。

第一步:构造Bean实例!

第二步:依赖注入属性。

第三步:实现BeanNameAware接口,会注入Bean id:lifeCycleBean1

第四步:实现ApplicationContextAware接口,会注入ApplicationContext对象:org.springframework.context.support.ClassPathXmlApplicationContext@157c2bd: startup date [Fri Nov 15 13:40:32 CST 2013]; root of context hierarchy

第五步:后处理Bean的:before初始化!!

第六步:属性设置完成!

第七步:配置文件配置的初始化方法!init-method='myinit'

第八步:后处理Bean,after初始化。

第九步:业务处理!!

LifeCycleBean1 [info=第二步:配置文件注入!]

分析:

前面前处理Bean和后处理Bean被执行多次,表示:钩子函数会对每个bean进行拦截(前面已经配置了其他的几个Bean,每个Bean都执行2次à前处理Bean后处理bean)。

故而执行多次,反复连续的输出五,八。

后面的destroy方法没有执行:如果交给tomcat管理spring,那么tomcat会自动关闭Spring,那么销毁方法就会执行。

那么在这里,我们只是测试一下方法,并没有tomcat来管理,故而需要自己来管理自己手动关闭Spring。调用close方法。

.Spring依赖注入Bean的属性.

为一个对象注入一个属性的值有三种方式:

  1. 构造函数注入
    uploading.4e448015.gif正在上传…重新上传取消
  2. 使用setter方法注入,在Spring配置文件中,通过<property>设置注入的属性

uploading.4e448015.gif正在上传…重新上传取消

  1. 使用<property>引入引用其他Bean

代码演示如下:

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

  1. 如何把一个prototype类型注入singleton中。

就像上面:在car类配置文件里面加入:scope=”prototype”,这是错误的。

那么下面解决方案如下:

使用lookUp方法注入:

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

  1. 继承,如果多个Bean具有相同的方法和属性,则可以引入父类Bean,配置父子bean关系

uploading.4e448015.gif正在上传…重新上传取消

抽取公共属性,下面需要变化的只需覆盖上面的即可。

这与java类之间没有任何关系,这不是类的继承。

这里至始至终只有一个类:记住:这是Bean之间的继承:只有下面一个类。

uploading.4e448015.gif正在上传…重新上传取消

  1. 依赖,一个类内部运行依赖另一个Bean初始化一些数据

uploading.4e448015.gif正在上传…重新上传取消

ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。至于ApplicationContext.xml这个配置文件部署在哪,如何配置多个xml文件,书上都没怎么详细说明。现在的方法就是查看它的API文档。在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。看看它的API说明

第一段说明ContextLoader可以由 ContextLoaderListener和ContextLoaderServlet生成。如果查看ContextLoaderServlet的API,可以看到它也关联了ContextLoader这个类而且它实现了HttpServlet。这个接口

    第二段,ContextLoader创建的是 XmlWebApplicationContext这样一个类,它实现的接口是WebApplicationContext->ConfigurableWebApplicationContext->ApplicationContext->

BeanFactory这样一来spring中的所有bean都由这个类来创建

    第三段,讲如何部署applicationContext的xml文件,如果在web.xml中不写任何参数配置信息,默认的路径是"/WEB-INF/applicationContext.xml,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml。如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:

view plaincopy to clipboardprint?
<context-param> 
        <param-name>contextConfigLocation</param-name> 
        <param-value> 
            /WEB-INF/classes/applicationContext-*.xml  
        </param-value> 
    </context-param> 
<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/classes/applicationContext-*.xml
        </param-value>
    </context-param>

在<param-value> </param-value>里指定相应的xml文件名,如果有多个xml文件,可以写在一起并一“,”号分隔。上面的applicationContext-*.xml采用通配符,比如这那个目录下有applicationContext-ibatis-base.xml,applicationContext-action.xml,applicationContext-ibatis-dao.xml等文件,都会一同被载入。

由此可见applicationContext.xml的文件位置就可以有两种默认实现:

第一种:直接将之放到/WEB-INF下,之在web.xml中声明一个listener

第二种:将之放到classpath下,但是此时要在web.xml中加入<context-param>,用它来指明你的applicationContext.xml的位置以供web容器来加载。按照Struts2 整合spring的官方给出的档案,写成:

view plaincopy to clipboardprint?
<!-- Context Configuration locations for Spring XML files --> 
<context-param> 
    <param-name>contextConfigLocation</param-name> 
    <param-value>/WEB-INF/applicationContext-*.xml,classpath*:applicationContext-*.xml</param-value> 
</context-param>

 

解释classes含义:
1.存放各种资源配置文件 eg.init.properties log4j.properties struts.xml
2.
存放模板文件 eg.actionerror.ftl
3.
存放class文件 对应的是项目开发时的src目录编译文件
总结:这是一个定位资源的入口

如果你知道开发过程中有这么一句话:惯例大于配置 那么也许你会改变你的想法

对于第二个问题
这个涉及的是libclasses下文件访问优先级的问题: lib>classes
对于性能的影响应该不在这个范畴

classpath classpath* 区别:

classpath:只会到你的class路径中查找找文件;

classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找.

 

6.Spring的注解方式。

  1. 使用Component进行注解。

注意:Component(“hello”)相当于<bean id=”hello” class=””>id

代码如下:

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

  1. 使用Autowired进行注入:Spring默认按照类型进行注入

代码如下:

 

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

7.Spring3.0的注解。

  1. Spring3.0以JavaConfig为核心,提供使用Java类定义Bean信息的方法
    • @Configuration 指定POJO类为Spring提供Bean定义信息
    • uploading.4e448015.gif正在上传…重新上传取消@Bean 提供一个Bean定义信息
       

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  1. Spring提供AnnotationConfigApplicationContext 用于加载使用@Configuration 配置注解工厂类
  2. register方法 用于向 注解上下文对象 添加一个配置类
  3. refresh 刷新容器以应用这些注册的配置类

uploading.4e448015.gif正在上传…重新上传取消

8.传统XML和注解配置混合使用

  1. 1、引入context命名空间

<beans xmlns="http://www.springframework.org/schema/beans"

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

     xmlns:context="http://www.springframework.org/schema/context"

     xsi:schemaLocation="http://www.springframework.org/schema/beans

         http://www.springframework.org/schema/beans/spring-beans.xsd

         http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context.xsd">

  1. 2、在配置文件中添加context:annotation-config标签

       <context:annotation-config/>

使@Resource@ PostConstruct@ PreDestroy@Autowired注解生效
注意:在这里我们没有使用<context:component-scan/>这个扫描配置。那么我们需要手动配置Bean,然后使用Autowired进行自动寻找类型匹配进行注入。

如下例:Example
<context:annotation-config/>

<bean id="productService" class="cn.itcast.self.ProductService"></bean>

    <bean id="productDao" class="cn.itcast.self.ProductDao"></bean>

public class ProductService {

   @Autowired//要想autowired生效,那么productDao必须进行手动进行配置。否则注入不成功。

   private ProductDao productDao; 

   public void findAll()

   {

      productDao.findAll();

   }

}

9.SpringWeb开发中初步应用

.自己在程序中编写加载配置文件交给tomcat管理。
*.编写servlet代码,在doGet中加载配置文件。
public void doGet(HttpServletRequest request, HttpServletResponse response)

         throws ServletException, IOException {

      ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");

      UserService user = (UserService) app.getBean("user");

      user.regist();  }

*.web.xml中配置Servlet.
<servlet>

    <servlet-name>UserServlet</servlet-name>

    <servlet-class>cn.itcast.servlet.UserServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>UserServlet</servlet-name>

    <url-pattern>/servlet</url-pattern>

  </servlet-mapping>

*.配置beans.xml配置文件

★.完全交给tomcat容器进行管理,导入web.jar包,配置监听器实现。

*.导入spring-web-3.2.0.RELEASE.jar包,并配置web.xml.

<listener>  <listener-class>org.springframework.web.context.ContextLoaderListenr</listener-class>

</listener>

//配置全局配置文件位置参数。

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:beans.xml</param-value>

</context-param>

*.spring交给tomcat管理,原理就是把spring容器变成一个对象交给tomcat管理

管理方式如下:

通常在Servlet中完成:

WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

另一种方式:

WebApplicationContext applicationContext = (WebApplicationContext) getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

本例在servlet代码完成如下:

public void doGet(HttpServletRequest request, HttpServletResponse response)

         throws ServletException, IOException {

      WebApplicationContext  web = WebApplicationContextUtils.getWebApplicationContext(getServletContext());//使用web容器对象来获取spring对象。

      //ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");

      UserService user = (UserService) web.getBean("user");

      user.regist(); 

   }

*.beans.xml文件

  <bean id="user" class="cn.itcast.service.UserService"></bean>

AOP面向切面编程

  1. AOP Aspect Oriented Programing 面向切面编程
  2.  AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
  3.  Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
  4.  AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入

.AOP相关术语:

Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.

Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.

Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

Target(目标对象):代理的目标对象

Weaving(织入):是指把增强(advice)应用到目标对象(Target)来创建新的代理对象(Proxy)的过程.advice放入Target创建Proxy的过程叫做织入。 spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入

Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面): 是切入点和通知(引介)的结合

★.AOP代理模式

代理模式:代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

抽象主题角色:声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题

代理主题(Proxy)角色:代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或之后,都要执行某个操作,而不是单纯地将调用传递给真实主题对象。

真实主题角色:定义了代理角色所代表地真实对象

★.静态代理

①.静态代理定义接口

/**

 * 业务主题接口

 * @author seawind

  */

public interface Subject {

// 业务方法

public void request();

}

   ②.创建真实业务对象

   public class RealSubject implements Subject {

   public void regist(){System.out.println("真实业务操作。。。。。。);

   }}

③.创建静态代理对象

public class ProxySubject implements Subject{

   //真实业务对象引用

   private Subject subject;

   public void setRealSubject(Subject subject) {

      this.subject = subject;

   }

   public void regist() {

      //持有真实业务对象,对真实业务进行加强。

      long start = System.currentTimeMillis();

      subject.regist();

      long end = System.currentTimeMillis();  

      System.out.println("运行时间是:"+(end-start));

   } 

}

④.创建代理测试类.

public class ProxyTest {

public static void main(String[] args) {

   //创建真实业务对象

   Subject realSubject = new RealSubject();

   //创建代理对象

   ProxySubject proxySubject = new ProxySubject();

   //代理对象持有真实业务对象的引用

   proxySubject.setRealSubject(realSubject);

   proxySubject.regist(); }

}

★.动态代理

JDK1.3引入动态代理技术

编写动态代理程序

  1. java.lang.reflect.Proxy
  2. java.lang.reflect.InvocationHandler
  • 创建动态代理接口

//业务接口

public interface Fly {

   public void goFly();

}

  • 创建接口实现真实业务类

//真实业务实现类

public class Bird implements Fly {

   public void goFly() {

      System.out.println("鸟飞走了!");}

}

  • 创建动态代理测试类

public class ProxyTest {

   @Test

   public void demo1()

   {

      //JDK自带动态代理

      //第一步:真实业务对象。可以使用真实类对象。

      final Fly fly = new Bird();

      //第二步:使用真实业务对象,类加载器和实现接口,在内存中创建代理对象。必须使用接口

Fly proxy =

   (Fly) Proxy.newProxyInstance(fly.getClass().getClassLoader(), fly.getClass().getInterfaces(), new InvocationHandler() {  

public Object invoke(Object proxy, Method method, Object[] args)

               throws Throwable {

            if(method.getName().equals("goFly"))

            {

                System.out.println("鸟飞不走了!被拦截了...");

                return null;

            }

            //如果不想拦截

            return method.invoke(fly, args);

         }

      });

      //调用代理对象的业务方法。

      proxy.goFly();

   }

}

  • 动态代理原理分析

uploading.4e448015.gif正在上传…重新上传取消

★.使用CGLIB生成代理

  • 对于不使用接口的业务类,无法使用JDK动态代理
  • CGlib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题
  • 创建真实业务类

//创建真实业务类

public class Cat {

public void run()

{

System.out.println("猫捉老鼠......"); 

}

}

  • 创建cglib动态代理

   public void demo2()

   {

      //cglib为Cat生成代理,无接口代理.

      //1.创建真实业务对象

      final Cat cat = new Cat();

      //2.根据真实业务对象生成代理对象。使用核心类enhancer,相当于proxy

      Enhancer enhancer = new Enhancer();

      // 为真实业务对象创建子类

      enhancer.setSuperclass(cat.getClass());

      //设置回调函数,类似invocationhandler.

      enhancer.setCallback(new MethodInterceptor() {

         //相比invocationhandler,多了一个methodProxy方法代理对象

         public Object intercept(Object proxy[h1] , Method metod[h2] , Object[] args[h3] ,

                MethodProxy methodProxy[h4] ) throws Throwable {

                //需要拦截run方法

            if(metod.getName().equals("run"))

            {

                System.out.println("猫被拦截了。。。。。。");

                Object result = metod.invoke(cat, args);

                System.out.println("真实方法操作后拦截....."+result);

                return result;

            }

            //不需要拦截

            return metod.invoke(cat, args);

         }

      });

      //生成代理对象

      Cat proxy = (Cat) enhancer.create();

      proxy.run(); }

  1. Spring只支持方法连接点,不提供属性连接

★.Spring AOP增强类型。

  1. AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
  2. Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
    • 前置通知 org.springframework.aop.MethodBeforeAdvice
      • 在目标方法执行前实施增强
    • 后置通知 org.springframework.aop.AfterReturningAdvice
      • 在目标方法执行后实施增强
    • 环绕通知 org.aopalliance.intercept.MethodInterceptor
      • 在目标方法执行前后实施增强
    • 异常抛出通知 org.springframework.aop.ThrowsAdvice
      • 在方法抛出异常后实施增强
    • 引介通知 org.springframework.aop.IntroductionInterceptor
      • 在目标类中添加一些新的方法和属性
  3. Spring AOP切面类型.

*Advisor : 代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截

*PointcutAdvisor : 代表具有切点的切面,可以指定拦截目标类哪些方法

*IntroductionAdvisor : 代表引介切面,针对引介通知而使用切面(不要求掌握)

  • 案例一:配置MethodBeforeAdvice:对目标类的所有方法进行拦截。
  • 导入jar

进行AOP开发,需要引入另外2个jar包:

spring-aop-3.2.0.RELEASE.jar

com.springsource.org.aopalliance-1.0.0.jar (来自AOP联盟)

寻找位置如下:

uploading.4e448015.gif正在上传…重新上传取消

uploading.4e448015.gif正在上传…重新上传取消

.创建接口

public interface IMethodBeforeAdvice {

   public void sayHello();

   public void search();

   public void find();

   public void save();

}

.创建实现接口的真实业务类

public class HelloService implements IMethodBeforeAdvice {

   public void sayHello(){

      System.out.println("执行sayHello方法.....");

   }

   public void search(){

      System.out.println("执行search方法.......");

   }

   public void find(){

      System.out.println("执行find方法........");

   }

   public void save(){

      System.out.println("执行save方法.......");

   }}

④.创建前置增强代理类

public class MethodHelloBeforeAdvice implements MethodBeforeAdvice{

   public void before(Method method, Object[] args, Object target)

         throws Throwable {

      System.out.println("目标方法被执行:"+method.getName());

   }}

⑤.创建配置文件

<!-- 配置真实业务bean -->

    <bean id="hello" class="cn.itcast.methodbefore.HelloService"></bean>

    <!-- 配置通知,增强代码逻辑  -->

    <bean id="advice" class="cn.itcast.methodbefore.MethodHelloBeforeAdvice"></bean>

    <!--  织入,使用ProxyFactoryBean为真实业务生成代理-->

    <bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

    <!-- 代理目标接口 -->

    <property name="proxyInterfaces" value="cn.itcast.methodbefore.IMethodBeforeAdvice"></property>

    <!-- 代理业务对象 -->

    <property name="target" ref="hello"></property>

    <!-- 通知增强 -->

    <property name="interceptorNames" value="advice"></property>  

    </bean>

⑥.创建测试类

ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

   IMethodBeforeAdvice hello = (IMethodBeforeAdvice) app.getBean("helloProxy");

   hello.save();

   hello.sayHello();

   hello.find();

   hello.search();

⑦.运行结果

目标方法被执行:save

执行save方法.......

目标方法被执行:sayHello

执行sayHello方法.....

目标方法被执行:find

执行find方法........

目标方法被执行:search

执行search方法.......

  1. ProxyFactoryBean常用可配置属性
    • target : 代理的目标对象
    • proxyInterfaces : 代理要实现的接口
      • 如果多个接口可以使用以下格式赋值

<list>

    <value></value>

    ....

</list>

    • proxyTargetClass : 是否对类代理而不是接口,设置为true时,使用CGLib代理
    • interceptorNames : 需要植入目标的Advice
    • singleton : 返回代理是否为单实例,默认为单例
    • optimize : 当设置为true时,强制使用CGLib

案例二:配置引介增强

①.编写真实业务类

public class OrderService {

   public void exportOrder(){

      System.out.println("导出订单...");

   } 

   public void deleteAllOrders(){

      System.out.println("删除所有订单...");

   }

}

②.编写性能监控接口,将接口方法,增强到代理对象上

public interface TimeMonitorable {

   // 设置是否进行性能监控

   public void setMonitorActive(boolean isActive);

}

③.编写引介增强类

public class MyIntroductionInterceptor extends DelegatingIntroductionInterceptor implements TimeMonitorable {

   private boolean isActive;

   @Override

   public Object invoke(MethodInvocation mi) throws Throwable {

      long begin = 0;

      long end = 0;

      if(isActive){

         // 进行性能监控

         System.out.println("开始监控....");

         begin = System.currentTimeMillis();

      }

      Object result =  super.invoke(mi); // 执行原方法逻辑

      if(isActive){

         end = System.currentTimeMillis();

         System.out.println("性能监控,运行时间:" +(end - begin) );

      }

      return result ;

   } 

   // 新的方法行为

   public void setMonitorActive(boolean isActive) {

      this.isActive = isActive;

   }}

④.编写配置文件

<!-- 案例二 -->

    <!-- 真实业务对象 -->

    <bean id="orderService" class="cn.itcast.methodbefore.OrderService"></bean>

 

    <!-- 配置引介通知 -->

    <bean id="myIntroductionInterceptor" class="cn.itcast.methodbefore.MyIntroductionInterceptor"></bean>

    <!-- 织入  -->

    <bean id="orderServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

       <property name="proxyInterfaces" value="cn.itcast.methodbefore.TimeMonitorable"></property>

       <property name="target" ref="orderService"></property>

       <property name="interceptorNames" value="myIntroductionInterceptor"></property>

       <!—true表示使用 CGLib -->

       <property name="proxyTargetClass" value="true"></property>

    </bean>

⑤.编写测试类

// 引介增强

   ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

   OrderService orderService = (OrderService) applicationContext.getBean("orderServiceProxy");  

   // 激活性能监控

   TimeMonitorable monitorable = (TimeMonitorable) orderService;

   monitorable.setMonitorActive(true);  

   orderService.exportOrder();  orderService.deleteAllOrders();

⑥.运行结果

性能监控,运行时间:1396322008225

开始监控....

导出订单...

性能监控,运行时间:16

开始监控....

删除所有订单...

性能监控,运行时间:0

 

 

  • Spring的事物管理

使用注解来管理事务(最简单的事物管理方式)

配置文件只需要配置2句就ok,然后使用注解@transactional就ok。

<!-- 配置事务管理器 -->

    <bean id="transactionManager"

       class="org.springframework.orm.hibernate3.HibernateTransactionManager">

       <property name="sessionFactory" ref="sessionFactory"></property>

    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />

事务管理复习:

三种数据源配置(DriverManagerDataSource 、DBCP、C3P0 ),使用properties属性文件

增删改查操作:

增加、修改、删除 : update(sql, args...)

查询:简单查询和对象查询 

简单查询 queryForInt queryForLong queryForObject  (不需要写结果集封装函数 )

对象查询 queryForObject 查询一个对象 query 查询一个集合 

                  queryForObject(sql,RowMapper,args...) 返回Object  query(sql,RowMapper,args... ) 返回List

                   List<User> users = jdbcTemplate.query("select * from user", new RowMapper<User>(){

                            @Override

                            public User mapRow(ResultSet rs, int rowNum) throws SQLException {

                                     User user = new User();

                                     user.setId(rs.getInt("id"));

                                     user.setName(rs.getString("name"));

                                     return user;

                            }

                   });

  • Spring事务管理机制 三个核心组件

1) PlatformTransactionManager  平台事务管理器 

commit 提交事务、rollback 回滚事务 、

TransactionStatus getTransaction(TransactionDefinition definition)

2) TransactionDefinition  事务定义信息(和事务管理相关一些参数)

 隔离级别、传播级别、超时 、事务是否只读  (静态信息)

3) TransactionStatus 事务状态  (事务运行过程中 一些动态信息 )

关系:

事务管理参数信息由 TransactionDefinition 加载, Spring事务管理由PlatformTransactionManager  完成, PlatformTransactionManager 管理事务过程中根据 TransactionDefinition 定义事务管理信息来管理事务  ,在事务运行过程中不同时刻 事务状态信息不同, TransactionStatus 就表示不同时刻事务状态信息

第一个组件 事务管理器 PlatformTransactionManager  接口

org.springframework.jdbc.datasource.DataSourceTransactionManager ------- 针对jdbc开发或者 iBatis开发 进行事务管理

org.springframework.orm.hibernate3.HibernateTransactionManager  -------  针对Hibernate3开发 进行事务管理

第二个组件 事务定义信息 TransactionDefinition

  • 隔离级别:

1) read_uncommitted 读未提交   引发所有事务隔离问题(脏读、不可重复读、虚读)

2) read_committed 读已提交  不会发生脏读,会导致不可重复读和虚读

3) repeatable_read 重复读  不会发生 脏读和不可重复读,会导致虚读

4) serializable  序列化 采用串行方式管理事务,同一时间,只能有一个事务在操作 ,阻止所有隔离问题发生

DEFAULT 采取数据库默认隔离级别  Oracle read_committed、 Mysql repeatable_read

  • 事务传播行为:(七种)

1) PROPAGATION_REQUIRED : 支持当前事务,如果不存在 就新建一个  , 将 deleteUser 和 removeAllOrders 放入同一个事务  (Spring默认提供事务传播级别)

2) PROPAGATION_SUPPORTS : 支持当前事务,如果不存在,就不使用事务 

3) PROPAGATION_MANDATORY : 支持当前事务,如果不存在,抛出异常

4) PROPAGATION_REQUIRES_NEW : 如果有事务存在,挂起当前事务,创建一个新的事务  , deleteUser和removeAllOrder 两个事务执行,无任何关系

5) PROPAGATION_NOT_SUPPORTED : 以非事务方式运行,如果有事务存在,挂起当前事务

6) PROPAGATION_NEVER  :  以非事务方式运行,如果有事务存在,抛出异常

7) PROPAGATION_NESTED : 如果当前事务存在,则嵌套事务执行

  • 转账案例(编程式事务管理):
  • 导入相关jar包

uploading.4e448015.gif正在上传…重新上传取消

  • 编写配置文件

准备配置文件:

Web.xml(略)

Struts.xml(略)

Log4j.properties(略)

Jdbc.properties(略)

Beans.xml:

<context:property-placeholder location="classpath:jdbc.properties"/>

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

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

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

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

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

       </bean>      

       <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

       <property name="dataSource[h5] " ref="dataSource"></property>

       </bean>      

       <bean id="accountDao" class="cn.itcast.dao.impl.AccountDaoImpl">

       <property name="jdbcTemplate[h6] " ref="jdbcTemplate"></property>

       </bean>      

       <bean id="accountService" class="cn.itcast.service.impl.AccountServiceImpl">

       <property name="accountDao[h7] " ref="accountDao"></property>

       </bean>

  • 创建数据表

CREATE TABLE `account` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `name` varchar(20) NOT NULL,

  `money` double DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `account` VALUES ('1', 'aaa', '1000');

INSERT INTO `account` VALUES ('2', 'bbb', '1000');

INSERT INTO `account` VALUES ('3', 'ccc', '1000');

④.创建三层代码

uploading.4e448015.gif正在上传…重新上传取消

Action层:

Service层:

public class AccountServiceImpl implements IAccountService {

   private IAccountDao accountDao;

   private TransactionTemplate transactionTemplate[h8] ;

   @Override

   public void transfer(String inAccount, String outAccount, Double money) {

      // TODO Auto-generated method stub

      accountDao.out(outAccount, money);

      accountDao.in(inAccount, money);

 

   } 

   public void setAccountDao(IAccountDao accountDao) {

      this.accountDao = accountDao;

   } 

   public void setTransactionTemplate(TransactionTemplate transactionTemplate) {

      this.transactionTemplate = transactionTemplate;

   }  }

Dao层:

public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{

   @Override

   public void in(String inAccount,Double money) {

      // TODO Auto-generated method stub

      this.getJdbcTemplate().update("update account set money = money+? where name = ?", money,inAccount);

   }

   @Override

   public void out(String outAccount,Double money) {

      // TODO Auto-generated method stub

      //判断账户余额是否充足

      Account acc = this.getJdbcTemplate().queryForObject[h9] ("select * from account where name = ?", new RowMapper<Account>(){

         @Override

         public Account mapRow(ResultSet rs, int rowNum) throws SQLException {

            // TODO Auto-generated method stub

            Account account = new Account();

            account.setId(rs.getInt("id"));

            account.setName(rs.getString("name"));

            account.setMoney(rs.getDouble("money"));

            return account;

         }},outAccount);   

      if(acc.getMoney()<money)//转账金额大于实际金额

      {

         throw new RuntimeException("余额不足无法转账");

      }

      //否则就转账

      this.getJdbcTemplate()

      .update("update account set money=money-? where name=?",

            money ,outAccount);

   }

}

编程式事务配置文件添加:

<!-- 编程式事务管理:先配置一个事务管理器 ,事务管理器是管理事务的核心,jdbc模板只是来操作事务管理器的-->

       <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

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

       </bean>      

       <!-- 将事务管理器注入给事务模板 -->

       <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">

       <property name="transactionManager" ref="transactionManager"></property>

       </bean>

       <!-- 将事务模板注入给service -->

       <bean id="accountService" class="cn.itcast.service.impl.AccountServiceImpl">

       <property name="transactionTemplate" ref="transactionTemplate"></property>

       <property name="accountDao" ref="accountDao"></property>

       </bean>

⑤.转账测试:

@Test

   public void test01()

   {

      ApplicationContext application = new ClassPathXmlApplicationContext("beans.xml");

      IAccountService accountService = (IAccountService) application.getBean("accountService");

      accountService.transfer("aaa", "bbb", 200d);

   }

测试结果:

uploading.4e448015.gif正在上传…重新上传取消转账成功。

⑥.转账失败(手动编程事务管理)

//编程式管理事务

public void transfer(final String inAccount, final String outAccount, final Double money) {

      transactionTemplate[h10] .execute(new TransactionCallbackWithoutResult() { 

         @Override

         protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {           

            // 匿名内部类

            accountDao.out(outAccount, money);

            int i = 1/0;

            accountDao.in(inAccount, money);

            System.out.println("转账成功");          

         }

      });

  • 声明式事务管理

TransactionProxyFactoryBean  事务代理工厂Bean为目标类生成代理,进行事务管理

transactionAttributes 事务属性配置格式

prop格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception

1) PROPAGATION 传播行为

2) ISOLATION 隔离级别

3) readOnly : 只读事务,不能进行修改操作

4) -Exception : 发生这些异常回滚事务

5) +Exception : 发生这些异常仍然提交事务

<prop key="*">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop>  发生java.lang.ArithmeticException 事务也会提交

<prop key="*">PROPAGATION_REQUIRED,readOnly</prop> 事务是只读,不能进行修改操作

  • 使用代理进行事务进行管理(原生模式:直接使用代理)

uploading.4e448015.gif正在上传…重新上传取消Application.getBean(“accountServiceProxy[h11] ”);

uploading.4e448015.gif正在上传…重新上传取消

 

 

 

 

 

 

 

 

 

  • 使用配置文件自动生成代理

Application.getBean(“accountService[h12] ”);

uploading.4e448015.gif正在上传…重新上传取消

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 使用注解来管理事务

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

       <property name="sessionFactory" ref="sessionFactory"></property>

       </bean>

       <tx:annotation-driven transaction-manager="transactionManager"/>

  • 测试事物管理(使用mysql数据库account账户):

uploading.4e448015.gif正在上传…重新上传取消

分析总结:

Mysql数据库中:默认每一条数据都是一个事务,几个语句在一个集合的大事务中,事务回滚回滚到事务的起始点。

事务一旦commit;那么就不能回滚了,这时候回滚已经没有意义,因为现在已经没有事务,事务已经提交完毕。

  • 保存点(Spring中嵌套事务概念:使用保存点)

Savepoint sp = conn.setSavepoint();

设置保存点:一个事务可以设置多个保存点。

事务回滚到保存点:conn.rollback(sp[h13] );

uploading.4e448015.gif正在上传…重新上传取消

  • 事务的隔离级别

概念:所谓事务的隔离级别就是:事务和事务并发时,相互之间要隔离开来,以免相互之间互相影响。

脏读:读到未提交事务

举例:张三开了一事务,给李四打钱,还未提交

         李四开了一事务,查看到未提交的数据,钱到了,其实还未到,读到未提交数据。

不可重复读:这个有时候允许,有时候不允许。(这个体现在查询一条记录的时候)

           李四开了一事务读取到了张三提交的事物,这和前一次读取的数据不一致,这时候可能使用户不知道以哪一个为准。这个可以根据业务需求,看是否允许出现不可重复读。

幻读(虚读):读到另一个事务的插入。

四大隔离级别:

SERIALIAZABLE(串行化):一个事务操作某数据,另一个事务也要操作该数据,那么需等待前一个事务结束。没有任何并发问题,因为没有并发。

          *没有任何并发

          *效率最低,不可能使用这一隔离级别

          *最容易出现死锁。

REPEATABLE READ(可重复读):不可能出现脏读,不可重复读,但可能出现幻读。

READ COMMITTED(读已提交):不可能出现脏读,但可能出现不可重复读,以及幻读。Oracle默认隔离级别。

READ UNCOMMITIED(读未提交):可能出现所有读问题

          *没有安全性可言,不可能使用这一隔离级别

          *效率无敌。

Mysql查看和设置事务隔离级别

查看:select @@tx_isolation

设置:set transaction isolation level [四个事务任选一]

uploading.4e448015.gif正在上传…重新上传取消

丢失更新:

举例:

张三开启事务,读取这条数据并修改

李四同时读取这个数据,并开启事务,当张三提交,李四修改数据,覆盖了张三修改的数据值。这就是丢失更新。

  • 数据源(深入分析):

数据源在JDBC中的应用简介众所周知,JDBC(Java数据库连接)是Java 2企业版的重要组成部分。
它是基于SQL层的API。通过把SQL语句嵌入JDBC接口的方法中,用户可以通过Java程序执行几乎所有的数据库操作。
JDBC只提供了接口,具体的类的实现要求数据库的设计者完成。
通过生成这些接口的实例,即使对于不同的数据库,Java程序也可以正确地执行SQL调用。

所以对于程序员来说,不必把注意力放在如何向数据库发送SQL指令,因为程序员需要了解和用到的只是JDBC的接口,只有在极少数情况下会用到面向特定数据库的类,例如程序员希望使用ORACLE的扩展API。
在JDBC程序中,首先需要做的是实现与数据库的连接。
在示例程序中,我们使用的是ORACLE8i的JDBC包。连接数据库通常需要实现以下几个步骤:
1. 注册数据库驱动程序(driver)。可以通过调用java.sql.DriverManager类的registerDriver方法显式注册驱动程序,也可以通过加载数据库驱动程序类隐式注册驱动程序。
2. 建立连接。调用java.sql.DriverManager类的getConnection()方法可以建立与数据库的连接。
从实际应用的角度出发,我们可以看出采取这种方式连接到数据库存在几个问题。
第一是安全性问题,由于程序代码中包含用户名和密码,其他人如果能得到bytecode,可以通过反编译工具获得用户名和密码。第二是代码的可移植性问题。如果希望连接的数据库名称或用户名有所更改,程序员需要修改源程序,然后把修改过的程序发送给用户。也就是说,软件无法脱离数据库独立存在。这样不仅会大大提高软件的成本,也不利于软件本身的发展。还可能出现这样的情况:在某些情况下,提供数据的机构不希望数据库的用户名和密码让编写程序的程序员知道知道。
这样就提出了一个问题,如何使Java和数据库之间建立连接时隐藏一些敏感的信息。

数据源(Data Source)及JNDI数据源是在JDBC 2.0中引入的一个概念。
在JDBC 2.0扩展包中定义了javax.sql.DataSource接口来描述这个概念。
如果用户希望建立一个数据库连接,通过查询在JNDI服务中的数据源,可以从数据源中获取相应的数据库连接。
这样用户就只需要提供一个逻辑名称(Logic Name),而不是数据库登录的具体细节。
在这里有必要简单介绍一下JNDI。JNDI的全称是Java Naming and Directory Interface, 可以理解为Java名称和目录服务接口。JNDI向应用程序提供了一个查询和使用远程服务的机制。这些服务可以是任何企业服务。对于JDBC应用程序来说,JNDI提供的是数据库连接服务。当然JNDI也可以向数据库提供其他服务,但是这超出了本文范围,在此不做论述。其实JNDI并不难理解。简单来说,名称服务提供了一个把文件,打印机,服务器等实体映射到一个逻辑名称的机制。例如在操作系统中的名称服务就把打印机映射到一个I/O端口。而目录服务可以理解为名称服务的一个扩展,它允许在服务中的各项拥有自己的属性。又以打印机为例,打印机可以是彩色打印机,支持双面打印,支持网络打印,支持高速打印等。所有这些打印机的属性都可以储存在目录服务中,和相应的打印机联系起来。一些常见的目录服务有NIS,NIS+,LDAP和Novell的NDS等。JNDI使应用程序通过使用逻辑名称获取对象和对象提供的服务,从而使程序员可以避免使用与提供对象的机构有关联的代码。例如在下面的例子中使用了在JNDI中的数据源,程序员就不需要提供Oracle8i驱动程序的名称,这样代码的移植能力就更强。
下面详细介绍一下数据源和javax.sql.DataSource接口。
在数据源中存储了所有建立数据库连接的信息。就象通过指定文件名你可以在文件系统中找到文件一样,通过提供正确的数据源名称,你可以找到相应的数据库连接。javax.sql.DataSource接口定义了如何实现数据源。
在该接口中定义了九个属性。
 dataSourceName String 数据源接口实现类的名称。
 description String 对数据源的描述。
 networkProtocol String 和服务器通讯使用的网络协议名。
 password String 用户登录密码。
 portNumber Int 数据库服务器使用的端口,缺省值为1521。 
 serverName String 数据库服务器名称。
 user String 用户登录名。 

在javax.sql.DataSource接口中定义了很多方法通过这些方法,程序员可以获得建立连接需要的所有信息。
需要注意的是,程序员不可以获取登陆密码,这就在一定程度上保证了安全性。需要注意的另一点是所有的方法都是synchronized方法,这是为了保证应用程序的线程安全(Thread-safe)。
如果在调用该方法时,即使数据源实例发生变化不会影响程序的正确运行。
然后通过该数据源对象进行数据库操作。在这个例子中,程序和名称服务环境都是在同一台计算机上运行。
在实际的应用中,程序可以通过RMI或CORBA向名称服务环境注册或查询对象。例如在一个服务器-客户机结构中,客户机上的应用程序只需要知道数据源对象在服务器名称服务环境中的逻辑名称,就可以通过RMI向服务器查询数据源,然后通过建立与数据库的连接.这样就可以解决本文最开始提出的问题。

事务复习(总结)

一、什么是事务

  事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取。事务的正确执行使得数据库从一种状态转换成另一种状态。 

  事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)的缩写事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)的缩写。

  •  原子性。即不可分割性,事务要么全部被执行,要么就全部不被执行。如果事务的所有子事务全部提交成功,则所有的数据库操作被提交,数据库状态发生转换;如果有子事务失败,则其他子事务的数据库操作被回滚,即数据库回到事务执行前的状态,不会发生状态转换。
  •  一致性或可串性。事务的执行使得数据库从一种正确状态转换成另一种正确状态。
  • 隔离性。在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务,即在事务正确提交之前,它可能的结果不应显示给任何其他事务。
  • 持久性。事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。   

  运行嵌入式SQL应用程序或脚本,在可执行SQL语句第一次执行时(在建立与数据库的连接之后或在现有事务终止之后),事务就会自动启动。在启动事务之后,必须由启动事务的用户或应用程序显式地终止它,除非使用了称为自动提交(automatic commit)的过程(在这种情况下,发出的每个单独的SQL语句被看做单个事务,它一执行就被隐式地提交了)。

  在大多数情况下,通过执行COMMIT或ROLLBACK语句来终止事务。当执行COMMIT语句时,自从事务启动以来对数据库所做的一切更改就成为永久性的了-- 即它们被写到磁盘。当执行ROLLBACK语句时,自从事务启动以来对数据库所做的一切更改都被撤销,并且数据库返回到事务开始之前所处的状态。不管是哪种情况,数据库在事务完成时都保证能回到一致状态。

  一定要注意一点:虽然事务通过确保对数据的更改仅在事务被成功提交之后才成为永久性的,从而提供了一般的数据库一致性,但还是须要用户或应用程序来确保每个事务中执行的SQL操作序列始终会导致一致的数据库。

二、数据库系统支持两种事务模式:

  • 自动提交模式:每个SQL语句都是一个独立的事务,当数据库系统执行完一个SQL语句后,会自动提交事务。
  • 手动提交模式:必须由数据库客户程序显示指定事务开始边界和结束边界。

  注:MySQL中数据库表分为3种类型:INNODB、BDB和MyISAM,其中MyISAM不支持数据库事务。MySQL中create table 语句默认为MyISAM类型。  

三、对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题,这些并发问题可归纳为以下几类:

  • 第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。 
  • 脏读:一个事务读到另一个事务为提交的更新数据。
  • 虚读:一个事务读到另一个事务已提交的新插入的数据。
  • 不可重复读:一个事务读到另一个事务已提交的更新数据。
  • 第二类丢失更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据。 

 

四、隔离级别

  当数据库系统采用read Commited隔离级别时,会导致不可重复读喝第二类丢失更新的并发问题,可以在应用程序中采用悲观锁或乐观锁来避免这类问题。从应用程序的角度,锁可以分为以下几类:

  • Serializable(串行化):一个事务在执行过程中完全看不到其他事务对数据库所做的更新。
  • Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。
  • Read Commited(读已提交数据):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新
  • Read Uncomitted(读未提交数据):一个事务在执行过程中可以拷打其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。

    隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以有优先考虑把数据库系统的隔离级别设为Read Commited,它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

  当数据库系统采用read Commited隔离级别时,会导致不可重复读喝第二类丢失更新的并发问题,可以在应用程序中采用悲观锁或乐观锁来避免这类问题。从应用程序的角度,锁可以分为以下几类:

A.悲观锁:指在应用程序中显示的为数据资源加锁。尽管能防止丢失更新和不可重复读这类并发问题,但是它会影响并发性能,因此应该谨慎地使用。 

  B.乐观锁:乐观锁假定当前事务操作数据资源时,不回有其他事务同时访问该数据资源,因此完全依靠数据库的隔离级别来自动管理锁的工作。应用程序采用版本控制手段来避免可能出现的并发问题。

 

五、悲观锁有两种实现方式。

  A.在应用程序中显示指定采用数据库系统的独占所来锁定数据资源。SQL语句:select ... for update,在Hibernate中使用get,load时如session.get(Account.class,new Long(1),LockMode,UPGRADE) 

  B.在数据库表中增加一个表明记录状态的LOCK字段,当它取值为“Y”时,表示该记录已经被某个事务锁定,如果为“N”,表明该记录处于空闲状态,事务可以访问它。增加锁标记字段就可以实现。

  利用Hibernate的版本控制来实现乐观锁

  乐观锁是由程序提供的一种机制,这种机制既能保证多个事务并发访问数据,又能防止第二类丢失更新问题。

  在应用程序中可以利用Hibernate提供的版本控制功能来视线乐观锁,OR映射文件中的<version>元素和<timestamp>都具有版本控制的功能,一般推荐采用<version>

Spring容器加载bean文件源码分析:

①.启动WEB容器(加载监听器ContextLoaderListenner)

public void contextInitialized(ServletContextEvent event) {

      this.contextLoader = createContextLoader()[h14] ;

      if (this.contextLoader == null) {

         this.contextLoader = this[h15] ;

      }     this.contextLoader.initWebApplicationContext(event.getServletContext());

   }

②.调用initWebApplicationContext(ServletContext)
   public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

      if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {

         throw new IllegalStateException(

                "Cannot initialize context because there is already a root application context present - " +

                "check whether you have multiple ContextLoader* definitions in your web.xml!");

      }

      Log logger = LogFactory.getLog(ContextLoader.class);

      servletContext.log("Initializing Spring root WebApplicationContext");

      if (logger.isInfoEnabled()) {

         logger.info("Root WebApplicationContext: initialization started");

      }

      long startTime = System.currentTimeMillis();

      try {

         // Store context in local instance variable, to guarantee that

         // it is available on ServletContext shutdown.

         if (this.context == null) {

            this.context [h16] = createWebApplicationContext(servletContext);

         }

         if (this.context instanceof ConfigurableWebApplicationContext) {         configureAndRefreshWebApplicationContext[h17] ((ConfigurableWebApplicationContext)this.context, servletContext);

         }     servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

         ClassLoader ccl[h18]  = Thread.currentThread().getContextClassLoader();

         if (ccl == ContextLoader.class.getClassLoader()) {

            currentContext = this.context;

         }

         else if (ccl != null) {

            currentContextPerThread.put(ccl, this.context);

         }

         if (logger.isDebugEnabled()) {

            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +                  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");

         }

         if (logger.isInfoEnabled()) {

            long elapsedTime = System.currentTimeMillis() - startTime;

            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");

         }

         return this.context[h19] ;

      }

      catch (RuntimeException ex) {

         logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);

         throw ex;

      }

      catch (Error err) {

         logger.error("Context initialization failed", err);   servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);

         throw err;

      }

   }

 

Struts2+Spring+Hibernate三大框架整合

uploading.4e448015.gif正在上传…重新上传取消

①.导入必要的jar包

Struts2:

  1. apps\struts2-blank.war 存放struts2最基本的jar包
  1. struts2-convention-plugin-2.3.7.jar用于struts使用注解(不使用注解可以先不导入)
  2. struts2-json-plugin-2.3.7.jar 用于struts2整合Ajax(这个很少使用)
  3. struts2-spring-plugin-2.3.7.jar 用于struts2整合Spring(这个比较常用,但是我们现在使用无插件整合,先暂时不使用插件整合)

 

 

②.配置web.xml文件

<filter>

      <filter-name>struts2</filter-name>

      <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>

  </filter>

  <filter-mapping>

      <filter-name>struts2</filter-name>

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

  </filter-mapping>

③. 在src下创建struts.xml 配置文件

<struts>

       <!-- 自动加载配置文件,报错信息非常详细 -->

    <constant name="struts.devMode" value="true" />     

       <package name="basic" extends="struts-default" namespace="/">

              <action name="addProduct" class="cn.itcast.web.action.ProductAction" method="add">

                     <result>/success.jsp</result>

              </action>

       </package>

</struts>

  1. Spring3.2 开发最基本jar包
    • spring-beans-3.2.0.RELEASE.jar
    • spring-context-3.2.0.RELEASE.jar
    • spring-core-3.2.0.RELEASE.jar
    • spring-expression-3.2.0.RELEASE.jar
    • com.springsource.org.apache.commons.logging-1.1.1.jar
    • com.springsource.org.apache.log4j-1.2.15.jar
  2. AOP开发
    • spring-aop-3.2.0.RELEASE.jar
    • spring-aspects-3.2.0.RELEASE.jar
    • com.springsource.org.aopalliance-1.0.0.jar
    • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

web应用中使用spring :  spring-web.jar

测试spring和junit整合: spring-test.jar

JdbcTemplate : spring-jdbc.jar spring-tx.jar 

spring整合其他ORM框架: spring-orm.jar  (用于整合Hibernate )

配置 log4j.properties 、web.xml 配置核心监听器 、 src或WEB-INF下创建applicationContext.xml

  1. Spring Jdbc开发
    • spring-jdbc-3.2.0.RELEASE.jar
    • spring-tx-3.2.0.RELEASE.jar
  2. Spring整合其他ORM框架
    • spring-orm-3.2.0.RELEASE.jar
  3. Spring在web中使用
    • uploading.4e448015.gif正在上传…重新上传取消spring-web-3.2.0.RELEASE.jar
  4. Spring整合Junit测试
    • spring-test-3.2.0.RELEASE.jar

Hibernate:jar文件分析:

  1. 解压根目录 hibernate3.jar
  2. 解压根目录\lib\required\*.jar
  1. 解压根目录\lib\jpa\hibernate-jpa-2.0-api-1.0.1.Final.jar
  2. uploading.4e448015.gif正在上传…重新上传取消数据库连接池c3p0
    • 解压根目录\lib\optional\c3p0\c3p0-0.9.1.jar
  3. 整合log4j :slf4j-log4j12-1.7.2.jar 、log4j-1.2.16.jar
  4. 使用二级缓存 Ehcache
  1. 数据库驱动 mysql-connector-java-5.0.8-bin.jar
  1. 在src或WEB-INF下创建applicationContext.xml

<bean id="productDAO" class="cn.itcast.dao.ProductDAO"></bean>

<!-- 配置Service,将DAO注入 -->

<bean id="productService" class="cn.itcast.service.ProductService">

        <property name="productDAO" ref="productDAO"></property>

</bean>

  1. 配置web.xml

<context-param>

      <param-name>contextConfigLocation</param-name>

      <param-value>classpath:applicationContext.xml</param-value>

  </context-param>     

  <listener>

      <listener-class>

       org.springframework.web.context.ContextLoaderListener

       </listener-class>

  </listener>

  1. 在Servlet/Filter中获得Spring上下文对象

WebApplicationContext context= WebApplicationContextUtils.getWebApplicationContext(servletContext

④.在src下面创建hibernate.cfg.xml

<hibernate-configuration>

       <session-factory>

              <!-- jdbc四个基本连接参数,方言 -->

              <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

              <property name="hibernate.connection.url">jdbc:mysql:///spring3day3</property>

              <property name="hibernate.connection.username">root</property>

              <property name="hibernate.connection.password">abc</property>

              <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>

              <!-- 自动建表 -->

              <property name="hibernate.hbm2ddl.auto">update</property>        

              <!-- 使用c3p0连接池 -->

              <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>      

              <!-- 控制台打印SQL -->

              <property name="hibernate.show_sql">true</property>

              <property name="hibernate.format_sql">true</property>

              <!-- 配置Session与当前线程绑定 -->

              <property name="hibernate.current_session_context_class">thread</property>       

              <!-- 映射hbm -->

              <mapping resource="cn/itcast/domain/Product.hbm.xml"/>

       </session-factory>

</hibernate-configuration>

⑤.创建三层代码

Action:

public class ProductAction extends ActionSupport{    

       public String add(){

              System.out.println("添加商品...");      

              Product product = new Product();

              product.setName("冰箱");

              product.setPrice(3000d);      

              // 获得Spring上下文,手动整合三大框架

              WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(ServletActionContext.getServletContext());

              ProductService productService = (ProductService) applicationContext.getBean("productService");

              productService.addProduct(product);          

              return SUCCESS;

       }

}

Service:

public class ProductService {

       private ProductDAO productDAO;//对dao属性在xml里面配置注入

       public void setProductDAO(ProductDAO productDAO) {

              this.productDAO = productDAO;

       }    

       // 添加商品

       public void addProduct(Product product){

              productDAO.save(product);

       }

}

DAO:

// 添加商品,使用工具类获得Session

       public void save(Product product) {

              Session session = HibernateUtils.getCurrentSession();

              Transaction transaction = session.beginTransaction();

              session.save(product);          

              transaction.commit(); // 不需要closeSession

       }

Utils:hibernate还是使用session来进行保存数据。

public class HibernateUtils {

       private static Configuration configuration ;

       private static SessionFactory sessionFactory;

      

       static{

              // 加载hibernate.cfg.xml

              configuration = new Configuration().configure();

              // 创建连接池

              sessionFactory = configuration.buildSessionFactory();

       }    

       // 从连接池随机获取一个Session

       public static Session openSession(){

              return sessionFactory.openSession();

       }

      

       // 返回与当前线程绑定的Session ,需要配置hibernate.cfg.xml

       public static Session getCurrentSession(){

              return sessionFactory.getCurrentSession();

       }

}

JavaBean类:

public class Product {

       private Integer id;

       private String name;

       private Double price;    

       public Integer getId() {

              return id;

       }

       public void setId(Integer id) {

              this.id = id;

       }

       public String getName() {

              return name;

       }

       public void setName(String name) {

              this.name = name;

       }

       public Double getPrice() {

              return price;

       }

       public void setPrice(Double price) {

              this.price = price;}  }

 

javabean的hbm文件:

hibernate-mapping>

       <class name="cn.itcast.domain.Product" table="products" catalog="spring3day3">

              <id name="id">

                     <generator class="native"></generator>

              </id>

              <property name="name"></property>

              <property name="price"></property>

       </class>

</hibernate-mapping>   

  • Xml方式整合三大框架

方式一 Action由Spring创建管理

uploading.4e448015.gif正在上传…重新上传取消①.导入整合jar包

uploading.4e448015.gif正在上传…重新上传取消

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

上面基本就是框架整合的全部jar包,需要扩展功能,只需要导入新的jar包即可。

下图:就是struts整合spring一种简单方式,把action交给spring管理:

uploading.4e448015.gif正在上传…重新上传取消

②.创建Action类

public class ProductAction extends ActionSupport{

   private ProductService productService;

   public void setProductService(ProductService productService) {

      this.productService = productService;

   }

   public String list()

   {

      Product product = new Product();

      product.setName("yashua");

      productService.save(product);

      System.out.println("访问成功!");

      return "success";

   }

③.创建Service类

public class ProductService{

private ProductDao productDao;

public void setProductDao(ProductDao productDao) {

   this.productDao = productDao;

}

public void save(Product product) {

   // TODO Auto-generated method stub

   System.out.println("success");

}//仅仅是测试,什么业务也没做。

}

④.创建struts.xml文件

<!-- 在struts.xml 配置常量, 将Action对象创建权 交给Spring   -->

    <constant name="struts.objectFactory" value="spring"></constant>

    <package name="demo" namespace="/" extends="struts-default">

    <action name="product_*" class="productAction" method="{1}">

    <result>/index.jsp</result>

⑤.配置applicationContext.xml

<bean id="productAction" class="cn.itcast.web.ProductAction">

    <property name="productService" ref="productService"></property>

    </bean>

    <bean id="productService" class="cn.itcast.service.ProductService">

    <property name="productDao" ref="productDao"></property>

    </bean>

⑥.代码结构图

uploading.4e448015.gif正在上传…重新上传取消

最后注意:struts2管理Action对象是多例的, Spring管理Action ,默认单例的

所以这个问题:例如:在登陆时,我们原来struts2管理时,每次请求都会创建一个action,valuestack等等。

那么一但交给spring管理后,没有配置多例,默认单例,那么每次请求只会创建一个action

那么这时就会出问题,那就是只允许登陆一次。【一直输入正确账号可以登陆,一旦错误一次,那么后来无论正确与否都不能登陆:原因是:现在使用单例,当我输入错误密码时,我在Actionerror里面放入错误信息,由于是单例,这个错误信息将一直存在,每次登陆都会检查Actionerror是否有值】

方式二 Action自动装配Service

  1. 导入struts2-spring-plugin-2.3.7.jar
  2. 将struts的Action对象创建交给插件
  3. <constant name="struts.objectFactory" value="spring"></constant>
  4. 由struts2 自己创建Action,会自动按name进行装配Service
    • struts.objectFactory.spring.autoWire = name

uploading.4e448015.gif正在上传…重新上传取消

其他代码基本一致,这种装配方式更简单,基本什么也不用配,就搞定。

  • Spring3整合Hibernate3

导入jar :spring-orm-3.2.0.RELEASE.jar 需要依赖 spring-jdbc.jar spring-tx.jar

第一种:方式一 零障碍整合

  1. 通过LocalSessionFactoryBean在spring中直接引用hibernate配置文件
  2. HibernateTransactionManager管理Spring事务
  3. 使用<tx:annotation-driven> 注解管理事务
  4. 将sessionFactory 注入DAO中

uploading.4e448015.gif正在上传…重新上传取消

 

 

 

 

 

 

 

 

 

 

 

 

  1. 编写的DAO继承HibernateDaoSupport //hibernateDaoSupport里面有setSessionFactory方法,注入成功。
  2. 可以将SessionFactory注入DAO,DAO中可以通过getHibernateTemplate() 获得Hibernate模板类对象

uploading.4e448015.gif正在上传…重新上传取消

Spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource、@PostConstruct以及@PreDestroy。
  @Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName (id)自动注入罢了。@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
  @Resource装配顺序
  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;

精髓理解:

当我在service层使用@service,在dao层使用@repository没有指定name属性,那么spring将会使用反射的方式构造一个bean,id就是类型的首字母小写(id=”userDao”) 默认安装byname进行注入,及byId进行注入。

方式二 将Hibernate参数配置到Spring中

  1. 不需要再编写单独hibernate.cfg.xml
  2. 将Hibernate的配置参数完全写入applicationContext.xml

uploading.4e448015.gif正在上传…重新上传取消

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 [h1]根据指定父类生成代理对象。

 [h2]拦截的方法。

 [h3]拦截方法的参数数组。

 [h4]方法的代理对象,可以执行父类的方法。

 [h5]把数据源交给jdbc模板来管理。

 [h6]把spring的jdbc模板交给dao来管理。

 [h7]把dao交给service来管理。

 [h8]编程式事务管理模板,编程式事务管理是通过在beans.xml文件中注入模板来管理事务。

 [h9]使用对象查询方法,返回对象。

 [h10]使用事务模板手动管理事务。下面是除0错误

事务会回滚。

代码原理:只要发送错误,事务回滚,否则提交。

编程式事务管理比较麻烦,每次都需要编写大量代码,所以不常使用。

 [h11]使用原生模式进行事务管理,直接获取代理类。

 [h12]自动生成代理类。

 [h13]保存点对象.

 [h14]程序一进来,contextLoader为空。

 [h15]为空,把ContextLoaderListener赋值给ContextLoader。

Debug结果:

org.springframework.web.context.ContextLoaderListener@1017675

 [h16]

获取webapplicationContext:

Debug如下:(创建成功)

Root WebApplicationContext: startup date [Thu Jan 01 08:00:00 CST 1970]; root of context hierarchy

 [h17]加载配置文件夹

 [h18]

执行结果:

WebappClassLoader

  context: /myscore

  delegate: false

  repositories:

    /WEB-INF/classes/

----------> Parent Classloader:

org.apache.catalina.loader.StandardClassLoader@1b98c3e

 

 [h19]返回Context

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值