<!doctype html>Spring框架

 

 

Spring框架

第一章 spring的使用

spring是现如今最流行的框架之一,有人说没学过spring框架那么就没有资格说自己学过java。创建一个简单的spring,需要哪些条件。

1、添加一个spring依赖,当然如果是maven项目的话只需要添加仓库地址即可,比如:

 

 
 
 
 
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.0.RELEASE</version>
            </dependency>
 

 

2、创建一个spring.xml配置文件(元数据),在此文件通过一个个的bean来告诉spring管理哪些类,这个类必须是可以实例化的,所以不能是接口,抽象类等。

如果你不确定spring.xml配置文件基础格式是什么样的,不要紧的,我们可以通过idea的快捷键来创建一个spring.xml的配置,做法如下:

 
 
 
xxxxxxxxxx
 
 
 
 
选中文件夹右键 ————> new ------>XML Configuration File ------->spring Config
 

如果你的idea没有这个功能,你也别慌,你可以把下面的配置代码复制粘贴到你的配置文件里:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
</beans>
 

记住,spring的xml文件一般命名为 applicationContext.xml

 

3、在xml里面写上你要管理的类,比如我有一个叫UserInfo的类,那么我在spring的xml就应该这样配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置如下-->
    <bean id="userInfo" class="com.entity.UserInfo"/>
</beans>
 

4、创建一个类,在类方法里面创建一个ApplicationContext对象,一般是它子类的是实例(ClassPathXmlApplicationContext),实例化对象的时候传递配置文件名。

5、获取管理的对象,用getBean(id:配置文件bean元素的id)获取这个id管理的bean。

用法如下:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
    public static void main(String[] args){
        //获取ApplicationContext对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过getBean()方法获取对象
        UserInfo userInfo = context.getBean("userInfo",UserInfo.class);
    }
}
 

6、哪些类应该被spring管理

dao、service、部分工具类可以被spring管理,但是servlet是一定不能被spring管理的。实体类,一般不被spring管理,从技术的角度来看是可以被spring管理的。

 

spring管理bean的作用域(scope)

作用域就是:你的脸有多大,你说的话就有多大的效果。

在spring中作用域其实指的就是被spring管理的bean的存活时间。

spring的作用域有4种:

1、prototype(原型):每次getBean的时候都会重新创建一个新的对象。

2、singleton(单例):此作用域类型的bean,在spring容器启动时已经被创建,每次getBean的时候,直接从容器中获取即可。

3、requet(请求):request与session作用域都是在web环境下才有效,被spring管理的bean它的生存期就是在一个完整的请求周期里面。

4、session(会话):就是bean的作用范围是一个完整的session,不同人有不同的会话,互相不干扰。

默认情况下,被spring管理的bean的作用域是singleton,用的最多的就是1、2。singleton作用域的bean,其实例是在容器启动时创建,以后就不再创建。prototype作用的bean是在每次getBean的时候都反复创建bean的实例出来。

如何创建一个原型的bean:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置如下-->
    <bean id="userInfo" class="com.entity.UserInfo" scope="prototype"/>
</beans>
 

 

初始化和销毁

如何做到对象初始化的时候做某件事情?

如果没有spring框架,写代码的一个规范是构造函数里面不要写很复杂的代码,如果是这种情况,建议是把这些代码抽取到一个独立的方法中去,这个方法一般叫init这种名字。按照以前的写法就必须在构造函数中调用这个init方法。

有了spring之后,就可以直接设置一下就会让init被执行,init执行就是在对象创建出来后自动得到执行。

init例子

下面写一个spring管理bean初始化的例子:

比如有一个UserInfo类:

 
 
 
xxxxxxxxxx
 
 
 
 
public class UserInfo{
public void init(){
System.out.pringln("我是初始化方法");
}
}
 

在spring的xml文件里就需要对这个init方法的调用。可以两个地方设置

第一种是在这个被spring管理的bean里设置,比如:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置如下-->
    <bean id="userInfo" class="com.entity.UserInfo" init-method="init"/>
</beans>
 

这个配置只在当前bean生效。

还有一种是在当前spring的xml管理的bean生效,是在beans里面配置一个default-init-method="init":

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-init-method="init">
    <!--配置如下-->
    <bean id="userInfo" class="com.entity.UserInfo"/>
</beans>
 

如果作用域是singleton,init只会执行一次,prototype的话就会反复调用。

设置了spring的xml配置后,那么应该怎么用呢?

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo userInfo = context.getBean("userInfo");
}
}
 

这样它就自动调用了UserInfo类里面的init方法

destroy例子

销毁方法(destroy)跟init差不多的用法。它是在ApplicationContext对象关闭生效的。

比如:

 

 
 
 
xxxxxxxxxx
 
 
 
 
public class UserInfo{
public void init(){
System.out.pringln("我是初始化方法");
}
    public void destroy(){
        System.out.pringln("我是销毁的方法");
    }
}
 

在spring的xml配置跟init一样也是有两种配法:

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置如下-->
    <bean id="userInfo" class="com.entity.UserInfo" init-method="init" destroy-method="destroy"/>
</beans>
 

还有一种整个spring配置文件管理的bean生效的配置

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-init-method="init" default-destroy-method="destroy">
    <!--配置如下-->
    <bean id="userInfo" class="com.entity.UserInfo"/>
</beans>
 

用法就跟init不一样了。它需要关闭ApplicationContext对象的时候才会调用destroy的方法。但是ApplicationContext对象里面是没有close方法的,但是它子类有。所以关闭的时候就需要类型强转。

 

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo userInfo = context.getBean("userInfo");
        //执行下面这句话的时候调用销毁的方法
        ((ConfigurableApplicationContext)context).close();
}
}
 

注意:如果全局设置后,spring管理的所有bean的初始化与销毁都不需要在进行设置,如果某一个bean也设置了init-method或者destroy-method就会覆盖全局的设置。

除了上面的方法外还可以实现接口进行初始化和销毁。

实习的接口有两个:一个是初始化的接口InitializingBean,和销毁的接口DisposableBean

实现这两个接口的方法:

afterPropertiesSet():这个方法名取名叫:"在属性设置完毕之后",其意思就是此类中setter方法被调用后才会调用这个初始化的方法。

destroy():销毁时运行。

代理类,返回代理类的实例

当调用一个类的方法的时候,返回另一个类的实例,这种就是叫做代理类。

举个例子:

当有个A类,它里面有个update方法:

 
 
 
xxxxxxxxxx
 
 
 
 
public class A{
public void update(){
System.out.println("我是A类的update方法");
}
}
 

下面创建一个A类的代理类B类,有个方法返回一个A的实例:

 

 
 
 
xxxxxxxxxx
 
 
 
 
public class B{
public void update(){
System.out.println("我是A类的update方法");
}
    public static A createA(){
        return new A();
    }
}
 

在配置文件里配置如下:

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-init-method="init" default-destroy-method="destroy">
    <!--配置如下-->
    <bean id="b" class="com.entity.B" factory-method="createA"/>
</beans>
 

上面这个配置方法createA方法必须是静态的,如果不是静态的,那么应该先创建一个B的实例出来,然后调用它的方法,比如:

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-init-method="init" default-destroy-method="destroy">
    <!--配置如下-->
    <bean id="b" class="com.entity.B"/>
   <bean id="createA" factory-bean="b" factory-method="createA" />
</beans>
 

那么在用的时候:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A a = context.getBean("createA");
}
}
 

它就成功的返回了A的实例出来。

如果不想太依赖spring框架,那么可以实现接口来实现代理方法:

 
 
 
x
 
 
 
 
public class MyFactoryBean implements FactoryBean<A> {
    /**
     * 这个方法用来创建一个对象
     * @return
     * @throws Exception
     */
    public A getObject() throws Exception {
        return new A();
    }
    /**
     * 这个方法是用来表明此工厂Bean创建出来的对象的class
     * @return
     */
    public Class<?> getObjectType() {
        return A.class;
    }
    /**
     * 这个方法表明此工厂Bean创建出来的对象,在spinrg管理下的作用域
     * true表示是singleton
     * @return
     */
    public boolean isSingleton() {
        return true;
    }
}
 

上面这个配置方式返回的是A这个类的实例,原因是因为MyFactoryBean实现了FactoryBean这个接口,如果没有这个实现,那么返回的就是MyFactoryBean这个实例本身。

小常识

BeanFactory与ApplicationContext的不同

这两个类是spring框架中2个代表性的容器类型(接口)功能上BeanFactory主要有获取实例的功能(getBean)

ApplicationContext在BeanFactory基础上进行了功能的增强,比如,读取消息(做国际化),发布事件,重新加载资源

spring-core,spring-beans,spring-web各个依赖里面大致有哪些功能?

spring-core:提供spring框架的一些核心的基础功能,比如动态代理的创建,读取文件等

spring-beans:提供spring框架的核心的bean管理,ioc,aop等能力

spring-web:spring框架与servlet技术整合使用

配置bean的时候,其lazy-init是什么意思?

lazy-init:延迟初始化的意思。 spring容器启动时,默认是马上实例化scope为singleton对象,这种做法有一个缺点,就是如果spring容器管理的bean过大,就会导致性能低,所以就提供了一个选项lazy-init,设置为true的时候,容器启动的时候,这个设置的bean不会立即实例化,而会等到第一次调用getBean获取实例对象是才实例化。

这个lazy-init属性对scope为prototype的bean无效.

理解prototype作用域时,为什么销毁方法不生效?

因为prototye就是每次getBean的时候临时创建一个新的对象 ,此对象不会被spring所引用,所以就无法对其生命周期进行管理, 所以就不会有销毁方法被调用。

singleton的bean,由于在容器启动时会创建对象出来,而且 会被spring所引用,管理,所以当spring关闭时,会调用其销毁方法

 

第二章 依赖注入

IOC:inverse of control 控制反转

DI:dependency injection 依赖注入

ioc==DI ,网上有些人认为ioc跟DI不是一回事。

了解控制反转

假设有两个类UserService、UserDao,不反转的情况,意思是:

UserService service = new UserService();

service.setDao(new UserDao);

因为上面的UserService 类需要UserDao,给其添加UserDao实例的过程是由自己写代码控制,就是一切都在自己的掌控中,这种情况就叫做不反转。

反转的就是:

context.getBean("UserService",UserService.class);

上面的代码执行完毕之后,UserService已经帮其设置了UserDao。

依赖注入:所有被spring管理的bean,当它依赖一个被其它spring管理的bean的时候,spring负责把其注入进来。

spring管理的依赖注入的形式有三种:

1、构造函数的注入。

2、属性注入(setter)。

3、接口注入(不推荐使用)。

构造函数注入与属性注入

有个类:

 
 
 
xxxxxxxxxx
 
 
 
 
public class DbConfig {
    private String url;
    private String username;
    private String password;
    private String driverClassname;
    public DbConfig(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getDriverClassname() {
        return driverClassname;
    }
    public void setDriverClassname(String driverClassname) {
        this.driverClassname = driverClassname;
    }
    @Override
    public String toString() {
        return "DbConfig{" +
                "url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", driverClassname='" + driverClassname + '\'' +
                '}';
    }
}
 

上面这个类,既有属性又有构造函数

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 下面的配置顺序与构造函数是一一对应,所以不再需要额外的设置-->
    <!--<bean id="dbConfig" class="inject.basic.DbConfig">
        <constructor-arg value="url.."/>
        <constructor-arg value="root"/>
        <constructor-arg value="pwd"/>
        <property name="driverClassname" value="driver"/>
    </bean>-->
    <!--
   当下面的构造设置顺序与想复制的类的构造函数顺序不一致时
   不能仅仅只是设置一个value,还需要额外的信息告诉
   spring,谁赋值给谁
    -->
    <bean id="dbConfig" class="inject.basic.DbConfig" >
        <!--本来这个root应该是赋值给url,但因为加了name=username
        所以就把root 赋值给了构造函数的第二个参数
        -->
        <constructor-arg value="root" name="username"  />
        <constructor-arg value="pwd" name="password"/>
        <constructor-arg value="url.." name="url"/>
        <property name="driverClassname" value="driver"/>
    </bean>
    <!--<bean id="dbConfig" class="inject.basic.DbConfig">
        <constructor-arg value="root" index="1" />
        <constructor-arg value="pwd" index="2"/>
        <constructor-arg value="url.." index="0"/>
        <property name="driverClassname" value="driver"/>
    </bean>-->
<!--配置时,一般是java类型对应同样名字的配置元素
比如List集合,你就用list来配置,Set集合就用set元素配置
集合类型与配置元素类型都会起作用:意思是Set集合类型的元素里面是不能重复
如果你用List元素配置了重复的内容,但Set集合仍然不会有重复
如果你是List集合,但用set元素来配置,就可以让此List集合内部没有重复
-->
    <bean id="addr" class="inject.basic.Address">
        <property name="province" value="gd"></property>
        <property name="city" value="zhuhai"/>
    </bean>
    <bean id="userInfo" class="inject.basic.UserInfo">
        <property name="name" value="aaaa"/>
       <!-- <property name="address" ref="addr"/>-->
        <!--下面的配置方式叫做inner bean(内部bean)
            只是给address属性使用,无法通过getBean方式得到这个对象
        -->
        <property name="address" >
            <bean class="inject.basic.Address" />
        </property>
        <property name="hobbyList" >
            <list>
                <value>football</value>
                <value>basketball</value>
                <value>basketball</value>
            </list>
        </property>
        <property name="xueLi">
            <set>
                <value>chu zhong</value>
                <value>gao zhong</value>
                <value>gao zhong</value>
            </set>
        </property>
        
        <property name="xueFen">
            <map>
                <entry key="yuwen" value="95"/>
                <entry key="shuxue" value="100"/>
            </map>
        </property>
        <!--如果用props配置,值只能是字符串类型-->
        <property name="properties">
            <props>
               <prop key="javaT">111</prop>
               <prop key="netT">yongguang</prop>
            </props>
        </property>
        <property name="addressList">
            <list>
                <ref bean="addr"/>
                <bean class="inject.basic.Address">
                    <property name="city" value="ganzhou"/>
                    <property name="province" value="jiangxi"/>
                </bean>
            </list>
        </property>
    </bean>
</beans>
 

所以说,构造函数的模糊性解决办法有三个:

1、根据name来匹配

2、根据index来匹配

3、根据type来匹配

 

注入的类型

大家都知道,在java里面是有很多类型的。那么在配置的时候应该怎么来配置:

1、普通类型:可以是用value来匹配:

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="name" value="aaaa"/>
 

2、对象类型。如果是自己定义的类,那么可以使用ref或者使用内部bean的方式。

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="address" ref="b"/>
 
 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="address">
     <bean class="com.entity.Address"></bean>
</property>
 

3、list属性

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="list">
     <list>
         <value>aaa</value>
     </list>
</property>
 

4、map

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="list">
      <map>
            <entry key="yuwen" value="95"/>
            <entry key="shuxue" value="100"/>
       </map>
</property>
 

5、set

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="list">
     <set>
         <value>bbb</value>
     </set>
</property>
 

set跟list的区别。list的值可以重复,set不可以重复

5、properties

 
 
 
xxxxxxxxxx
 
 
 
 
 <!--如果用props配置,值只能是字符串类型-->
        <property name="properties">
            <props>
               <prop key="javaT">111</prop>
               <prop key="netT">yongguang</prop>
            </props>
        </property>
 

ref:references的缩写,引用的意思,它的值一般是另一个被spring管理的bean的id。

6、如果类型是String的类型,它想赋值为null。那么直接设置为”“是表示空字符串,而不是null。

 
 
 
xxxxxxxxxx
 
 
 
 
<property name="name">
    <null></null>
</property>
 

 

命名空间

namespace:命名空间

在spring里命名空间有两个:分别是

p:代表着属性的意思

c:代表着构造函数的意思

命名空间主要是让我们少写一些代码。

用法如下:

 
 
 
x
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       <!--需要引入这两个地址-->
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="dataSource"
        class="inject.namespace.MyDataSource"  p:username="sa" c:url="url--" >
      <property name="password" >
          <null></null>
      </property>
  </bean>
    <bean id="factory" class="inject.namespace.MySqlFactory"
          p:dataSource-ref="dataSource"
    c:_0-ref="dataSource">
    </bean>
</beans>
 

引入了

xmlns:p="http://www.springframework.org/schema/p" ​ xmlns:c="http://www.springframework.org/schema/c"

就可以在配置里用p跟c了。

父子bean

在子bean里面设置了parent属性,只是表名在xml配置中会有继承配置的关系,并不要求实际的java类有继承关系。

当一个bean设置abstract为true的时候,意思就是告诉spring不要实例化这个bean出来跟实际的java类是否是抽象无关。一般情况如果一个bean的主要的作用是给子bean继承用,其class可以不设置。

比如:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--在子bean里面设置了parent属性,只是表明在xml配置中会有继承配置的关系
并不要求实际的java类有继承关系
当一个bean设置abstract为true的时候,意思就是告诉spring不要实例化这个bean出来
跟实际的java类是否是抽象无关
一般情况如果一个bean的主要的作用是给子bean继承用,其class可以不设置
-->
    <bean id="myParent"  abstract="true">
        <property name="p1" value="p1"/>
        <property name="p2" value="p2"/>
    </bean>
    <bean id="childOne"
          class="inject.parentchild.ChildOne" parent="myParent">
        <property name="childOne" value="childOne"/>
    </bean>
</beans>
 

autowire

autowire的理解

 
 
 
xxxxxxxxxx
 
 
 
 
 关于自动装配(autowire)的理解
        1.启动spring容器
        2.扫描xml文件.
        3.spring就知道所有被它管理的bean
        4.每个bean,spring通过反射是不是都知道
        其类型,以及构造函数,以及所有的属性(property)
        5.因为bean的作用域是singleton,所以spring要创建出
        对象,创建对象包含实例化和初始化
         5.1 实例化就是调用构造函数,如果构造函数有依赖
         ,spring就会尝试解决掉这个依赖是什么东西
         5.2 初始化:所有的属性,spring都尝试帮你注入值进来
        6.spring尝试给某个bean所有依赖的东西,帮你注入进来
        7.如果你配置了自动装配,那么spring就帮你找一个合适的
        8.如何找呢? 需要依据自动装配类型的设置,autowire的值
            8.1 byType:因为你的Service类依赖的类型是UserDao
                spring又知道所有被它管理的UserDao bean配置只有一个
                所以就自动帮你注入这一个.
                如果找到多个:报错
             8.2 byName:找到service类里面有一个属性名叫dao
             找所有被管理bean的名字为dao的,就帮你注入.
            如果找到的bean id值为dao的类型不符合要求,也会报错
如果有多个bean符合自动装配
可以通过在所有符合条件的被装配bean上进行设置来解决
1.在想被注入的bean上设置primary=true,就表示用这个
2. 在不想被注入的bean上设置autowire-candidate=false
还可以在beans这个根元素上,配置default-autowire-type来设定
一个全局的自动装配类型,这样就不需要在每一个bean上进行设置了
beans上的default-autowire-candidates =设置作为候选baen的名字模式
多个之间用逗号分隔,比如*dao这个名字,意思就是以dao结尾的bean 的名字
 

xml配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire-candidates="*dao" default-autowire="byType">
    <bean id="dao" class="autowire.UserDaoImpl" ></bean>
    <bean id="userDao2" class="autowire.UserDaoImpl2"  ></bean>
    <bean id="userService"
          class="autowire.UserServiceImpl"
          >
    </bean>
</beans>
 

在 default-autowire-candidates="*dao" 可以写多个值。

第三章 与其它框架整合

与dbutil+druid整合

既然是和dbutil、druid整合,那么它们的依赖肯定要导入项目里来。

在这里dbutil是apache下的dbutil,你可以在中央仓库找这个依赖。如果你使用的是maven框架写项目的话就加入如下配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<dependency>
       <groupId>commons-dbutils</groupId>
       <artifactId>commons-dbutils</artifactId>
       <version>1.7</version>
</dependency>
 

druid是阿里的druid。同样如果你要这个jar包,你也可以去中央仓库去找。你使用的是maven框架写项目的话就在配置里加入:

 
 
 
xxxxxxxxxx
 
 
 
 
<dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.19</version>
            </dependency>
 

弄好依赖就开始写项目了。我们最后的效果是在浏览器里显示数据,简单来说就是使用spring+dbutils+druid整合写一个简单的web项目。

一开始肯定要写好项目结构,这里我就默认创建好了。

实体类:Employee

 
 
 
xxxxxxxxxx
 
 
 
 
public class Employee {
    private Integer id;
    private String username;
    private BigDecimal salary;
    private Integer gender;
    private  Integer did;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public BigDecimal getSalary() {
        return salary;
    }
    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public Integer getGender() {
        return gender;
    }
    public void setGender(Integer gender) {
        this.gender = gender;
    }
    public Integer getDid() {
        return did;
    }
    public void setDid(Integer did) {
        this.did = did;
    }
    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", salary=" + salary +
                ", gender=" + gender +
                ", did=" + did +
                '}';
    }
}
 

dao层:EmployeeDao

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeDaoImpl {
    private  QueryRunner queryRunner ;
    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }
    public Employee getById() {
        String sql = "select id,username from employee where id = 1";
        BeanHandler<Employee> handler = new BeanHandler<>(Employee.class);
        Employee employee = null;
        try {
            employee = queryRunner.query(sql,handler);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("query failed...");
        }
        return employee;
    }
}
 

service层:EmployeeService

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServiceImpl {
    private EmployeeDao dao;
    public void setDao(EmployeeDao dao) {
        this.dao = dao;
    }
    public Employee getById() {
        return dao.getById();
    }
}
 

web层(放置servlet、jsp文件的):EmployeeServlet类 、index.jsp

 
 
 
xxxxxxxxxx
 
 
 
 
@WebServlet("/index")
public class EmployeeController extends HttpServlet {
@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeService service =  context.getBean("employeeService", EmployeeService.class);
        Employee employee = service.getById();
        req.setAttribute("emp", employee);
        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }
}
 

如果使用这种方法的话,如果servlet多了,就会创建很多个spring容器对象,那么相应的也会导致被spring容器管理的bean不具备单例的情况,有多少个spring容器,单例的bean就会有多少个,所有spring容器应该只需要一个就可以了。

解决方法:

使用监听器,确保spring容器与web共存亡,且只创建一次。

这里我就创建一个listener包,用来存放监听器:

 
 
 
xxxxxxxxxx
 
 
 
 
public class ApplicationContextInstantiateListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
          ServletContext servletContext = servletContextEvent.getServletContext();
        //获取在web.xml配置的context-param标签的值,也就是动态的实现调用spring配置文件
        String configFille = servletContext.getInitParameter("configFile");
        ApplicationContext context = new ClassPathXmlApplicationContext(configFille);
        servletContext.setAttribute("context",context);
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    }
}
 

监听器的配置在WEB-INF/web.xml

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>configFile</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>com.spring.ApplicationContextInstantiateListener</listener-class>
    </listener>
</web-app>
 

用的时候可以通过实现ApplicationContextAware接口的类来实现调用

 
 
 
xxxxxxxxxx
 
 
 
 
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    public static ApplicationContext getApplicationContext(){
        return context;
    }
    public static <T> T getBean(String name,Class<T> clz){
        return context.getBean(name,clz);
    }
}
 

当调用一次ApplicationContext对象时,那么spring就会自动帮这个类注入一个ApplicationContext对象。当然这个类应该被spring管理。

用法:

 
 
 
xxxxxxxxxx
 
 
 
 
@WebServlet("/index")
public class EmployeeServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        EmployeeService service =  ApplicationContextHolder.getBean("employeeService", EmployeeService.class);
        Employee employee = service.getById();
        req.setAttribute("emp", employee);
        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }
}
 

配置文件:beans.xml

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="password" value="root"></property>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg ref="dataSource"></constructor-arg>
        <constructor-arg value="true"/>
    </bean>
    <bean id="employeeDao" class="com.dao.impl.EmployeeDaoImpl">
        <property name="queryRunner" ref="queryRunner"/>
    </bean>
    <bean id="employeeService" class="com.service.impl.EmployeeServiceImpl">
        <property name="dao" ref="employeeDao"/>
    </bean>
    <!---->
    <bean class="com.web.ApplicationContextHolder"/>
</beans>
 

与mybatis+druid整合

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

它所在的依赖配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
 

因为是mybatis与spring的整合,所以需要用到mybatis-spring依赖,

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.2</version>
</dependency>
 

不是有了mybatis-spring就不用spring的依赖了,还需要用到spring的依赖:

spring-jdbc

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
 

spring-context

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
 

下面看下怎么去实现一个web项目

不使用mybatis配置文件与mapper映射文件,使用注解的方式:

跟上面的dbutils+spring+druid代码相似,唯一不同的就是dao层的代码与applicationContext的配置:

dao层,只需要一个接口,跟mybatis的做法一样。还记得mybatis是用SqlSession对象的getMapper(Class clz)方法获取一个代理对象,接口就不需要有具体的实现类。

 
 
 
xxxxxxxxxx
 
 
 
 
public interface EmployeeDao {
    @Select("select id,username from employee where id=1")
    Employee getById();
}
 

因为需要用到SqlSession对象的getMapper()方法获取一个代理类,所以就开始就需要一个SqlSessionFactory对象。applicationContext里面就要怎么代理这个对象。如果我们自己去实现的话就十分麻烦,庆幸mybatis-spring帮我们做了这件事。

 
 
 
xxxxxxxxxx
 
 
 
 
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
 

有了SqlSessionFactory对象就可以创建一个dao层的代理类:

 
 
 
xxxxxxxxxx
 
 
 
 
 <bean id="employeeDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
       <!--注入要创建实现类的mapper接口就可以得到class信息-->
        <property name="mapperInterface" value="com.dao.EmployeeDao"/>
       <!--注入了SqlSessionFactory就可以得到SqlSession-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
 

最终applicationContext.xml的效果是:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="password" value="root"></property>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
<!--
这个配置是一个FactoryBean,用来创建SqlSessionFactory
-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--
    这个配置用来给某一个特定的Mapper接口(dao)创建出它的实现类
    sqlSession.getMapper(XxxDao.class);
    如果项目中有多个Mapper接口,就需要多个MapperFactoryBean的配置
-->
    <bean id="employeeDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
       <!--注入要创建实现类的mapper接口就可以得到class信息-->
        <property name="mapperInterface" value="com.dao.EmployeeDao"/>
       <!--注入了SqlSessionFactory就可以得到SqlSession-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    <bean id="employeeService" class="com.service.impl.EmployeeServiceImpl">
        <property name="dao" ref="employeeDao"/>
    </bean>
<!--配置此bean是因为此bean实现了ApplicationContextAware
被spring管理后会自动往此bean里面注入spring容器对象
-->
    <bean class="com.spring.ApplicationContextHolder"/>
</beans>
 

这个方法现在都废弃不用了,因为如果有多个dao的话,就要有多个MapperFactoryBean配置。

下面说两种比较常见的整合方式:

第一种(有mybatis-config文件)

保留mybatis配置文件与mapper映射文件。

mybatis配置文件:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <mappers>
        <mapper resource="employeeMapper.xml"/>
    </mappers>
</configuration>
 

如果需要插件就要在mybatis配置文件里面配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
      <!--这是是否使用分页插件-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="supportMethodsArguments" value="true"/>
        </plugin>
    </plugins>
    <mappers>
        <mapper resource="employeeMapper.xml"/>
    </mappers>
</configuration>
 

至于mapper文件是用来写sql语句的:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dao.EmployeeDao">
    <select id="getById" resultType="com.entity.Employee">
        select id,username from employee where id = 1
    </select>
</mapper>
 

这样的配置的好处:遵循开闭原则。在dao层这里配置注解的方式,如果需要修改sql语句那么就需要修改代码。

applicationContext配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
       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://mybatis.org/schema/mybatis-spring
       http://mybatis.org/schema/mybatis-spring.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    
     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="password" value="root"></property>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <mybatis:scan base-package="com.dao"/>
    <bean id="employeeService" class="com.service.impl.EmployeeServiceImpl" autowire="byType"/>
</beans>
 

这里sqlSessionFactory里面是可以配置mybatis配置文件的,所以我们可以给他一个资源名字。然后它就会自动去找这个mybatis配置文件。

上面提到的需要多个MapperFactoryBean配置,我们可以用:

<mybatis:scan base-package="com.dao">

如果dao层有多个dao类,它都会代理出来准备你去用它。

那么问题来了,EmployeeService需要的dao,那么我们怎么知道需要注入哪个dao类。这里我们就可以使用自动注入的机制了:

第二方法(没有mybatis配置文件)

如果我们想不要mybatis的配置文件,那么我们也是可以做到的,那么我们就需要在spring的配置文件里配置mybatis所需要的配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
       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://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/com.demo"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:employeeMapper.xml"/>
        <property name="configuration">
            <!--这个是配置sql日志的-->
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"></property>
            </bean>
        </property>
        <!--配置插件-->
        <property name="plugins">
            <list>
                <!--分页的插件-->
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <value>
                            supportMethodsArguments=true
                            resonable=true
                        </value>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    <mybatis:scan base-package="com.dao"/>
    <bean id="employeeService" class="com.service.EmployeeService" autowire="byType"/>
</beans>
 

sqlSessionFactoryBean类下有很多属性,其中就有配置mybatis所需要的配置:

mapperLocations,通过这个属性可以告诉spring我们用的是哪个映射文件。我们就可以把mybatis的配置丢掉了。

mybatis-config文件解析之后mybatis是用Configuration类型来代表(入口对象),所以我们就可以配置是否使用各种mybatis的配置。

plugins:配置插件。

 

使用spring-web

对了,说下之前我们做过一个web项目,用到过sprng容器根据tomcat启动而创建且只创建一次的知识。

那时是实现了一个接口ApplicationContextAware接口,其实这个功能已经有人帮我们做了,我们只需要拿来用就好了。怎么拿呢,我们可以添加一个spring-web的依赖。

 
 
 
xxxxxxxxxx
 
 
 
 
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
 

然后在web.xml配置监听器:

 
 
 
xxxxxxxxxx
 
 
 
 
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--
    这个上下文参数的配置是给ContextLoaderListener监听器使用的
    用来告诉监听器在创建spring容器对象时元数据文件的路径
    如果不配置,默认就是在WEB-INF目录下找applicationContext.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>
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
 

tomcat一启动时,它就会加载spring容器。然后把applicationContext对象注入到WebApplicationContextUtil里,然后我们就可以用它:

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());
        EmployeeService service =  context.getBean("employeeService", EmployeeService.class);
        Employee employee = service.getById();
        req.setAttribute("emp", employee);
        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }
}
 

 

在这里我写个与pageHelper整合的web层的代码:

servlet:

 
 
 
xxxxxxxxxx
 
 
 
 
@WebServlet("/index")
public class EmployeeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("WEB-INF/index.jsp").forward(req,resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        int pageNum = Integer.parseInt(req.getParameter("pageNum"));
        int pageSize = Integer.parseInt(req.getParameter("pageSize"));
        EmployeeService service = new EmployeeService();
        List<Employee> emps = service.getEmpsByPageHelper(pageNum, pageSize);
        PageInfo<Employee> pageInfo = new PageInfo<Employee>(emps,3);
        Gson gson = new Gson();
        String json = gson.toJson(pageInfo);
        resp.getWriter().print(json);
    }
}
 

jsp:

 
 
 
xxxxxxxxxx
 
 
 
 
<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/10/14
  Time: 9:55
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/jquery/jquery-3.4.1.js"></script>
</head>
<body>
<table>
    <thead>
    <tr>
        <th>编号</th>
        <th>姓名</th>
        <th>工资</th>
        <th>性别</th>
        <th>部门编号</th>
    </tr>
    </thead>
    <tbody class="tab-body">
    </tbody>
</table>
<br>
<div id="data">
</div>
<script>
    $.ajax({
        url: "${pageContext.request.contextPath}/index",
        method: "post",
        data: {
            pageNum: 1,
            pageSize: 3
        },
        dataType: "json"
    }).done(function (res) {
        resultReduction(res)
        c()
        //事件
        function c() {
            $(".pageClick").click(function (e) {
                var pageNum = $(this).val()
                $.ajax({
                    url: "${pageContext.request.contextPath}/index",
                    method: "post",
                    data: {
                        pageNum: pageNum,
                        pageSize: 3
                    },
                    dataType: "json"
                }).done(function (res) {
                   resultReduction(res)
                    c()
                })
            })
        }
        
        function resultReduction(res) {
            $(".tab-body").html("")
            $("#data").html("")
            res.list.forEach(function (emp) {
                var sql = "<tr><td>" + emp.id + "</td><td>" + emp.username + "</td><td>" +
                    emp.salary + "</td><td>" + emp.gender + "</td><td>" + emp.did + "</td></tr>";
                $(".tab-body").append(sql);
            })
            var sql = "<button class='pageClick' value ='1'>首页</button>" +
                "<button class='pageClick' value ='" + res.prePage + "'>上一页</button>" +
                "<button class='pageClick' value ='" + res.nextPage + "'>下一页</button>" +
                "<button class='pageClick' value ='" + res.pages + "'>尾页</button>";
            res.navigatepageNums.forEach(function (num) {
                sql += "<br><button class='pageClick' value ='" + num + "'>"+num+"</button>";
            })
            $("#data").append(sql);
        }
    })
</script>
</body>
</html>
 

 

第四章 aop

aop的理解

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

在使用spring的aop之前应该导入一个依赖:

 
 
 
xxxxxxxxxx
 
 
 
 
 <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
 </dependency>
 

aspectjweaver是spring的切入点表达式需要用的包。

案例

下面通过一个案例实现aop的运用,主要的目标是在执行业务层的时候输出日志:

业务层EmployeeImpl类的代码:

 
 
 
xxxxxxxxxx
 
 
 
 
package com;
public class EmployeeServiceImpl {
    public final void insert(){
        System.out.println("开始插入(insert)-----");
    }
    public void update(){
        System.out.println("开始更新(update)-----");
    }
}
 

日志类的代码:

 
 
 
xxxxxxxxxx
 
 
 
 
public class LogImpl {
    public void beforeXXX(){
        System.out.println("开始执行*********");
    }
}
 

applicationContext.xml的配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="log" class="com.LogImpl"/>
    <bean id="employeeService" class="com.EmployeeServiceImpl"/>
    <aop:config>
        <!--这是切面类的配置-->
        <aop:aspect id="logAspect" ref="log">
            <!--这是切点表达式的配置-->
            <aop:pointcut id="myPointcut" expression="execution(* com.*.*())"/>
            <!--before的通知,主要用来确定当前这个切面类的什么方法在什么时候切入到
            切点表达式指向的方法里面.
            -->
            <aop:before method="beforeXXX" pointcut-ref="myPointcut" />
        </aop:aspect>
    </aop:config>
</beans>
 

然后就可以使用:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeServiceImpl service = context.getBean("employeeService", EmployeeServiceImpl.class);
        service.insert();
        service.update();
    }
}
 

输出:

 
 
 
xxxxxxxxxx
 
 
 
 
开始执行*********
开始插入(insert)-----
开始执行*********
开始更新(update)-----
 

 

案例讲解

Aspect:切面 pointcut:切点

aop的几个术语:
 
 
 
xxxxxxxxxx
 
 
 
 
1.切面(aspect):案例中指的就是LogImpl这个类
2.切点(pointcut):它也称为切点表达式,目的是描述符合条件的方法
3.目标(target):就是案例中的EmployeeServiceImpl对象
4.连接点(join point):就是目标对象中的insert,update方法
5.通知(advice):就是切面类中的beforeXXX这个方法
6.aop代理(aopProxy):spring aop的实现就是靠代理来做到的,默认利用jdk代理和cglib代理  来实现
7.织入(weaving):是个动词,表示把切面类的通知与目标对象连接点糅合在一起的过程就叫织入
 
通知类型

上面说到before是一种通知类型,那么相对应的还有其他通知类型:

 
 
 
xxxxxxxxxx
 
 
 
 
1.before:前置通知,在连接点方法之前执行,而且不能控制连接点是否执行
2.after:后置通知也叫最终通知,意思就是连接点方法只要执行(不管是否有错误),它都会得到执行。它是在目标方法执行完执行。
3.after-return:返回通知,连接点正常执行(不报错)时才会执行这个通知.
4.throwing:异常通知:连接点方法抛出异常时才会得到执行.这个通知不能处理异常只能得到异常信息.异常通知如果想把目标方法抛出的异常传递给通知方法只需要在异常通知的throwing属性设置的值等于通知方法的参数名就可以.
当异常通知方法没有异常类型作为参数时,潜台词就是目标方法抛出任何异常,通知都会得到执行
当异常通知方法"有"异常类型作为参数是,潜台词是只有目标方法抛出的异常是参数指定类型的异常或是子类型时,此通知方法才会得到执行
5.around通知:环绕通知,环绕通知是最强的通知类型,它可以完全取代上面的4种
也可以进行异常的捕获处理,也可以组织目标方法执行
 

这五种是比较常见的通知类型,其实大部分在项目里比较常用的就只有环绕通知。

获取目标方法的参数

一般来说,目标方法是有一些是有参数的,如果需要获取这些参数的值那么应该怎么做呢?

我们先来说说环绕通知怎么获取目标方法参数:

比如:

主要实现功能:有个类,方法里面有参数,然后在日志类里获取其参数。

EmployeeServiceImpl:

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServiceImpl {
    public void update(){
        System.out.println("update*****");
    }
    public void insert(){
        System.out.println("insert***");
    }
    public int add(int a,int b){
        return a+b;
    }
}
 

这个方法有返回值,也有参数。

然后日志类:

 
 
 
xxxxxxxxxx
 
 
 
 
public class LogImpl {
    public Object aroundParam(ProceedingJoinPoint joinPoint){
        System.out.println("before");
        //获取参数的集合
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println("arg = " + arg);
        }
        Object result = null;
        try {
            //获取返回值
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            System.out.println("throw......");
        }
        System.out.println("after");
        return result;
    }
}
 

applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="log" class="com.demo.LogImpl"/>
    <bean id="employeeService" class="com.demo.EmployeeServiceImpl"/>
    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.demo.*.*(..))"/>
        <aop:aspect id="logAspect" ref="log">
          <aop:around method="aroundParam" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
 

使用:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeServiceImpl employeeService = context.getBean("employeeService", EmployeeServiceImpl.class);
        employeeService.add(5,6);
    }
}
 

结论:如果是环绕通知,那么就可以通过ProceedingJoinpoint里面的getArgs()来获取所有参数。同时如果有返回值的话,可以定义一个变量存储,然后result = joinPoint.proceed();。

如果是其他方法想要获取目标方法的参数:

 
 
 
xxxxxxxxxx
 
 
 
 
  1. 切点表达式中的args与execution都叫切点指示器(PointCut Designer)
        2.args是用来表示查找有多少个参数,args(x),表示找到只有一个参数
        3.args里面的名字相当于一个变量名,在调用目标方法时,依据调用目标方法
        传递的数据,给这个变量名赋值
        4.通知执行的时候,需要参数,那么这些参数的值就通过从变量里面找,默认就是找同名的
        所以在不设置arg-names的时候,就会找args()表达式里面与通知方法同名的数据
        5.但是4步的做法会导致切点表达式与通知方法的形参名耦合一起,所以spring提供了一个灵活的机制
        就是在配置通知时,利用arg-names指定"别名"
        6.args-names指定了别名后就以别名为准,那么就会导致args表达式里面就需要与args-names对应起来
     总而言之: 想让通知方法获取到传递给目标方法的参数需要这么做:
     1. args表达式里面的名字与通知方法的名字与个数匹配即可,此时不需要配置通知的arg-names
     配置代码如下:
      <aop:pointcut id="myPointcut1" expression="execution(* com.service.EmployeeServiceImpl.*(..)) and args(m,n)"/>
      <aop:before method="before2" pointcut-ref="myPointcut1" />
     2. args表达式里面的名字任意,此时就需要在通知方法的arg-names配置得与args表达式名字一样
     此时这两者配置的名字不需要与通知方法的形参名一样.
     <aop:pointcut id="myPointcut1" expression="execution(* com.service.EmployeeServiceImpl.*(..)) and args(x1,y1)"/>
      <aop:before method="before2" pointcut-ref="myPointcut1" arg-names="x1,y1"/>
      3.由于所有的通知方法都可以添加JoinPoint类型作为第一个参数,而这个类型
      是可以很方便的得到参数,你也可以采用这种方式,这样就不需要上面的2种方法来达成同样的目的
 

配置文件:

 
 
 
xxxxxxxxxx
 
 
 
 
 <aop:config>
        <aop:pointcut id="myPointcut1" expression="execution(* com.EmployeeServiceImpl.*(..)) and args(x1,y1)"/>
        <aop:aspect ref="paramAspect" order="3">
            <aop:before method="before2" pointcut-ref="myPointcut1" arg-names="x1,y1"/>
            <!-- <aop:around method="aroundParam" pointcut-ref="myPointcut1"/>-->
        </aop:aspect>
    </aop:config>
 

aop排序

 
 
 
xxxxxxxxxx
 
 
 
 
在同一个切面中,配置的同类型通知,其顺序官方没有明确的说明就不能认为以配置的顺序为准,但是如果是在不同的切面中配置,可以通过order属性来准确的控制其执行顺序数字小的先执行.
 

比如:

 
 
 
xxxxxxxxxx
 
 
 
 
<aop:config>
    <aop:pointcut id="myPointcut1" expression="execution(* com.service.EmployeeServiceImpl.*(..))"/>
    
    <aop:aspect ref="logImpl" order="3">
        <aop:around method="aroundAdvice" pointcut-ref="myPointcut1"/>
    </aop:aspect>
    <aop:aspect ref="logImpl" order="4">
        <aop:around method="aroundAdvice2" pointcut-ref="myPointcut1"/>
    </aop:aspect>
</aop:config>
 

上面这个案例肯定是order="3"先执行,

 

切点表达式

spring aop只针对方法进行aop代理,不想aspectj联盟搞的aop实现 这个aop联盟的实现功能比spring aop要强大,比如可以针对字段进行切面编程.

 

1.切点指示器(PCD:PointCutDesigner):指示器可以理解为一种描述找到方法的方式.spring支持的切点指示器有如下几个:

 
 
 
xxxxxxxxxx
 
 
 
 
1.1 execution:用来匹配连接点方法的,用的最多的一个指示器 
1.2 within:英文的意思是:在某某之内,一般就是指的是在"某些"类之内 within(com.service.*)就是指com.service包下的所有类的方法进行aop代理 
1.3 this:指的就是动态代理生成的对象,这种指示器一般表示某个动态代理 对象是某个类型,比如this(com.service.EmployeeService),就表示 动态代理对象是EmployeeService的实现类
1.4 target:指的是被代理的对象.指定的是"某个"特定的目标对象 
1.5 args:此指示器是在方法的参数层面来描述,比如args(integer,String)就表示 所有有2个参数,并且类型分别是integer,string的方法 
1.6 @target:就表示目标类型上有特定注解修饰的目标对象 @target(com.MyFirstAnnotation),就会找所有被spring 管理的bean上面有MyFirstAnnotation注解的目标类 
1.7 @args:与arg类似,只不过是表明参数上有特定注解的 
1.8 @Within:与within类似,只不过是表明类上有特定的注解修饰 
1.9 @annotation:指的是连接点方法上有特定注解 
1.10 bean:这个指示器不是aop联盟的标准,是spring自己提供的 指示特定bean的名字的指示器
 

2、 指示器的逻辑运算符

并且:and(&&)

或者:or(||)

非:not(!)

 

3、 execution指示器不同的指示器,表达式的写法的模式可能是不一样的额, 它的编写格式: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 上面格式的含义如下:

 
 
 
xxxxxxxxxx
 
 
 
 
 3.1 ?:表示有或者没有  ,没有问号就表示必须写 
 3.2 modifiers-pattern:修饰符模式
 ret-type-pattern:返回类型模式      
 declaring-type-pattern:声明类型模式     
 name-pattern:指的就是方法的名字模式    
 param-pattern:指的就是方法参数的模式      
 throws-pattern:指的是方法抛出的异常的模式  

 其中三个模式必不可少:返回类型,方法名,方法参数   
 

通配符: *:表示任意名字 ..:任意段的任意字符

常见的例子:

 
 
 
xxxxxxxxxx
 
 
 
 
1.  execution(public * *(..)):找到所有的公有方法
2.  execution(* get*(..)):找到以get开头的方法
3.  public * com.service.emp.EmployeeService+.*(..))
+ 表示接口的所有方法以及此接口实现类自己的方法都会被aop代理
4. public * com.service..*.*(..)) 表示com.service包以及子孙包下
5. within(  com.service..*) 表示com.service包以及子孙包下的所有类,写法与execution指示器是不同的,不需要写返回类型
6. target(  com.service.emp.impl.EmployeeServiceImpl) 给EmployeeServiceImpl进行aop代理,不能使用通配符.表达式中指定父类型或者接口时是可以的.
7. this(com.service.emp.impl.EmployeeServiceImpl):给EmployeeServiceImpl类进行aop代理.写法与target类似.意思是:如果能代理成功,那么生成的代理类是表达式里面设定的类型的实例
8. bean(emp)表示给emp这个bean进行aop代理
 

proxy-target-class的用法

如果有个接口叫EmployeeService,其有个实现类EmployeeServiceImpl,如果在aop那里代理的是实现类。然后获取这个类的时候用的是接口来接受的,这样是依赖倒转设计原则里面所说的。但是如果你在调用其方法是错误的。

原因是:默认情况下,EmployeeServiceImpl被aop代理的时候生成的是一个代理类。所以代理类是不可以向上转型成为EmployeeService的。如果你想要生成EmployeeServiceImpl的子类的话,就要设置:

 
 
 
xxxxxxxxxx
 
 
 
 
<aop:config proxy-target-class="true">
...
</aop:config>
 

这样设置就是叫aop代理的时候使用jdk的方式代理,生成的是EmployeeServiceImpl的子类。然而这个子类是可以向上转型成为EmployeeService的。

 

aop的通知器(advisor)

Advice:通知,代表一种注入方式,比如前置,后置。

Advisor:通知器或者统治者.有某个或某些特定通知类型的切面类

特定通知是靠此类实现某些接口来表示的.

spring有如下接口来表明不同的通知类型:

 
 
 
xxxxxxxxxx
 
 
 
 
MethodBeforeAdvice:前置通知
AfterReturningAdvice:返回通知
ThrowsAdvice:异常通知
MethodInterceptor:环绕通知
注意:没有最终通知(after通知)
 

实现这些接口就是告诉spring,我这个实现类里的哪个方法是什么样的通知。

这四种接口里,除了异常通知接口是空接口外,其它都是有方法的。

但是我们如果实现了异常接口,那么我们就需要自己写一个方法,不然是会保错的。

异常接口的方法的签名必须是:

 
 
 
xxxxxxxxxx
 
 
 
 
1.返回类型是void
2.方法名是afterThrowing
3.方法的参数可以是
    3.1 Method method, Object[] args, Object target
    3.2 或者Method method, Object[] args, Object target,异常类
     
 

但是,我试了下,可能第三个方法参数不能为第一个。

下面我们通过一个案例了解一下:

业务层:EmployeeServiceImpl

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServiceImpl implements EmployeeService {
    @Override
    public int add(Integer x, Integer y) {
       // throw new RuntimeException("failed----"); //异常通知测试
        return x + y;
    }
}
 

通知器类:MyAdvisor

 
 
 
xxxxxxxxxx
 
 
 
 
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class MyAdvisor implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor, ThrowsAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("before----");
    }
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----debug: returnValue = " + returnValue);
    }
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //这行代码相当于改变了传递给目标对象方法的参数值.
        invocation.getArguments()[0] = (Integer) invocation.getArguments()[0] + 100;
        System.out.println("around before");
        Object result = invocation.proceed();
        System.out.println("around after");
        return result;
    }
    //异常通知方法
    public void afterThrowing(Method method, Object[] args, Object target, RuntimeException re) throws Throwable {
        System.out.println("throw----" + re.getMessage());
    }
}
 

因为我们告诉了spring,我这些方法是什么方法。所以就不需要在applicationContext.xml里面在声明我调用的是什么方法了,<aop:before />。我们只需要告诉spring,我用的是哪个通知器就行,当然通知器也是需要被spring所代理。

applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--通知器被spring管理-->
    <bean id="myAdvisor" class="com.advisor.MyAdvisor"/>
    <bean id="emp" class="com.dao.EmployeeDaoImpl"/>
    <aop:config>
        <!--配置切点表达式-->
        <aop:pointcut id="myPointcut"
                      expression="execution(* com.service..*.*(..))"/>
        <!--告诉spring。我用的是哪个通知器-->
        <aop:advisor advice-ref="myAdvisor"
                     pointcut-ref="myPointcut"/>
    </aop:config>
</beans>
 

这样当你用的方法符合切点表达式的时候,通知器里面的方法只要符合通知类型且需要通知,那么都会执行。

Main:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeService employeeService = context.getBean("emp", EmployeeService.class);
        int result = employeeService.add(5, 6);
        System.out.println(result);
    }
}
 

aop代理实现通知器

这个我也不怎么懂,原理是什么。但是还是写个案例吧。其主要功能就是代理目标对象,然后设置下,就能做到aop代理这个目标方法。

配置文件:applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--
        关于这方面的详细可以在官方文档中搜索ProxyFactoryBean去了解
        主要在官方文档的spring aop api这一节中
    -->
    <bean id="myAdvisor" class="com.advisor.MyAdvisor"/>
    <bean id="emp" class="com.dao.EmployeeDaoImpl"/>
    <bean id="empFactory" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="emp"></property>
        <property name="interfaces">
            <list>
                <value>com.service.EmployeeService</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myAdvisor</value>
            </list>
        </property>
    </bean>
</beans>
 

main:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext_factoryBean.xml");
        EmployeeService employeeService = context.getBean("empFactory", EmployeeService.class);
        int result = employeeService.add(5, 6);
        System.out.println(result);
    }
}
 

有兴趣去官网看吧。

 

第五章 spring事务管理器

事务管理

一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性说成是 ACID

  • 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
  • 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
  • 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
  • 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。

一个真正的 RDBMS 数据库系统将为每个事务保证所有的四个属性。使用 SQL 发布到数据库中的事务的简单视图如下:

  • 使用 begin transaction 命令开始事务。
  • 使用 SQL 查询语句执行各种删除、更新或插入操作。
  • 如果所有的操作都成功,则执行提交操作,否则回滚所有操作。

Spring 框架在不同的底层事务管理 APIs 的顶部提供了一个抽象层。Spring 的事务支持旨在通过添加事务能力到 POJOs 来提供给 EJB 事务一个选择方案。Spring 支持编程式和声明式事务管理。EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现。

局部事物 vs. 全局事务

局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。

局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。

全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。

 

spring事务写法要点:

 
 
 
xxxxxxxxxx
 
 
 
 
1.配置一个DataSource
2.配置事务管理器,用上dataSource
3.配置一个事务通知tx:advice   
3.1 对某些方法进行事务相关属性配置,比如超时(timeout),事务隔离级别 ,事务传播方面的配置,只读配置    3.2 一定要记得关联事务管理器,默认名字是transactionManager
4.配置aopconfig,确定对哪些业务类的方法进行事务处理
***事务的处理是针对业务类,不是dao***
 

事务管理器:

主要用来管理物理连接,事务提交,回滚等功能有了事务配置,对我们的dao里面用的连接相关的信息就有了要求:

1.因为这个事务管理器是针对DataSource,所以我们的dao必须用"同一个"dataSource

2.DataSource获取方法必须是Spring提供的方式

事务管理器案例

下面通过案例来说明spring事务管理器

主要实现功能:两个表删除数据,实现事务功能——如果一个表的列删除失败有异常,则另一个表的列也不会删除

这就是回滚机制。

dao层:因为有两张表,所以有两个dao类

DeptDaoImpl:

 
 
 
xxxxxxxxxx
 
 
 
 
public class DeptDaoImpl extends BaseDao {
    public void deleteById(int id) throws Exception {
        String sql = "delete from dept where id =?";
        //int i = 5/0; //这个注释是会抛异常的
        //jdbcTemplate是spring-jdbc里的方法。其主要功能是用来操作数据库的
        jdbcTemplate.update(sql, id);
    }
}
 

EmployeeDaoImpl:

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeDaoImpl extends BaseDao {
    public void deleteByDeptId(int id){
        String sql = "delete from employee where deptid =?";
        jdbcTemplate.update(sql, id);
    }
}
 

上面两个类都继承了父类,该父类是个抽象类。主要是两个子类的重复代码的封装:

 
 
 
xxxxxxxxxx
 
 
 
 
public abstract class BaseDao {
    protected JdbcTemplate jdbcTemplate;
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
 

然后是业务层的代码:

 
 
 
xxxxxxxxxx
 
 
 
 
public class DeptServiceImpl {
    private DeptDaoImpl deptDao;
    private EmployeeDaoImpl employeeDao;
    public void setDeptDao(DeptDaoImpl deptDao) {
        this.deptDao = deptDao;
    }
    public void setEmployeeDao(EmployeeDaoImpl employeeDao) {
        this.employeeDao = employeeDao;
    }
    public void deleteWholeDeptById(int id) throws Exception {
        employeeDao.deleteByDeptId(id);
        deptDao.deleteById(id);
    }
}
 

主要的applicationContext.xml配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
        <property name="password" value="root"/>
        <property name="username" value="root"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="baseDao" abstract="true">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="deptDao" class="com.dao.DeptDaoImpl" parent="baseDao">
    </bean>
    <bean id="empDao" class="com.dao.EmployeeDaoImpl" parent="baseDao">
    </bean>
    <bean id="deptService" class="com.service.impl.DeptServiceImpl">
        <property name="employeeDao" ref="empDao"/>
        <property name="deptDao" ref="deptDao"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--
        tx:advice的事务管理器设置:
        如果你配置的事务管理器的名字就叫做transactionManager,
        那么transaction-manager就可以不用设置
    -->
    <tx:advice id="txAdvisor" transaction-manager="txManager">
        <tx:attributes>
            <!--查询操作-->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED" rollback-for="com.dao.MyCheckEx"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="serivceTx" expression="execution(* com.service..*.*(..))"/>
        <aop:advisor advice-ref="txAdvisor" pointcut-ref="serivceTx"/>
    </aop:config>
</beans>
 

可以配置多个<tx:method />,一般的配置,查询操作用只读事务,会优化性能它也支持通配符*默认情况下,spring会对运行时异常产生回滚,检查异常不回滚如果想针对检查异常也回滚,那么就需要配置rollback-for

mybatis这种持久层框架,其所有数据库操作的异常都是运行时异常所以method的rollback-for保留默认即可,不需要额外配置

注意:tx:attributes是必须配置,如果不配置,整个就运行在非事务环境下

 
 
 
xxxxxxxxxx
 
 
 
 
 ///////////////////////******************源码解析*******************/////////////////
        tx:attributes是必须配置的,否则在xml这种配置情况下就没有事务相关的配置信息,
        spring并不提供事务相关属性的默认值,所以会导致方法运行在非事务环境下
        研究方法:点击tx:advice跳转到xsd文件,会看到TransactionInterceptor,在此类的invoke方法中可以看到
        调用了invokeWithinTransaction方法,在此方法中可以看到获取TransactionAttribute信息时,考虑到了方法.如果没有
        相关的事务信息配置,就不会创建调用事务管理器的getTransaction方法,就不会有事务.
        1.有配置tx:attributes时会用到NameMatchTransactionAttributeSource,没有配置时会用到AbstractFallbackTransactionAttributeSource
        2.方法调用流:invoke()->invokeWithinTransaction()->createTransactionIfNecessary()
        ///////////////////////******************源码解析*******************/////////////////
        
 

Main:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        DeptServiceImpl deptService = context.getBean("deptService", DeptServiceImpl.class);
        try {
            deptService.deleteWholeDeptById(22);
        } catch (Throwable throwable) {
            System.out.println("-----debug: throwable.getClass().getName() = " + throwable.getClass().getName());
        }
    }
}
 

事务与mybatis整合

注意:整合时,事务管理器用的dataSource必须与sqlSessionFactory一样(与mybatis整合时);

其实这个整合和mybatis与spring的整合差不多。只是添加了事务而已。但是他们不会相互影响。

applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="mapperLocations" value="classpath*:*Mapper.xml"/>
    </bean>
    <mybatis:scan base-package="com.dao" />
    <bean id="manager" class="com.service.XXXManager" autowire="byType"></bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice id="txAdvisor">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="servicePointcut"
                      expression="execution(* com.service..*.*(..))"/>
        <aop:advisor advice-ref="txAdvisor" pointcut-ref="servicePointcut"/>
    </aop:config>
</beans>
 

其他都是没什么变化的,具体的可以去看上面的mybatis与spring的整合。

 

posted on 2019-10-31 09:56  不希望有明天  阅读(197)  评论(0编辑  收藏  举报