Spring

什么是Spring?

1.Spring 是一个开源框架。

2.Spring是一个业务逻辑层框架。

2.Spring 是一个 IOC(DI) 和 AOP 容器框架。

3.Spring的优良特性:

  1. 1.非侵入性(轻量级):基于Spring开发的应用中的对象可以不依赖于SpringAPI
  2. 2.依赖注入:**DI——反转控制(IOC)最经典的实现**。 IOC 是思想,DI是实现。
  3. 3.面向切面编程:**AOP**。
  4. 4.容器:Spring是一个容器,因为它**包含并且管理应用对象的生命周期**。
  5. 5.组件化:Spring实现了使用简单的组件配置合成一个复杂的应用。可以使用XMLJava注解组合这些对象。
  6. 6.一站式:在IOCAOP的基础上可以整合各种第三方的类库。(实际上Spring自身也提供了 表述层SpringMVC 持久层SpringJDBC)

Spring底层使用的反射获取对象。

搭建Spring

1.加入jar包:

日志jar包:

    commons-logging-1.1.1.jar

Spring自身jar包:

    spring-beans-4.0.0.RELEASE.jar<br />
    spring-context-4.0.0.RELEASE.jar<br />
    spring-core-4.0.0.RELEASE.jar<br />
    spring-expression-4.0.0.RELEASE.jar

2.如果开发工具自身创建了 Spring配置文件就无需创建,如果没有则自己创建(Spring Config XML)

Spring简单案例

    1..创建 Spring Config XML 文件,文件名:applicationContext.xml   (默认文件名)

applicationContext.xml:

<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里的命名空间:规定当前XML 里能写什么,不能写什么-->

        <!--bean:定义Spring所管理的一个对象 
             class :指明对象的所属类(全限定名,全类名)  
            id:唯一标识Bean(对象),不能重复。在通过类型获取bean的过程中可以不设置
            根据class属性可知,Spring底层就是使用反射获取的对象-->
        <bean class="com.guolian.Spring.mod.Person" id="person">
                <!--property:为对象的某个属性赋值   
                    name:属性名   
                    value:属性值-->
                <property name="id" value="111"></property>
                <property name="name" value="guolian"></property>
        </bean>
        <!--如果有多个Person对象,则必须给id赋值,不然会报错-->
        <bean class="com.guolian.Spring.mod.Person" id="person2">
                <!--property:设置属性   name:属性名   value:属性值-->
                <property name="id" value="222"></property>
                <property name="name" value="郭联"></property>
        </bean>
</beans>
    2.在 java里使用:
public class TestBySpring {
    public static void main(String[] args){
         //1.初始化容器
        ApplicationContext ac =
                new ClassPathXmlApplicationContext("applicationContext.xml");

        //2.通过getBean()获取对象
        //通过id获取bean
//        Person person = (Person) ac.getBean("person");

        //通过类型获取bean
        //使用此方法获取对象时,要求Spring所管理的此类型的对象只能有一个
//        Person person = ac.getBean(Person.class);

        //通过id和类型获取bean
        //尽可能的使用这个重载方法。可以更准确的获取到对象。
        Person person = ac.getBean("person", Person.class);
        System.out.println(person);
    }
}

IOC容器和Bean的配置

1.IOC和DI:

IOC:反转控制(控制反转)

反转控制:把程序员对 对象的控制权,反转给程序本身,让程序自己管理对象

反转了资源的获取方向:该由容器主动将资源推送给需要的组件。

开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可。

DI:依赖注入

把具有依赖关系的属性进行赋值。

IOC的另外一种表述方式:即组件以一些预先定义好的方式,接受来自于容器的资源注入。

**IOC就是一种反转控制的思想,而DI是对IOC的一种具体实现**。

IOC容器在Spring中的实现

Spring中有IOC思想,而IOC思想必须基于IOC容器来完成。而IOC容器在最底层实质上就是一个对象工厂

Spring提供了IOC容器的两种实现方式:

    1.BeanFactory:IOC容器的基本实现,面向Spring本身,不提供开发人员使用。

    2.ApplicationContext:BeanFactory的子接口,提供了更多高级特性,面向Spring的使用者。

ApplicationContext的主要实现类:
    ClassPathXMLApplicationContext:对应类路径下的XML格式的配置文件(写相对路径)

    FileSystemXMLApplicationContext:对应文件系统中XML格式的配置文件(写绝对路径)

    在初始化时就创建单例的bean,可以通过配置的方式指定创建多实例的bean。

2.给bean的属性赋值:

Spring默认bean 是单例的。

2.1依赖注入的方式
        <!--
        scope:设置设计模式(单例、多例、properties)
        index:注入值在形参里的索引  
        ref:引用当前Spring里一个对象的id 
        type:注入值的类型
        name:参数名   
        value:注入值-->

2.1.1通过bean的setXXX()方法赋值
<bean id="s1" class="com.guolian.di.Student">
    <!--它们底层就是使用的bean类里的setXXX()方法赋值-->
    <property name="id" value="10000"></property>
    <property name="sex" value="男"></property>
    <property name="name" value="郭联"></property>
    <property name="age" value="19"></property>
    <!--ref:引用,引用当前Spring所管理的一个bean对象的id-->
    <property name="teacher" ref="t1"></property>
</bean>

2.1.2通过bean的构造器赋值
    <bean id="s2" class="com.guolian.di.Student">
        <!-- 自动匹配到类中相对应的构造方法 -->
        <constructor-arg value="10086"></constructor-arg>
        <constructor-arg value="泽野弘之"></constructor-arg>
        <!--指定这个参数,是第3个形参的值,类型是Integer,可以用于指定相应的构造器-->
        <constructor-arg value="30" index="2" type="java.lang.Integer">
        </constructor-arg>
        <constructor-arg value="男"></constructor-arg>
    </bean>

2.2P命名空间

需要在 beans里额外加入这两行代码引入p命名空间,context必须在p前面

   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:p="http://www.springframework.org/schema/p"
<?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:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="s3" class="com.guolian.di.Student" p:id="10033" p:name="张三" p:age="35" p:sex="男" p:teacher-ref="t1">
    </bean>
</beans>

2.3可以注入的值

2.3.1字面量
    1.可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
       value子节点: <property name="id">
                            <value>10010</value>
                    </property>
        value属性:<property name="sex" value="男"></property>

    2.基本数据类型及其封装类、String等类型,都可以使用字面量注入的方式

    3.若字面值中包含特殊字符,可以使用<![CDATA[]]> 把字面值包裹起来

2.2.2 null

2.2.3给bean的级联属性赋值
<bean id="s4" class="com.guolian.di.Student">
    <property name="name" value="法外狂徒"></property>
    <property name="age" value="30"></property>
    <!--获得 teacher类的对象-->
    <property name="teacher" ref="t1"></property>
    <!--给teacher 里的 name 属性赋值-->
    <property name="teacher.name" value="小红"></property>
</bean>

2.2.4 引用其他bean
<!--通过 ref 属性 获取其他的bean。 里面写其他bean的id-->
<property name="teacher" ref="t1"></property>

2.2.5内部bean

在谁的内部定义的bean,它就只属于谁,在其他的bean里引用不到

<bean id="s5" class="com.guolian.di.Student">
    <property name="id" value="16666"></property>
    <property name="sex" value="男"></property>
    <property name="name" value="s5永远的18岁"></property>
    <property name="age" value="18"></property>
    <property name="teacher">
        <!--在谁的内部定义的bean,它就只属于谁,在其他的bean里引用不到-->
        <bean id="st1" class="com.guolian.di.Teacher">
            <property name="tid" value="22222"></property>
            <property name="name" value="admin"></property>
        </bean>
    </property>
</bean>

2.4集合属性

在Spring中可以通过一组内置的XML标签来配置集合属性。例如:、或

数组的定义和list一样,都使用 标签,也可以使用 标签

2.4.1 list
<!--List 里写入字面量-->
<bean id="t2" class="com.guolian.di.Teacher">
    <property name="tid" value="100006"></property>
    <property name="name" value="老师二号"></property>
    <property name="cls">
        <list>
            <value>a</value>
            <value>b</value>
            <value>c</value>
        </list>
    </property>
</bean>

<!--List 里 写入 bean-->
  <bean id="t3" class="com.guolian.di.Teacher">
        <property name="tid" value="100006"></property>
        <property name="name" value="老师二号"></property>
        <property name="students">
            <list>
                <ref bean="s1"></ref>
                <ref bean="s2"></ref>
                <ref bean="s3"></ref>
            </list>
        </property>
    </bean>

2.4.2 map
    <bean id="t4" class="com.guolian.di.Teacher">
        <property name="tid" value="100124"></property>
        <property name="name" value="老师四号"></property>
        <property name="bossMap">
            <map>
                <entry key="10001" value="领导一号"></entry>
                <entry key="10002" value="领导二号"></entry>
            </map>
        </property>
    </bean>

2.4.3 集合类型的bean
<bean id="t5" class="com.guolian.di.Teacher">
    <property name="tid" value="100125"></property>
    <property name="name" value="老师五号"></property>
    <property name="students" ref="students"></property>
</bean>

<util:list id="students" >
        <ref bean="s1"></ref>
        <ref bean="s2"></ref>
        <ref bean="s3"></ref>
        <ref bean="s4"></ref>
</util:list>

<util:map id="map">
      <entry key="1" value="张三"></entry>
</util:map>

2.5 FactoryBean

Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean。

工厂Bean:

    帮忙创建对象的对象。

    创建一个类实现FactoryBean,当我们获取它的对象时,获取的是它getObject()里返回的对象,而不是它本身的对象。

工厂Bean:
public class MyFactory implements FactoryBean<Car>{
    @Override
    public Car getObject() throws Exception {
        Car car = new Car();
        car.setBrand("奥迪");
        car.setPrice(20000.00);
        return car;
    }

    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

配置工厂Bean
<bean id="factory" class="com.guolian.Spring.factorybean.MyFactory"></bean>

测试:
public class Test {
    public static void main(String[] args){
        ClassPathXmlApplicationContext classPathXmlApplicationContext =
                new ClassPathXmlApplicationContext("factorybean.xml");

        Object factory = classPathXmlApplicationContext.getBean("factory");

        System.out.println(factory);
        //输出的 Car的对象,而不是MyFactoy的对象
    }
}

2.6 Bean的作用域

Spring 所创建的bean 默认是单例。可以通过 scope 属性更改。prototype:多例。

若Spring中有单例模式的Bean,则当Spring容器初始化时,就会创建单例bean的对象

而多例(原型)bean,会在使用时创建其对象

<bean id="s1" class="com.guolian.Spring.scope.Student" scope="prototype">
    <property name="sid" value="1001"></property>
    <property name="sname" value="张三"></property>
</bean>

2.7 Bean的生命周期

Spring IOC容器对bean 的生命周期进行管理的过程:

1.通过构造器或工厂方法创建bean 实例。

2.为bean的属性设置值和对其他bean的引用(依赖注入)

3.调用bean的初始化方法

4.bean可以使用了

5.当容器关闭时,调用bean的销毁方法
<!--init-method 和 destroy-method 定义了当前对象初始化和销毁时被调用的方法。
    需要在类里创建两个方法,名字随意。-->
<bean id="person" class="com.guolian.Spring.IOC.life.Person" init-method="init" destroy-method="destory">
    <property name="id" value="10001"></property>
    <property name="sex" value="男"></property>
    <property name="name" value="张三"></property>
</bean>
public class Test {
    public static void main(String[] args){
        ClassPathXmlApplicationContext ac =
                new ClassPathXmlApplicationContext("spring-beanlife.xml");

        Person person = ac.getBean("person", Person.class);

        System.out.println(person);
        //关闭容器,调用销毁方法。
        ac.close();
    }
}

2.7.2bean的后置处理器

1.bean的后置处理器允许在调用初始化方法前后对bean进行额外的处理。

2.bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例

3.bean的后置处理器需要实现接口:BeanPostProcessor,在初始化方法调用前后,Spring将把每个bean实例分别传递给其中实现的两个方法。

只要在XML配置文件里加入了后置处理器,它就会自动的对当前页面内的每一个bean进行处理

<bean class="com.guolian.Spring.IOC.life.AfterHandler"></bean>
public class AfterHandler implements BeanPostProcessor {
    @Override
    //在初始化之前对bean进行操作
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //因为Spring里就一个 person类的bean,所以强转一下。
        Person person = (Person) bean;
        if (person.getSex().equals("男")){
            person.setName("周杰伦");
        }else {
            person.setName("宋祖英");
        }
        //返回处理之后的新的bean
        return person;
    }

    @Override
    //初始化之后对bean进行操作。
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //没有进行任何操作,返回传入的bean。
        return bean;
    }
}

2.8引用外部属性文件

当bean的配置文件过多时,可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用它

引用外部文件数据固定格式:${}

两种方式选一种即可:

<!--引用外部配置文件-->
<!--方式1:使用bean标签引入外部配置文件-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="com/guolian/Spring/datasource/db.properties"></property>
</bean>

<!--方式2:使用context标签引入外部配置文件-->
<context:property-placeholder location="com/guolian/Spring/datasource/db.properties"></context:property-placeholder>

<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
    <!--引用db.properties 文件里的数据-->
    <!--引用外部文件数据固定格式:${}-->
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

2.9自动装配和兼容性

自动装配:自动注入

手动装配:以value或ref的方式明确指定属性值

自动装配:根据指定的装配规则,不需要明确指定。Spring自动将匹配的属性值注入bean中。

2.9.1autowire:
设置自动装配<br />
    autowire="byName" 根据**_属性名称和bean的id_**自动装配   (bean的id跟属性名一致,就会自动赋值)

autowire="byType" 根据**_属性类型和bean的类型_**自动装配   (bean的类型跟属性的类型**兼容**,就会自动赋值)          byType要求:**Spring容器中只能有一个能为属性赋值的bean**。

选用建议: 只要设置了autowire属性,会作用于该bean 中所有的非字面量值。

            因此,谁都不用。

2.9.2兼容性
如果使用 byType 自动装配,那么它们**不仅仅只能给相同的类型赋值**。**还能给它们的父类或实现的接口类赋值**。这就是兼容性。
    <!-- autowire:设置自动装配
          autowire="byName" 根据名称(name)自动装配(bean的id跟属性名一致,就会自动赋值)         -->
    <bean id="emp" class="com.guolian.Spring.IOC.auto.Emp" autowire="byName">
        <property name="eid" value="1001"></property>
        <property name="ename" value="张三"></property>
    </bean>

    <bean id="car" class="com.guolian.Spring.IOC.auto.Car">
        <property name="cid" value="666"></property>
        <property name="cname" value="霸道"></property>
    </bean>

    <bean id="dept" class="com.guolian.Spring.IOC.auto.Dept">
        <property name="did" value="111111"></property>
        <property name="dname" value="开发部"></property>
    </bean>


    <!-- autowire="byType" 根据类型自动装配(bean的类型跟属性的类型一致,就会自动赋值)
        限制条件:当前配置页面内,不能有相同的类型
         兼容性:这些类还可以为它们的父类或实现的接口类赋值-->
    <bean id="emp2" class="com.guolian.Spring.IOC.auto.Emp" autowire="byType">
        <property name="eid" value="1001"></property>
        <property name="ename" value="张三"></property>
    </bean>

    <bean id="car2" class="com.guolian.Spring.IOC.auto.Car">
        <property name="cid" value="666"></property>
        <property name="cname" value="霸道"></property>
    </bean>

    <bean id="dept2" class="com.guolian.Spring.IOC.auto.Dept">
        <property name="did" value="111111"></property>
        <property name="dname" value="开发部"></property>
    </bean>

2.10 通过注解配置bean

注解的配置相对于XML 更加的简洁和优雅。但是后期修改更新不容易。

2.10.1使用注解标识组件

1.普通组件:@Component

        标识一个受Spring IOC 容器管理的组件

2.持久化层组件:@Repository

        标识一个受Spring IOC 容器管理的持久化层组件

3.业务逻辑层组件:@Service

        标识一个受Spring IOC 容器管理的业务逻辑层组件

4.表述层控制器(servlet)组件:@Controller

        标识一个受Spring IOC 容器管理的表述层控制器组件

5.context:component-scan:扫描组件,加载注解,在Spring生成对应的Bean,
这些bean 的id 会以类的首字母小写为值

6.以上注解都可以加上:@Component(value = “userController”),自定义加载到Spring里的id。

@Component(value = “userController”) = @Component( “userController”)

两步走:1.在Java类中加上对应注解 2.在Spring配置文件中扫描组件

@Service
public class UserServiceImpl implements UserService{}

@Repository
public class UserDAOImpl implements UserDAO {}

@Controller
public class UserController {}
    <!--扫描指定的 包package ,加载组件-->
    <!--context:component-scan:扫描组件,加载注解,在Spring生成对应的Bean,
    这些bean 的id 会以类的首字母小写为值-->
    <!--组件:指Spring 中管理的Bean-->

    <context:component-scan base-package="com.guolian.Spring2.annotate"></context:component-scan>

2.10.2扫描组件时的包含和排除

默认的过滤器:扫描所有类。

包含要将默认的过滤器关闭:关闭过滤器,然后再放进来,增加效率。

排除要将默认的过滤器打开:只有先扫描了所有的类,才能进行排除。

包含:

<!--use-default-filters="false":将默认的过滤器关闭,不然将使用默认的过滤-->
<context:component-scan base-package="com.guolian.Spring2.annotate" use-default-filters="false">

    <!--context:include-filter:除了指定的类,其他的都过滤掉-->


    <!-- annotation :根据注解包含, 后面写注解所在类的全类名-->
    <context:include-filter type="annotation" 
                            expression="org.springframework.stereotype.Controller">        </context:include-filter>


    <!-- assignable:根据类型包含 ,后面写要包含类的全类名-->
    <context:include-filter type="assignable"
                expression="com.guolian.Spring2.annotate.service.UserServiceImpl">    </context:include-filter>

</context:component-scan>

排除:

    <!--use-default-filters="true":将默认的过滤器开启,扫描所有的类-->
    <context:component-scan base-package="com.guolian.Spring2.annotate" use-default-filters="true">

        <!--context:exclude-filter:过滤掉指定的类-->

        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"></context:exclude-filter>
        <context:exclude-filter expression="com.guolian.Spring2.annotate.service.UserServiceImpl" type="assignable"></context:exclude-filter>

    </context:component-scan>

2.10.3基于注解的自动装配

1.@Autowired 会默认使用:byType 如果byType装配不了,就会使用 byName

在需要装配的变量上,加上注解 @Autowired,然后在 Spring扫描组件,装配到该变量。

2.@Qualifier(value = “要装配的bean的id”):限定符,如果有多个bean可以装配到该变量,则使用限定符限定,只让某一个bean装配该属性。

需要先装配,再限定。

Spring会自动的扫描组件,用相应的类型去装配属性

public class UserController {
    //在Spring IOC 容器中扫描于 UserServiceImpl相匹配的bean,然后装配该变量
    @Autowired
    @Qualifier(value = "userServiceImpl")
    private UserServiceImpl userService;
    @Autowired
    private UserServiceImpl userService1;
    }

public class UserServiceImpl implements UserService{
    //在Spring IOC 容器中扫描于 UserDAOImpl相匹配的bean,然后装配该变量
    @Autowired
    private UserDAOImpl userDAO;
}

AOP前奏

日志:在程序执行期间追踪正在发生的功能。(向控制台输出一条相应的信息)

3.1动态代理

实现的 jdk动态代理,依赖于接口。

还有一种CGLIB动态代理,依赖于继承。

具体实现请回顾反射。

public class ProxyUtil {
    public Object getProxy(Object obj){
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?>[] interfaces = obj.getClass().getInterfaces();

         //第一个参数:获取类的加载器(什么类的加载器都行,不用一定要被代理类的)
        //第二个参数:获取被代理类实现的一套或一个接口,为了了解被代理类所实现的功能。
        //第三个参数:设置代理对象如何实现目标对象的功能。实现接口InvocationHandler。
        return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            //代理对象实现功能的方式。
            //第一个参数:代理类(不使用)
            //第二个参数:被调用的方法
            //第三个参数:被调用方法的参数
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("前");

                //method.invoke :实现了方法的调用(非常重要)
                //那么我们就可以在调用所有的被代理方法前后加入一些功能
                Object invoke = method.invoke(obj, args);

                System.out.println("后");
                return invoke;
            }
        });
    }


    public static void main(String[] args){
        ProxyUtil proxyUtil = new ProxyUtil();
        MathImpl math = new MathImpl();
        //不能直接转换为被代理类:会出现类型转换异常(因为传到代理类里的是接口)
        //需要转换为被代理类实现的接口
        Math proxy = (Math) proxyUtil.getProxy(math);

        proxy.add(1,2);
    }
}

3.2AOP概述

1.AOP:面向切面编程,是一种新的方法论,是对传统OOP(面向对象编程)的补充。

    OOP:纵向继承机制  (找到对象,调用功能方法)

    AOP:横向抽取机制(把一些公共的功能,非业务的代码抽取出来)

2.AOP编程操作的主要对象是切面,而切面用于模块化横切关注点(公共功能)

3.切面:存储非业务代码和公共功能的类横切关注点:非业务代码、公共功能

4.AOP图解:AOP的实现依赖于动态代理

3.3AOP术语

1.横切关注点:

            每个方法里的   非业务代码、公共功能(日志功能、验证功能.......)。

2.切面:

            封装横切关注点的类,**每个关注点体现为一个通知方法**。

3.通知:

            切面必须完成的各个工作。(**把横切关注点放到切面,它就叫通知**)

            前置通知、后置通知、返回通知、异常通知、环绕通知(包含前四种通知)

4.目标:

            被通知的对象。(抽取出来的代码**要作用到的对象**)

5.代理:

            向目标对象应用通知之后,创建的代理对象。

6.连接点:

            横切关注点在程序代码中的具体体现,对应目标程序执行的某个特定位置。(如:方法调用前后、异常捕获之后、finally)

7.切入点:

            定位连接点的方式。AOP 可以通过切入点定位到特定的连接点。

            只有写了切入点,切面才能找到它要作用的位置。

            切点通过 **org.springframework.aop.Pointcut; 接口进行描述**,它使用**类和方法作为连接点的查询条件**。

    **切入点表达式**:execution(public int 切入类的全类名.方法(int,int))

8.图解:

3.4AspectJ

    AspectJ:Java社区里最完整最流行的**AOP框架**。它不属于Spring

    Spring2.0以上:可以使用基于 AspectJ 注解或XML配置的AOP。

3.4.1在Spring启用AspetJ注解
    1.导入Jar包

    com.springsource.net.sf.cglib-2.2.0.jar(动态代理的模式,如果jdk不行可以使用CGLIB)

    com.springsource.org.aopalliance-1.0.0.jar

    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

    spring-aop-4.0.0.RELEASE.jar

    spring-aspects-4.0.0.RELEASE.jar

3.4.2简单使用AspectJ

AspectJ的自动代理底层使用的是:动态代理

AOP 依赖于 IOC 。所以使用AOP的注解时,要在上面加上IOC的组件注解,告诉IOC容器加载它们

@Component
@Aspect
//标注当前类为切面
public class MyloggerAspect {}

1.需要在配置文件中配置 组件扫描。和开启 AspectJ的自动代理。

2.在切面上定义注解(组件注解和切面注解)

3.在切面中定义通知。并在通知上加上注解,并定义其切入点,告诉IOC容器这是一个通知。

4.切入点:@Befor(value=”public int 切入类的全类名.切入方法(形参列表)”)

5.AOP底层使用的动态代理,所以在获取bean时,类型需要用其实现的接口类型

XML 配置:
    <!--开启AspectJ的自动代理功能 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!--扫描组件,加载注解-->
    <context:component-scan base-package="com.guolian.Spring2.AOP.AspectJ"></context:component-scan>

切面类:
/**
 * 切面:存储横切关注点的类
 */
@Component
@Aspect
//标注当前类为切面
public class MyloggerAspect {
    //通知

    /**
     * @Before:将方法指定为前置通知
     * 必须设置 value  其值为切入点表达式
     */

    @Before(value = "execution(public int com.guolian.Spring2.AOP.AspectJ.MathImpl.add(int,int))")
    private void beforeMethod(){
        System.out.println("这是一个前置通知");
    }
}

目标类:
@Component
public class MathImpl implements Math {......}

测试类:
public class Test {
    public static void main(String[] args){
        ClassPathXmlApplicationContext ac =
                new ClassPathXmlApplicationContext("com/guolian/Spring2/AOP/AspectJ/aop-aspectj.xml");

        //因为AOP底层是使用的动态代理,所以需要用接口类,而不是实现类。
        Math mathImpl = ac.getBean("mathImpl", Math.class);

        int add = mathImpl.add(1, 2);
        System.out.println(add);
    }
}

3.4.3前置通知

@Before

作用于:方法执行之前

JoinPonint :连接点。

@Before(value = "execution(public int com.guolian.Spring2.AOP.AspectJ.MathImpl.add(int,int))")
private void beforeMethod(JoinPoint joinPoint){
    //JoinPoint 连接点
    //获取方法的参数
    Object[] args = joinPoint.getArgs();
    //getSingnature :签名,对一些名字的封装
    //获取方法名
    String name = joinPoint.getSignature().getName();
    System.out.println("method:"+name+",arguments:"+ Arrays.toString(args));
}

3.4.4切入点表达式
@Before(value = "execution(public int com.guolian.Spring2.AOP.AspectJ.MathImpl.add(int,int))")
//  标准的切入点表达式:  访问修饰符  返回值   全类名.方法名(形参列表)



@Before(value = "execution(* com.guolian.Spring2.AOP.AspectJ.*.*(..))")
//第一个*  :代表任意的  访问修饰符和返回值
//第二个*  :代表任意的  AspectJ下的类
//第三个*  :代表任意的  AspectJ下的方法
//(..)    :代表任意的  形参列表

3.4.5后置通知

@After

作用于:方法的 finally 语句块,即不管方法有没有异常,都会执行

    @After(value = "execution(* com.guolian.Spring2.AOP.AspectJ.*.*(..))")
    private void after(JoinPoint joinPoint){
        System.out.println("后置通知");
    }
}

3.4.6返回通知

@AfterReturning 将方法标注为返回通知。

作用于:方法执行之后。相当于:写在了try 代码块的最后一行。如果出现异常,则不执行

returning = “” :设置接收方法返回值的变量名。类型为Object

要想在方法中使用返回值,必须在方法的形参中设置和变量名相同的参数名。

//returning = "result" :定义一个Object类型的参数名,接收方法的返回值
@AfterReturning(value = "execution(* com.guolian.Spring2.AOP.AspectJ.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
    //将注解里,接收的返回值写入形参列表,供方法使用
    //获取方法名
    String name = joinPoint.getSignature().getName();

    System.out.println("method:"+name+",result:"+result);
}

3.4.7异常通知

@AfterThrowing

/**
 *
 * @AfterThrowing: 将方法标注为 异常通知(例外通知)
 * 作用于:方法抛出异常时  相当于:写在了catch 代码块里。
 * 通过  throwing  接收方法返回的异常信息
 * 在形参列表中,可通过  具体的异常类型  对指定的异常信息进行操作
 */
@AfterThrowing(value = "execution(* com.guolian.Spring2.AOP.AspectJ.*.*(..))",throwing = "ex")
public void afterthrowingMethod(Exception ex){
    //可以根据形参列表,处理指定的异常
    System.out.println("有异常了,messages:"+ex);
}

3.4.8环绕通知

@Around

环绕通知 与 动态代理 类似。

@Around(value = "execution(* com.guolian.Spring2.AOP.AspectJ.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
    Object result = null;
    try {
        //前置通知
        System.out.println("前置通知");
        //执行方法:相当于代理里的 method.invoke()
        result = joinPoint.proceed();
        //返回通知
        System.out.println("返回通知");
        //返回方法执行结果。
        return result;
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        //异常通知
        System.out.println("异常通知");
    }finally {
        //后置通知
        System.out.println("后置通知");
    }
    return -1;

}

3.4.9公共切入点

重用切入点

@Pointcut(value = "execution(* com.guolian.Spring2.AOP.AspectJ.*.*(..))")
public void test(){ }

//引用了 test() 类的 切入点
 @Around(value = "test()")
public Object aroundMethod(ProceedingJoinPoint joinPoint){}

    @After(value = "test()")
    private void after(JoinPoint joinPoint){}

    @AfterThrowing(value = "test()",throwing = "ex")
    public void afterthrowingMethod(Exception ex){}

3.4.10切面优先级

切面的优先级:确定哪个切面先执行

value 里放一个整型,默认值为 int 的最大值。不设置时,先定义的切面先执行。

值越小,优先级越高,越先执行。

//定义切面的优先级
@Order(value = 100)

@Order(value = 1)
//1 比 100 先执行

3.4.10 使用XML配置AOP

使用Spring 原生的 XML 配置AOP

<!--扫描i组件-->
<context:component-scan base-package="com.guolian.Spring2.AOP.xml"></context:component-scan>
<!--配置AOP-->
<aop:config>
    <!--配置切面-->
    <aop:aspect ref="myLogger">
    <!--配置通知的位置和切入点表达式-->

        <!--方式一:先创建一个切入点表达式,然后引用-->
        <aop:pointcut id="cut" expression="execution(* com.guolian.Spring2.AOP.xml.*.*(..))"></aop:pointcut>
        <aop:before method="before" pointcut-ref="cut"></aop:before>
        <!--方式二:直接在方法配置里写切入点表达式-->
        <aop:before method="before" pointcut="execution(* com.guolian.Spring2.AOP.xml.*.*(..))"></aop:before>
    </aop:aspect>
</aop:config>
@Component
public class MyLogger {}

JdbcTemplate

为了使JDBC更易于使用,Spring在 JDBC APL上定义了一个抽象层,以此建立一个JDBC存取框架。

可以将 Sping 的 JdbcTemplate 看作是一个小型的轻量级持久化框架,和之前使用的 DBUtils 很相似

4.环境准备

4.1导入 Jar包

4.1.1IOC容器所需jar包:
    commons-logging-1.1.1.jar:

    spring-beans-4.0.0.RELEASE.jar    beans:配置访问文件、创建和管理bean

    spring-context-4.0.0.RELEASE.jar    context:Spring的上下文,即IOC容器。

    spring-core-4.0.0.RELEASE.jar        core:Spring框架基本的核心工具类

    spring-expression-4.0.0.RELEASE.jar    expression:表达式解析语言

4.1.2JdbcTemplate所需jar包:
    spring-jdbc-4.0.0.RELEASE.jar         jdbc:封装的jdbc

    spring-orm-4.0.0.RELEASE.jar     orm:对象关系映射

    spring-tx-4.0.0.RELEASE.jar    tx:事务

4.1.3数据库驱动和数据源jar包:
    druid-1.1.9.jar       数据库连接池

    mysql-connector-java-5.1.7-bin.jar    mysql 的驱动

4.2增删改查

重点:
在 sql语句中使用通配符:?。

若传过去的是一个字符串,则自动为 这个字符串加   ‘  ’(单引号)。

有两种sql语句不能用通配符赋值:

        第一种: where ...   in(?)。  (in 里不能用?。in('1,2,3,')  只会执行1)。
若使用通配符,sql中执行的语句就是:DELETE FROM emp WHERE id IN('3,4,5');
#只会删除 id为3的数据
        第二种: 模糊查询。模糊查询时,通配符会自动为其加上   单引号。
若使用通配符,sql中执行的语句就是:SELECT id FROM emp WHERE `name` LIKE '%'a'%' ;
#语句直接报错。

xml配置:
<!--引入属性文件-->
<context:property-placeholder location="com/guolian/Spring2/template/jdbc/mysql.properties"></context:property-placeholder>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${mysqlDriver}"></property>
    <property name="url" value="${mysqlUrl}"></property>
    <property name="username" value="${mysqlUsername}"></property>
    <property name="password" value="${mysqlPassword}"></property>
</bean>

<!--通过数据源配置 JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
</bean>

4.2.1update

单条数据增删改

public class TestJdbcTeamplate {

    ClassPathXmlApplicationContext ac =
            new ClassPathXmlApplicationContext("com/guolian/Spring2/template/jdbc/jdbc.xml");
    //获取配置好的 jdbcTemplate
    JdbcTemplate jdbcTeamplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);

    @Test
    public void testUpdate(){
        //直接使用固定的 sql 语句
//        jdbcTeamplate.update("INSERT INTO emp VALUES(null,'张三',23,'男')");
        //使用预编译
/*        String sql = "insert into emp values(null,?,?,?)";
        jdbcTeamplate.update(sql,"李四",25,"男");*/

        //在in 里不能使用 通配符,否则只会执行第一个id的删除。
        String ids = "2,3";
        String sql = "delete from emp where id in("+ ids +")";
        jdbcTeamplate.update(sql);
    }
}

4.2.2batchUpdate

批量增删改

@Test
public void testBatchUpdate(){
    String sql = "insert into emp values(null,?,?,?)";
    //一个value 就是一个数组。
    //所有数组组成一个集合。就是批量操作。
    List<Object[]> list = new ArrayList<>();
    list.add(new Object[]{"a1",1,"男"});
    list.add(new Object[]{"a2",2,"男"});
    list.add(new Object[]{"a3",3,"男"});
    jdbcTeamplate.batchUpdate(sql,list);
}

4.2.3QueryForObject

查询单个数据或单条数据。

rowMapper:行的映射

    @Test
    public void testQueryForObject(){
        //用来获取单个的值
//        jdbcTeamplate.queryForObject(sql,Class);
        //用来获取单条数据
//        jdbcTeamplate.queryForObject(sql,rowMapper);

        String sql = "select id,`name`,age,sex from emp where id=?";
        //将字段名与属性名进行映射
        RowMapper<Emp> rowMapper = new BeanPropertyRowMapper<>(Emp.class);
        Emp emp = jdbcTeamplate.queryForObject(sql, new Object[]{6}, rowMapper);
        System.out.println(emp);

        //获取单个数据
        String sql2 = "select count(*) from emp";
        Integer integer = jdbcTeamplate.queryForObject(sql2, Integer.class);
        System.out.println(integer);
    }

4.2.4Query

查询所有数据。

@Test
public void testQuery(){
    String sql = "select id,name,sex,age from emp";
    RowMapper<Emp> rowMapper = new BeanPropertyRowMapper<>(Emp.class);
    List<Emp> query = jdbcTeamplate.query(sql, rowMapper);
    for (Emp emp : query){
        System.out.println(emp);
    }
}

声明式事务管理

使用原生的JDBC API 实现事务管理,是所有事务管理的基石。

5.1简单配置执行事务

5.1.1XML代码
    <context:component-scan base-package="com.guolian.Spring2.book"></context:component-scan>
    <!--引入属性文件-->
    <context:property-placeholder location="com/guolian/Spring2/book/mysql.properties"></context:property-placeholder>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${mysqlDriver}"></property>
        <property name="url" value="${mysqlUrl}"></property>
        <property name="username" value="${mysqlUsername}"></property>
        <property name="password" value="${mysqlPassword}"></property>
    </bean>

    <!--通过数据源配置 JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


<!--配置事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--事务是针对于 连接对象 的。由连接对象(Connection) 关闭自动提交、执行手动提交和回滚。-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--开启注解驱动:扫描并解析与事务有关的注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>

5.1.2Java代码

@Transactional :对方法中所有的操作作为一个事务管理

//为当前方法执行事务管理,若方法出错则回滚整个方法。
//若标注在类上,则对整个类有作用。
@Transactional
public void buyBook(String bid, String uid) {
    Integer privce = dao.selectPrice(bid);
    dao.updatest(bid);
    dao.updateBalance(uid,privce);
}

5.2事务

5.2.1@Transactional的属性

若在方法和类上都有 @Transactional 且它们所定义的属性一样则就近原则

若定义的属性不一样,则叠加

propagation:
    **事务的传播级别(传播行为):**<br />
            A方法和B方法都有事务,当A在调用B时,会将A中的事务传播给B方法。
    @Transactional(propagation = ...)

        //Propagation.REQUIRED:必须使用调用者的事务

        //Propagation.REQUIRES_NEW:将调用者的事务挂起,使用新的(被调用者)事务进行处理。

isolation:
    **事务的隔离级别**:

                并发情况下,操作数据的一种规定。
        读未提交:脏读、不可重复读、幻读。1
        读已提交:不可重复读、幻读。2
        可重复读:幻读。4
        串行化:线程问题都解决,但效率低下。8
        @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认隔离级别


        不设置时:默认和数据库的隔离级别一样。
            MySQL:默认可重复读。

timeout:
    **超时**:

        在事务强制回滚前,最多可以执行(等待)的时间**。
@Transactional(timeout = 3)//若该方法的执行(等待)时间超过3秒,则回滚整个事务

readOnly:
    **只读:**

        设置当前事务中的一系列操作是否为只读。

        设置为只读之后:Spring会通知MySQL,这个方法是用来读数据的,不用加锁。提高性能。
@Transactional(readOnly = false)//不为只读
@Transactional(readOnly = true)//为只读

rollbackFor :
    **设置因为什么异常而回滚**
@Transactional(rollbackFor = {MyException.class,NullPointerException.class})
//设置该方法:因为 MyException(自定义异常)和空指针异常  而回滚
//如果没有遇到这些异常,则不回滚。

norollbackFor :
    **设置不因为什么异常而回滚**
@Transactional(noRollbackFor =  {MyException.class,NullPointerException.class}   )
//与 rollbackFor 相反。

5.2.2使用XML的方式配置事务

不管是使用注解方式,还是使用XML的方式配置事务,都一定要有事务管理器

思路:将事务管理器看作是一个 切面(将一些方法里的事务横向抽取出来了)。

要配置的事务就是这个切面的通知。(也就是说,我们需要为事务配置切入表达式)

**并且将 事务 全部交给事务管理器来管理**。
    <!--配置事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--事务是针对于 连接对象 的。由连接对象(Connection) 关闭自动提交、执行手动提交和回滚。-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置事务通知,并将通知交给事务管理器来管理。-->
    <tx:advice transaction-manager="dataSourceTransactionManager" id="advice">
        <tx:attributes>
            <!--在设置好的切入点表达式下再次进行事务设置,看哪些方法需要被事务管理-->
            <tx:method name="buyBook"/>
            <tx:method name="checkout"/>
            <!--只有以 select 开头的方法才会被事务处理-->
            <tx:method name="select*"/>
            <!--将所有方法都管理-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--配置aop-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pointcut" expression="execution(* com.guolian.Spring2.book_xml.service.impl.*.*(..))"></aop:pointcut>
        <!--为事务通知配置切入点表达式-->
        <aop:advisor advice-ref="advice" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>