02SpringFramework

Spring总结 by Seven

  1. Spring主要包括IOC(控制反转)和AOP(面向切面编程)两部分。
  2. IOC将对象的对象的生命周期交给Spring容器管理,实现解耦合,使项目组件之间的关系松散。
  3. AOP 将程序中的交叉业务逻辑(如:开启关闭事务)提取出来, 封装成切面; 由AOP容器在适当的时机将这些切面动态地织入到具体的业务逻辑中使得程序易于扩展。
  4. Spring在具体使用的时候有编写配置文件和注解两种方式。
    一般对于自己编写的类,使用注解方式,如service层中写的serviceImpl
    对于第三方jar包中的对象创建,使用配置文件。
  5. 本文主要介绍Spring, IOC(IOC管理对象的几种方式,依赖注入), Spring中对象的生命周期, AOP(通知类型,注解版本),Spring和JDBC整合, Spring和Mybatis整合。

一, Spring框架

  • Spring框架是一个开源框架

  • 一个轻量级框架, 非侵入式的

  • 为简化企业级应用开发而生

  • Spring框架有两个核心容器: IOC, AOP

  • Spring关心对象的生命周期, 从对象的创建到销毁整个过程中的任意节点, Spring框架都可以参与

  • 依赖注入, IOC容器的功能

  • 面向切面编程, AOP容器的功能

  • 一站式, 在IOC和AOP的基础上, 可以很好的和其它第三方框架进行整合
    在这里插入图片描述

使用SpringFramework的目的: 实现实际开发的两个基本原则, 高低原则和开闭原则

  • Spring IOC容器的作用: 解耦合, 使项目组件之间的关系松散
  • Spring AOP容器的作用: 可以使程序易于扩展

二, IOC

Inversion Of Control, 控制反转

将对象的创建和管理交给SpringFramework, 由框架管理对象的生命周期


三,Hello World

  1. 引入相关依赖

    <dependencies>
        <!-- Spring核心依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.1.0.RELEASE</version>
        </dependency>
        <!-- SpringIOC容器的核心依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.1.0.RELEASE</version>
        </dependency>
        <!-- Spring上下文依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.0.RELEASE</version>
        </dependency>
    </dependencies>
    
  2. 编写java类

    public class SomeClass {
        public void doSome() {
            System.out.println("SomeClass.doSome");
        }
    }
    
  3. 编写Spring配置文件, 将java类配置给IOC容器

    <?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">
        <!-- Spring的xml配置, 文件名建议使用 applicationContext.xml -->
        <!-- 该文件遵循maven标准, 放置在resources目录下 -->
        <!-- 将java类配置给IOC容器 -->
        <!-- 通过<bean/>标签进行配置 -->
        <!-- 标签的calss属性: 指明要管理的是哪个类 -->
        <!-- 标签的id属性: ioc容器管理对象时的key -->
        <!-- id属性Spring官方称之为"bean name" -->
        <!-- bean anme指的是<bean/>标签的id属性值, 而非name属性值 -->
        <bean class="demo.SomeClass" id="someClass"/>
    </beans>
    
  4. 从容器中获取对象

    // 方式1(不建议使用)
    // Resource res = new ClassPathResource("applicationContext.xml");
    // BeanFactory factory = new XmlBeanFactory(res);
    // SomeClass som = (SomeClass) factory.getBean("someClass");
    // som.doSome();
    // 方式2
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    SomeClass some = (SomeClass) ac.getBean("someClass");
    some.doSome();
    

四,IOC管理对象的几种方式

总结:

​ 无参构造 有参构造 静态方法 实例方法 FactoryBean(万能方法)

1. 使用无参构造创建对象

  1. 编写java类

    public class SomeClass {
        public void doSome() {
            System.out.println("SomeClass.doSome");
        }
    }
    
  2. 编写Spring配置文件, 将java类配置给IOC容器

    <bean class="demo.SomeClass" id="someClass"/>
    
  3. 从容器中获取对象

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    SomeClass some = (SomeClass) ac.getBean("someClass");
    some.doSome();
    

2. 通过有参构造创建对象

  1. 编写java类

    public class SomeClass {
        public void doSome() {
            System.out.println("SomeClass.doSome");
        }
    
        public SomeClass(String name, int age) {
            System.out.println("SomeClass.SomeClass()");
            System.out.println(name + " - " + age);
        }
    
        public SomeClass(String name, int age, String address) {
            System.out.println("SomeClass.SomeClass(String name, int age, String address)");
            System.out.println(name + " - " + age + " - " + address);
        }
    }
    
  2. 编写Spring配置文件, 将java类配置给IOC容器

    <bean class="demo.SomeClass" id="someClass">
        <!-- 通过<bean/>标签的子标签<constructor-arg/> -->
        <!-- 向构造方法的参数传值 -->
        <!-- <constructor-arg/>index属性: 指明参数索引, 从0开始 -->
        <!-- <constructor-arg/>value属性: 实参(参数值) -->
        <!-- <constructor-arg/>type属性: 指明参数类型 -->
        <constructor-arg index="0" value="alice" type="java.lang.String"/>
        <constructor-arg index="1" value="22" type="int"/>
    </bean>
    
    <bean class="demo.SomeClass" id="someClass2">
        <!-- 通过<constructor-arg/>向构造方法传递参数时 -->
        <!-- 还可以通过参数名进行传递, 使用的是标签的name属性 -->
        <!-- type属性如果没有配置, 框架会自动识别数据类型 -->
        <constructor-arg name="name" value="jessica"/>
        <constructor-arg name="age" value="123"/>
        <constructor-arg name="address" value="jiang-su"/>
    </bean>
    
  3. 从容器中获取对象

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    SomeClass some = (SomeClass) ac.getBean("someClass");
    some.doSome();
    
    SomeClass some2 = (SomeClass) ac.getBean("someClass2");
    some2.doSome();
    

3. 使用静态方法创建对象

  1. 编写java类

    public class SomeClass {
        public static String getSomeStr() {
            return "SomeClass.getSomeStr()";
        }
    
        public static SomeClass getSomeObj() {
            return new SomeClass();
        }
    
        public void doSome() {
            System.out.println("SomeClass.doSome()");
        }
    }
    
  2. 编写Spring配置文件, 将java类配置给IOC容器

    <!-- factory-method属性用于指明创建对象的方法 -->
    <!-- 该方法被视为创建指定类型对象的工厂方法 -->
    <!-- factory-method所配置的目标方法, 必须是静态方法 -->
    <!-- 该bean标签所管理的对象是什么类型, 不取决于标签的class属性 -->
    <!-- 而是取决于factory-method方法的返回值类型 -->
    <bean class="demo.SomeClass" id="someStr" factory-method="getSomeStr"/>
    <bean class="demo.SomeClass" id="someObj" factory-method="getSomeObj"/>
    <bean class="java.util.Calendar" id="calendar" factory-method="getInstance"/>
    
  3. 从容器中获取对象

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Object obj = ac.getBean("someStr");
    System.out.println(obj.getClass());
    obj = ac.getBean("someObj");
    System.out.println(obj.getClass());
    obj = ac.getBean("calendar");
    System.out.println(obj.getClass());
    

4. 使用实例方法创建对象

  1. 编写java类

    public class SomeClass {
        public SomeClass() {
            System.out.println("SomeClass.SomeClass()");
        }
    
        public Date getDate() {
            System.out.println("SomeClass.getDate()");
            return new Date();
        }
    
        public Date getDate(String str) {
            try {
                return new SimpleDateFormat("yyyy-MM-dd").parse(str);
            } catch (ParseException e) {
                return null;
            }
        }
    
        public Date getDate(String str, String format) {
            try {
                return new SimpleDateFormat(format).parse(str);
            } catch (ParseException e) {
                return null;
            }
        }
    }
    
  2. 编写Spring配置文件, 将java类配置给IOC容器

    <!-- 构造方法与构造器 -->
    <!-- 构造器: 一个概念, 凡是可以获取对象的方法, 都可以被视为该对象的构造器 -->
    <!-- 构造方法属于构造器的一种 -->
    <!-- 不论构造方法, 静态方法是实例方法, 只要能创建对象 -->
    <!-- 都可以被视为对象的构造器 -->
    <!-- 在ioc配置factory-method时, 如果需要传入参数 -->
    <!-- 都通过constructor-arg子标签配置 -->
    
    <!-- 当factory-method指向是非静态方法时 -->
    <!-- 由于实例方法需要一个对象调用 -->
    <!-- 就需要给factory-method分配一个可以调用方法的bean -->
    <!-- 该bean称为factory-bean -->
    <bean class="demo.SomeClass" id="some"></bean>
    
    <bean id="date" factory-bean="some" factory-method="getDate"></bean>
    
    <bean id="date2" factory-bean="some" factory-method="getDate">
        <constructor-arg name="str" value="2019-12-10"/>
    </bean>
    
    <bean id="date3" factory-bean="some" factory-method="getDate">
        <constructor-arg name="str" value="2019/12/20"/>
        <constructor-arg name="format" value="yyyy/MM/dd"/>
    </bean>
    
  3. 从容器中获取对象

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Date date = (Date) ac.getBean("date");
    System.out.println(date);
    Date date2 = (Date) ac.getBean("date2");
    System.out.println(date2);
    Date date3 = (Date) ac.getBean("date3");
    System.out.println(date3);
    

5. FactoryBean

一种万能解决方案

FactoryBean: 一个工厂性质的bean, 用于创建对象的bean

以这种方式管理bean, 需要定义一个bean, 实现接口FactoryBean<T>

  • 无参形式

    // 定义一个bean, 该bean是作为FactoryBean
    // 泛型指明的是该FactoryBean所创建的目标对象的类型
    public class DateFactoryBean implements FactoryBean<Date> {
    
        public DateFactoryBean() {
            System.out.println("DateFactoryBean.DateFactoryBean()");
        }
    
        // 该方法返回的对象, 就是要创建的对象
        // 该方法的返回值类型, 取决于实现接口时指明的泛型
        public Date getObject() throws Exception {
            System.out.println("DateFactoryBean.getObject()");
            // return new Date();
            // return new SimpleDateFormat("").parse("");
            return Calendar.getInstance().getTime();
        }
    
        // 获取该FactoryBean所创建的目标对象的Class
        public Class<?> getObjectType() {
            return java.util.Date.class;
        }
    
        // 判断所创建的对象是否是单例对象
        // true: 单例
        // false: 非单例
        // 该判断是给Spring框架使用的
        // 框架根据判断结果决定是否创建单例对象
        public boolean isSingleton() {
            return false;
        }
    }
    
    <!-- FactoryBean类似无参构造方式创建 -->
    <bean id="date" class="demo.DateFactoryBean"/>
    <!-- Spring如何知道是否该调用getObject()方法创建对象 -->
    <!-- Spring会获取该类的接口, 看是否实现了FactoryBean接口 -->
    <!-- 如果实现了, 则会调用getObject()方法 -->
    <!-- 如果没有, 则调用该类的无参构造创建该类对象 -->
    
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Object obj = ac.getBean("date");
    System.out.println(obj.getClass());
    System.out.println(obj.hashCode());
    Object obj2 = ac.getBean("date");
    System.out.println(obj2.hashCode());
    
  • 有参形式

    public class DateFactoryBean2 implements FactoryBean<Date> {
    
        public DateFactoryBean2() {
            System.out.println("DateFactoryBean2.DateFactoryBean2()");
        }
    
        private int year;
        private int month;
        private int day;
    
        public DateFactoryBean2(int year, int month, int day) {
            System.out.println("DateFactoryBean2.DateFactoryBean2(int year, int month, int day)");
            this.year = year;
            this.month = month;
            this.day = day;
        }
    
        // 获取指定年月日的日期对象
        public Date getObject() throws Exception {
            System.out.println("DateFactoryBean2.getObject()");
            Calendar c = Calendar.getInstance();
            c.set(year, month, day);
            return c.getTime();
        }
    
        public Class<?> getObjectType() {
            return java.util.Date.class;
        }
    
        public boolean isSingleton() {
            return false;
        }
    }
    
    <bean id="date2" class="demo.DateFactoryBean2">
        <constructor-arg name="year" value="2020"/>
        <constructor-arg name="month" value="12"/>
        <constructor-arg name="day" value="23"/>
    </bean>
    
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Object obj = ac.getBean("date2");
    System.out.println(obj);
    

FactoryBean方式管理对象, FactoryBean本身也会在IOC容器中创建对象

因为FactoryBean的getObject()方法是实例方法, 需要对象才能调用

五,IOC功能之依赖注入

依赖注入: DI(Dependency Injection), 也称为"装配"

为Spring框架所管理的bean的属性注入值


1. 简单值装配

简单值: 基本类型及对应的包装类, String, Class, Resource

public class SomeClass {
    private int attrA;
    private Double attrB;
    private String attrC;
    private Class attrD;
    // getters & setters
}
<bean id="some" class="demo.SomeClass">
    <property name="attrA" value="123"/>
    <property name="attrB" value="3.14"/>
    <property name="attrC" value="abvcd"/>
    <!-- 属性D的类型是Class, 而xml文件中配置内容本质上是字符串 -->
    <!-- 这里其实装配的是某个类的完整类名字符串 -->
    <property name="attrD" value="java.lang.String"/>
</bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeClass some = (SomeClass) ac.getBean("some");
System.out.println(some.getAttrA());
System.out.println(some.getAttrB());
System.out.println(some.getAttrC());
System.out.println(some.getAttrD());

2. 集合类型的装配

该装配方式包括对数组的装配

public class SomeClass {
    private String[] arr;
    private List<String> list;
    private Set<String> set;
    private Map<String, String> map;
    private Properties prop;
    // getters & setters
}
<bean id="some" class="demo.SomeClass">
    <!-- 集合装配 -->
    <property name="arr">
        <array>
            <value>aaa</value>
            <value>bbb</value>
            <value>ccc</value>
        </array>
    </property>

    <property name="list">
        <list>
            <value>abc</value>
            <value>def</value>
            <value>ghi</value>
        </list>
    </property>

    <property name="set">
        <set>
            <value>123aaa</value>
            <value>456aaa</value>
            <value>789aaa</value>
        </set>
    </property>

    <property name="map">
        <map>
            <entry key="a" value="123"/>
            <entry key="b" value="456"/>
        </map>
    </property>

    <property name="prop">
        <props>
            <prop key="a">123</prop>
            <prop key="b">456</prop>
        </props>
    </property>
</bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeClass some = (SomeClass) ac.getBean("some");
System.out.println(Arrays.toString(some.getArr()));
System.out.println(some.getList());
System.out.println(some.getSet());
System.out.println(some.getMap());
System.out.println(some.getProp());

3. Resource类型的装配

非JDK中的类型, 是Spring提供的org.springframework.core.io.Resource

该接口有2个实现类

  • org.springframework.core.io.FileUrlResource, 可以从文件系统加载资源

  • org.springframework.core.io.ClassPathResource, 可以从类加载路径加载资源

public class SomeClass {
    private Resource res1;
    private Resource res2;
    // getters & setters
}
<bean class="demo.SomeClass" id="some">
    <property name="res1" value="file:e:/demo.txt"/>
    <property name="res2" value="classpath:demo.txt"/>
    <!-- 由于Resource接口有2个具体的实现类 -->
    <!-- 在进行装配时, 创建哪种对象, 由property标签的value属性值的标识决定 -->
    <!-- 如果是FileUrlResource对象, value中资源路径前要加上"file:"标识 -->
    <!-- 如果是ClassPathResource对象, value中资源路径前要加上"classpath:"标识 -->

    <!-- "classpath:"与"classpath*:"的区别 -->
    <!-- 相同点: 这两种标识都可以加载整个类加载路径(包括jar包中的内容)下的资源 -->
    <!-- classpath: -->
    <!-- 该标识只会返回第一个匹配到的资源, 优先查找工程中的资源文件, 再查找jar包 -->
    <!-- 如果是web工程, 运行时classpath是"/WEB-INF/classes"和"/WEB-INF/lib" -->
    <!-- 如果有同名资源, 只加载一个 -->
    <!-- classpath*: -->
    <!-- 告诉web容器, 去classpath下加载资源 -->
    <!-- 如果有同名资源, 全部加载 -->
    <!-- 注意: 非web工程, 使用"classpath*:"会获取不到资源 -->
</bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeClass some = (SomeClass) ac.getBean("some");
System.out.println(some.getRes1());
System.out.println(some.getRes2());

4. 其它bean的引用装配

public interface SomeDao {}
public class SomeDaoImpl implements SomeDao {}
public interface SomeService {
    void showSome();
}

public class SomeServiceImpl implements SomeService {

    private SomeDao someDao;

    // 要进行属性装配, 必须有set方法IOC容器才能进行装配操作
    // 原因: private修饰的成员变量并不表示属性
    // 真正表示属性的是get/set方法
    public void setSomeDao(SomeDao someDao) {
        this.someDao = someDao;
    }

    public void showSome() {
        System.out.println(someDao);
    }
}
<bean id="someDao" class="demo.SomeDaoImpl"/>

<bean id="someService" class="demo.SomeServiceImpl">
    <!-- 引用类型属性的装配, 要使用property标签的ref属性 -->
    <!-- 它表示的是对象的引用指向, 指向一个具体的bean对象 -->
    <!-- ref值是另一个bean标签的id值 -->
    <!-- ref指向的bean的类型, 必须与property标签所表示的属性类型一致 -->
    <property name="someDao" ref="someDao"/>
</bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService someService = (SomeService) ac.getBean("someService");
someService.showSome();

5. 属性编辑器

public class Address {
    private String province;
    private String city;
    // getters & setters
}
public class User {
    private String name;
    private Address address;
    // getters & setters
}
// 自定义属性编辑器类, 需要继承java.beans.PropertyEditorSupport
public class AddressPropertyEditor extends PropertyEditorSupport {
    
    // Spring的配置中, 装配的是一个字符串
    // 编辑器要进行的操作是以文本信息进行属性设置
    // 重写setAsText()方法
    // 该方法的参数text, 就是Spring配置中的装配字符串
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        // 江苏-南京
        String[] arr = text.split("-");
        Address a = new Address();
        a.setProvince(arr[0]);
        a.setCity(arr[1]);

        // 将封装好的对象, 通过setValue()方法装配到bean的属性上
        setValue(a);
    }
}
<bean class="demo.User" id="user">
    <property name="name" value="alice"/>
    <property name="address" value="江苏-南京"/>
</bean>

<!-- 编写好自定义的属性编辑器之后 -->
<!-- 要将自定义的属性编辑器配置到Spring框架中 -->
<!-- 该bean标签不需要指明id属性值 -->
<!-- 该bean标签配置的是Spring框架的配置信息类: 用户自定义编辑器信息 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <!-- CustomEditorConfigurer的属性customEditors -->
    <!-- 将用户自定义的编辑器配置到该bean的该属性上 -->
    <!-- 该属性是一个Map集合 -->
    <property name="customEditors">
        <map>
            <!-- Map的key是编辑器要转换的目标类型 -->
            <!-- value: 用户自定义的编辑器(完整类名) -->
            <!-- Spring框架在进行属性装配, 当遇到目标类型的属性时 -->
            <!-- 就会调用value所指明的编辑器进行属性编辑装配 -->
            <entry key="demo.Address" value="demo.AddressPropertyEditor"/>
        </map>
    </property>
</bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User u = (User) ac.getBean("user");
System.out.println(u.getName());
System.out.println(u.getAddress().getProvince() + " ~ " + u.getAddress().getCity());

6. 配置继承

public class Parent {
    private String name;
    private int age;
    // getters & setters
}

public class Child extends Parent {
    private String password;
    // getters & setters
}
<bean class="demo.Parent" id="parent">
    <property name="name" value="jack"/>
    <property name="age" value="20"/>
</bean>

<bean class="demo.Child" id="child">
    <property name="name" value="abc"/>
    <property name="age" value="22"/>
    <property name="password" value="123456"/>
</bean>

<!-- 该child的name属性与age属性值与parent对象的属性值相同 -->
<!-- 可以通过bean标签的parent属性让该bean配置继承另一个bean的配置 -->
<!-- 这里所说的继承是配置项与配置项之间的继承 -->
<!-- 而非java对象的继承 -->
<!-- parent属性值与被继承的bean标签的id值一致 -->
<bean class="demo.Child" id="child2" parent="parent">
    <property name="password" value="123456"/>
    <!--<property name="name" value="jack"/>-->
    <!--<property name="age" value="20"/>-->
</bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Child c = (Child) ac.getBean("child");
System.out.println(c.getName() + " - " + c.getAge() + " - " + c.getPassword());
Child c2 = (Child) ac.getBean("child2");
System.out.println(c2.getName() + " - " + c2.getAge() + " - " + c2.getPassword());

即使是没有继承关系的两个java类, 也可以使配置项发生继承关系, 只要配置项中有相同的配置内容

public class Parent {
    private String name;
    private int age;
    // getters & setters
}

public class User {
    private String name;
    private int age;
    // getters & setters
}
<bean class="demo.Parent" id="parent">
    <property name="name" value="jack"/>
    <property name="age" value="20"/>
</bean>
<!-- 发生继承关系的是Spring的两个配置项, 而非java类 -->
<bean class="demo.User" id="user" parent="parent"/>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User u = (User) ac.getBean("user");
System.out.println(u.getName() + " - " + u.getAge());

7. 配置抽象类

public abstract class Parent {
    private String name;
    private int age;
    // getters & setters
}

public class Child extends Parent {
    private String password;
    // getters & setters
}
<!-- 该bean标签所配置的类是一个抽象类, 无法创建对象 -->
<!-- 但该配置项被其它bean配置所继承, 又必须存在 -->
<!-- 需要通过bean标签的abstract属性告诉Spring该类是一个不需要创建对象的抽象类 -->
<bean class="demo.Parent" id="parent" abstract="true">
    <property name="name" value="jack"/>
    <property name="age" value="20"/>
</bean>

<bean class="demo.Child" id="child2" parent="parent">
    <property name="password" value="123456"/>
</bean>

8. 访问外部配置文件装配属性

public class User {
    private String name;
    private int age;
    // getters & setters
}

在resources目录下创建配置文件user.properties

user.name=Tom
user.age=28
<!-- 该bean是用来读取配置文件的 -->
<!-- 该bean由Spring框架自动使用, 所以不需要配置id属性 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <!-- location属性: Resource类型, 加载一个资源 -->
    <property name="location" value="classpath:user.properties"/>
    <!-- 如果有多个资源加载, 配置locations属性
    该属性是一个变长参数, 当作数组装配
    <property name="locations">
        <array>
            <value>classpath:user.properties</value>
        </array>
    </property>
    -->
</bean>

<bean class="demo.User" id="user">
    <!-- 当通过PropertyPlaceholderConfigurer加载配置文件之后 -->
    <!-- 可以在配置中通过类似EL表达式的语法读取配置中的内容 -->
    <property name="name" value="${user.name}"/>
    <property name="age" value="${user.age}"/>
</bean>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User u = (User) ac.getBean("user");
System.out.println(u.getName() + " - " + u.getAge());

9. 属性的自动装配

public class SomeClass {}
public class OtherClass {
    private SomeClass some1;
    private SomeClass some2;

    public OtherClass() {}

    public OtherClass(SomeClass some1, SomeClass some2) {
        this.some1 = some1;
        this.some2 = some2;
    }

    public SomeClass getSome1() {
        return some1;
    }

    public void setSome1(SomeClass some1) {
        this.some1 = some1;
    }

    public SomeClass getSome2() {
        return some2;
    }

    public void setSome2(SomeClass some2) {
        this.some2 = some2;
    }

    public void showSome() {
        System.out.println(this.some1);
        System.out.println(this.some2);
    }
}
<bean class="demo.SomeClass" id="some1"/>
<bean class="demo.SomeClass" id="some2"/>
<!--<bean class="java.lang.String" id="some2"/>-->

<bean class="demo.OtherClass" id="other" autowire="byName"/>
<!-- autowire属性取值 -->

<!-- 1. no -->
<!--    不进行自动注入, 需要通过property子标签进行属性注入配置 -->

<!-- 2. byName -->
<!--    根据要注入的属性名实现自动注入 -->
<!--    以属性名为id在容器中查找bean实例 -->
<!--    如果找到bean实例, 则进行注入操作 -->
<!--    如果没有查找相应的bean实例, 则不进行注入操作 -->
<!--    如果找到的bean实例类型与要注入的属性类型不一致, 报错 -->

<!-- 3. byType -->
<!--    根据属性的类型进行自动装配 -->
<!--    Spring会在ac容器中寻找类型与属性类型一致的bean实例 -->
<!--    将找到的bean实例装配到属性上, 如果找不到则不进行装配 -->
<!--    如果找到的bean实例不唯一, 报错 -->

<!-- 4. constructor -->
<!--    根据构造方法的参数, 使用byType方式进行自动注入 -->
<!--    根据构造方法的参数类型, 以byType的形式在ac容器中匹配bean实例 -->
<!--    所以以该方式注入时, 属性可以没有set方法 -->

<!-- 5. default, 行为与no一样 -->

六,对象的生命周期

1. Spring管理对象的生命周期

生命周期 说明
实例化 调用构造方法创建对象(默认在Spring容器(ac容器)的初始化时)
依赖注入 创建对象过程中, 对属性进行依赖注入
postProcessBeforeIntialization 初始化方法前的后处理, DI之后
初始化方法 并非构造方法, 是一个普通方法. 通过init-method指定, 由Spring自动调用
postProcessAfterIntialization 初始化方法后的后处理, 初始化方法之后
就绪, 使用 对象创建并初始化完成, 可以从ac容器中获取使用(ac.getBean(""))
销毁方法 并非对象从JVM中销毁, 对象从ac容器中被销毁. 一个普通方法, 通过destroy-method指定, 由Spring自动调用
对象销毁 对象被垃圾回收器从JVM中销毁

ac.getBean("")之前的事情, 都是由Spring容器(ac容器)执行; 这些操作对于框架的使用者而言是不可见的; 但可以通过编写代码, 对每一个步骤介入


2. 修改实例化时机

public class SomeClass {
    public SomeClass() {
        System.out.println("SomeClass.SomeClass()");
    }
}
public class OtherClass {
    public OtherClass() {
        System.out.println("OtherClass.OtherClass()");
    }
}
<bean class="demo.SomeClass" id="some"/>
<bean class="demo.OtherClass" id="other" lazy-init="true"/>
<!-- lazy-init属性: 修改对象的实例化时机 -->
<!-- true: ac容器初始化时, bean不会被实例化 -->
<!-- false|default: ac容器初始化时, bean会被实例化 -->
<!-- bean的实例化时机延后到第一次从ac容器中获取bean时 -->
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
ac.getBean("some");
System.out.println("doSome......");
ac.getBean("other");

3. 修改实例化次数

public class SomeClass {
    public SomeClass() {
        System.out.println("SomeClass.SomeClass()");
    }
}
public class OtherClass {
    public OtherClass() {
        System.out.println("OtherClass.OtherClass()");
    }
}
<bean class="demo.SomeClass" id="some"/>
<bean class="demo.OtherClass" id="other" scope="prototype"/>
<!-- scope属性: 配置bean的实例化次数 -->
<!-- prototype: 非单例, 每次从ac容器中获取对象时, 都会实例化一个新对象 -->
<!-- singleton: 单例 -->
<!-- bean的实例化次数默认是单例的 -->
<!-- request: web工程相关, 每次请求都会创建新对象 -->
<!-- session: web工程相关, 每次会话创建新对象 -->
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

SomeClass some1 = (SomeClass) ac.getBean("some");
System.out.println(some1);

SomeClass some2 = (SomeClass) ac.getBean("some");
System.out.println(some2);

OtherClass other1 = (OtherClass) ac.getBean("other");
System.out.println(other1);

OtherClass other2 = (OtherClass) ac.getBean("other");
System.out.println(other2);

执行结果

SomeClass.SomeClass()
demo.SomeClass@6f7fd0e6
demo.SomeClass@6f7fd0e6
OtherClass.OtherClass()
demo.OtherClass@47c62251
OtherClass.OtherClass()
demo.OtherClass@3e6fa38a

4. 初始化方法与销毁方法

public class SomeClass {
    public SomeClass() {
        System.out.println("SomeClass.SomeClass()");
    }

    public void init() {
        System.out.println("SomeClass.init()");
    }

    public void destroy() {
        System.out.println("SomeClass.destory()");
    }
}
<bean class="demo.SomeClass" id="some"
      init-method="init"
      destroy-method="destroy"
/>
// 当容器初始化, 按照框架的默认设置, 对象会随着容器的初始化而初始化
// 通过init-method指定的初始化方法会被执行
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

// 手动关闭容器
// 当容器被关闭, 容器中的对象会从容器中被销毁
// 通过destroy-method指定的销毁方法会被执行
((ClassPathXmlApplicationContext) ac).close();

5. 后处理bean

  • BeanFactory: 对象工厂, 也是一个容器, 用于管理对象

  • 后处理bean: 实现了某个特殊接口的bean, 用于对容器中其它所有bean进行后处理操作

  • 后处理

    • 在DI之后, postProcessBeforeIntialization

    • init-method之后, postProcessAfterIntialization

    • 或容器的初始化完成之后

后处理bean是Spring基于开闭原则提供的生命周期的扩展点, 可以根据需求进行扩展操作

  • BeanPostProcessor接口: 对象的后处理(DI之后和init-method之后)

  • BeanFactoryPostProcessor接口: 容器的后处理

对象的后处理

public class SomeClass {
    private String val;

    public SomeClass() {
        System.out.println("SomeClass.SomeClass()");
    }
    // getters & setters
    public void doSomeInit() {
        System.out.println("初始化方法: SomeClass.doSomeInit()");
        System.out.println("init-method: " + this);
        System.out.println("init-method: " + this.val);
        this.val = "new value in init-method";
    }
}
<bean class="demo.SomeClass" id="some1" init-method="doSomeInit">
    <property name="val" value="this is a test String"/>
</bean>

定义后处理bean

public class MyBeanPostProcessor implements BeanPostProcessor {

    // bean的初始化方法之前执行的后处理
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor.postProcessBeforeInitialization() -> " + bean + " -> " + beanName);
        try {
            Field f = bean.getClass().getDeclaredField("val");
            f.setAccessible(true);
            // 此处获取到的是ac配置中向bean的属性注入的值: this is a test String
            System.out.println(f.get(bean));
            // 对bean的属性值进行修改
            f.set(bean, "the val is change");
            // 由于该方法是在bean的初始化方法之前执行
            // bean的初始化方法中再获取属性值时, 获取的就是the val is change
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // bean的初始化方法之后执行的后处理
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor.postProcessAfterInitialization() -> " + bean + " -> " + beanName);
        try {
            Field f = bean.getClass().getDeclaredField("val");
            f.setAccessible(true);
            // 该方法是在bean的初始化方法之后执行
            // 获取到的是bean的初始化方法中所修改的值: new value in init-method
            System.out.println("初始化方法之后的后处理: " + f.get(bean));
            f.set(bean, "new value in postProcessAfterInitialization");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

将后处理bean配置到ac容器中

<!-- 定义好后处理bean之后, 配置到容器中 -->
<!-- Spring框架内部使用, 所以不需要配置id属性 -->
<bean class="demo.MyBeanPostProcessor"/>
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeClass some = (SomeClass) ac.getBean("some1");
// bean的val属性值经历的赋值过程是
// 1. ac配置中的初始注入: this is a test String
// 2. postProcessBeforeInitialization中的修改: the val is change
// 3. bean的初始化方法中的修改: new value in init-method
// 4. postProcessAfterInitialization中的修改: new value in postProcessAfterInitialization
// 所以此时获取到的最终值是: new value in postProcessAfterInitialization
System.out.println(some.getVal());

容器的后处理

定义容器后处理

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory cb) 
    throws BeansException {
        // 参数ConfigurableListableBeanFactory对象, 其实就是容器
        // 可以从容器中获取bean
        SomeClass some = cb.getBean(demo.SomeClass.class);
        System.out.println("BeanFactoryPostProcessor -> " + some.getVal());
    }
}

将容器后处理配置到ac中

<bean class="demo.MyBeanFactoryPostProcessor"/>

**注意: **

<bean class="demo.MyBeanPostProcessor"/>
<bean class="demo.MyBeanFactoryPostProcessor"/>
<!-- 当BeanPostProcessor与BeanFactoryPostProcessor同时存在时 -->
<!-- BeanPostProcessor就失效了 -->
<!-- BeanFactoryPostProcessor是对整个容器进行后处理 -->
<!-- 当容器初始化完成时, 容器中的bean也都初始化完成了 -->
<!-- BeanFactoryPostProcessor的后处理包含更全面 -->

七,Spring的注解

注解 说明
@Component 定义bean, 相当于xml配置中的<bean/>标签的作用, 通过value属性定义bean的id(即beanName)
@Controller 等效于@Component注解, 通常用于controller层
@Service 等效于@Component注解, 通常用于service层
@Repository 等效于@Component注解, 通常用于dao层
@Value 属性注入(简单值装配), 可以加在成员变量上, 也可以加在set方法上
配合PropertyPlaceholderConfigurer使用可以实现从配置文件注入
@Autowired 属性注入(对象类型), 默认注入方式byType, 可以加在成员变量, 也可以加在set方法上
required属性: 默认为true, 要注入的目标对象必须在IOC容器中存在, 如果不存在就会报错
值为false时, 如果要注入的目标对象在IOC容器中不存在, 注入结果为null, 不会报错
@Qualifier 用于指明要注入的bean的id, 当@AutowiredbyType的方式注入产生冲突时, 配合该注解将注入方式改为byName
@Lazy 修改对象的实例化时机, 通常加在类上, 默认值true
@Scope 修改对象的实例化次数, 通常加在类上, 默认值空字符串
@PostConstruct 加在方法上, 与init-method对应
@PreDestroy 加在方法上, 与destroy-method对应
@Configuration 加在类上, 该类代替applicationContext.xml文件, 该类就称为配置类
@Bean 加在配置类中的方法上, 相当于xml配置文件中<bean/>标签的作用
@ComponentScan 组件扫描, 扫描指定包及子包中类上的注解

由于后处理bean是自定义类实现相应接口, 然后在ac配置中配置一个<bean/>, 通过@Component注解就可以配置

以注解形式使用Spring容器时, 属性可以没有set方法, 只有私有化成员变量也可以实现注入

@Resource注解(javax.annotation.Resource), 该注解也可以用于对象类型属性注入

实际开发中Spring框架的使用通常是xml + 注解的形式

自定义的类要交给容器管理, 用注解

jar包中的类或配置文件的读取等要交给容器, 用xml

// 给ApplicationConfig类上加@Configuration注解
// 它就可以取代原先的applicationContext.xml配置文件
// 称为Spring配置类
@Configuration
@ComponentScan("demo")
public class ApplicationConfig {
    // 配置类中定义一个方法, 头上加@Bean注解, 表示向IOC配置中配置一个bean
    // 该方法的返回值类型就是目标bean的类型, 返回值就是目标bean实例
    // 最后return到容器中
    @Bean
    public PropertyPlaceholderConfigurer ppc() {
        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
        Resource res = new ClassPathResource("user.properties");
        ppc.setLocation(res);
        return ppc;
    }
}
// 将类交给IOC容器管理, 同时指明beanName, 即xml配置中<bean/>标签的id属性
@Component("some")
// 对应xml配置中<bean/>标签的lazy-init属性
@Lazy
// 对应xml配置中<bean/>标签的scope属性
@Scope("prototype")
public class SomeClass {
    public SomeClass() {
        System.out.println("SomeClass.SomeClass()");
    }

    // bean的初始化方法, 对应xml配置中<bean/>标签的init-method属性
    @PostConstruct
    public void init() {
        System.out.println("SomeClass.init()");
    }

    // bean的销毁方法, 对应xml配置中<bean/>标签的destroy-method属性
    @PreDestroy
    public void destroy() {
        System.out.println("SomeClass.destroy()");
    }
}
public interface SomeService {}

@Component("impl01")
public class SomeServiceImpl01 implements SomeService {}

@Component("impl02")
public class SomeServiceImpl02 implements SomeService {}
@Component
public class OtherClass {
    @Value("${user.name}")
    private String name;

    @Autowired // 默认为byType形式注入
    private SomeClass someClass;

    @Autowired
    @Qualifier("impl01") // 配合使用, 将@Autowired的注入方式改为byName
    private SomeService someService;

    public void show() {
        System.out.println(this.name);
        System.out.println(this.someClass);
        System.out.println(this.someService);
    }
}
// ac对象的获取改为AnnotationConfigApplicationContext类型
// 参数用来指明配置类是谁
ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
SomeClass some = (SomeClass) ac.getBean("some");
System.out.println(some);
// OtherClass类头上的@Component注解没有指定beanName
// 默认以简单类名首字母小写为beanName
OtherClass other = (OtherClass) ac.getBean("otherClass");
other.show();

@Resource注解的使用演示

@Component
public class OtherClass {
    @Value("${user.name}")
    private String name;

    // @Autowired // 默认为byType形式注入
    @Resource
    private SomeClass someClass;

    // @Autowired
    // @Qualifier("impl01") // 配合使用, 将@Autowired的注入方式改为byName
    @Resource(name = "impl02")
    private SomeService someService;

    public void show() {
        System.out.println(this.name);
        System.out.println(this.someClass);
        System.out.println(this.someService);
    }
}

八,AOP

Aspect Oriented Programming, 面向切面编程

将程序中的交叉业务逻辑提取出来, 封装成切面; 由AOP容器在适当的时机将这些切面动态地织入到具体的业务逻辑中

  • 交叉业务逻辑: 混合在业务逻辑中的代码, 但这些代码又不属于业务逻辑

  • 切面: 交叉业务逻辑代码片段

  • 适当的时机

    • 编译时: 静态代理

    • 运行时: 动态代理

  • 动态: Spring AOP使用的是动态代理

  • 织入: 将交叉业务和核心业务重新耦合到一起

将交叉业务逻辑抽象成切面的目的:

  • 将交叉业务逻辑代码和核心业务逻辑代码分离

  • 切面的复用: 独立模块化, 在不改变现有代码的前提下动态地增加功能

AOP的相关概念

概念 说明
切面(Aspect) 提取出来的交叉业务逻辑代码片段
切点(Pointcut) 切面的织入位置
织入(Weaving) 将切面耦合到切点
通知(Advice) 切面的实际实现

Spring实现动态代理的两种方式

  • 实现了接口的类: 使用JDK中java.lang.reflect.Proxy创建代理对象

  • 没有实现接口的类: 使用CGLib中的Proxy创建代理对象, 使用继承目标类的方式创建代理对象

  • 既没有实现接口也不能被继承的类(被final修饰的类), 不能创建代理

Hello World

引入依赖

<!-- Spring核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- SpringIOC容器的核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- Spring AOP核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- Spring AOP对AspectJ表达式的依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

编写目标类

public interface SomeService {
    public void doSome();
    public void doOther();
}
public class SomeServiceImpl implements SomeService {
    public void doSome() {
        System.out.println("SomeServiceImpl.doSome()");
    }

    public void doOther() {
        System.out.println("SomeServiceImpl.doOther()");
    }
}

编写切面代码

public class ServiceAdvice {
    public void beforeAdvice() {
        // System.out.println("ServiceAdvice.beforeAdvice(), 在目标方法之前执行的通知");
        System.out.println("开启事务");
    }

    public void afterAdvice() {
        // System.out.println("ServiceAdvice.afterAdvice(), 在目标方法之后执行的通知");
        System.out.println("提交事务");
    }
}

Spring配置

<!-- 将目标类和切面类配置到容器中 -->
<bean class="demo.SomeServiceImpl" id="someService"/>
<bean class="demo.ServiceAdvice" id="advice"/>

<!-- 配置切面 -->
<!-- 告诉Spring框架, 在执行哪个bean中的哪个方法之前|之后, 执行哪个bean中的什么方法 -->
<aop:config>
    <!-- 告诉Spring要在SomeServiceImpl中所有方法上进行织入 -->
    <aop:pointcut id="ssi" expression="within(demo.SomeServiceImpl)"/>
    <!-- 告诉Spring切面是ServiceAdvice -->
    <aop:aspect ref="advice">
        <aop:before method="beforeAdvice" pointcut-ref="ssi"/>
        <aop:after method="afterAdvice" pointcut-ref="ssi"/>
    </aop:aspect>
</aop:config>

使用aop标签需要引入相关schema
在这里插入图片描述

运行

ApplicationContext ac = new ClassPathXmlApplicationContext("app*.xml");
SomeService service = (SomeService) ac.getBean("someService");
service.doSome();
service.doOther();

运行结果

开启事务
SomeServiceImpl.doSome()
提交事务
开启事务
SomeServiceImpl.doOther()
提交事务

九,AOP通知类型

1. 前置通知

目标类

public interface SomeService {
    String doSome(String str, int a);
}
public class SomeServiceImpl implements SomeService {
    public String doSome(String str, int a) {
        System.out.println("SomeServiceImpl.doSome()");
        return "abcdefghjkoijklljklhlk";
    }
}

切面类

public class ServiceAdvice {
    public void beforeAdvice() {
        System.out.println("ServiceAdvice.beforeAdvice()");
    }
}

配置

<bean class="demo.SomeServiceImpl" id="service"/>
<bean class="demo.ServiceAdvice" id="advice"/>

<aop:config>
    <aop:pointcut id="ssi" expression="within(demo.SomeServiceImpl)"/>
    <aop:aspect ref="advice">
        <aop:before method="beforeAdvice" pointcut-ref="ssi"/>
    </aop:aspect>
</aop:config>

运行

ApplicationContext ac = new ClassPathXmlApplicationContext("app*.xml");
SomeService service = (SomeService) ac.getBean("service");
service.doSome("aaa", 123);

前置通知带参数

public class ServiceAdvice {
    // 前置通知方法可以带一个参数: org.aspectj.lang.JoinPoint
    // 通过该参数可以获取到目标方法的相关信息
    public void beforeAdvice(JoinPoint jp) {
        // org.aspectj.lang.Signature
        Signature s = jp.getSignature();
        System.out.println(s);
        // 获取方法名
        System.out.println(s.getName());
        // 方法的声明类
        System.out.println(s.getDeclaringType());
        // 方法的修饰符常量
        int mod = s.getModifiers();
        System.out.println(mod);
        System.out.println(Modifier.toString(mod));
        // 获取方法的实参
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        // 获取调用方法的具体对象
        Object target = jp.getTarget();
        System.out.println(target);
    }
}

2. 后置通知

public class ServiceAdvice {
    public void afterAdvice() {
        System.out.println("ServiceAdvice.afterAdvice()");
    }

    // 后置通知(after-returning)的参数
    // 该参数就是目标方法的返回值
    // 如果目标方法是void方法, 则该参数值为null
    public void afterReturning(Object obj) {
        System.out.println("ServiceAdvice.afterReturning() -> " + obj);
    }

    // 后置通知(after-throwing)的参数
    // 该参数就是方法所发生的异常对象
    public void afterThrowing(Exception e) {
        System.out.println("ServiceAdvice.afterThrowing()");
    }
}
<bean class="demo.SomeServiceImpl" id="service"/>
<bean class="demo.ServiceAdvice" id="advice"/>

<aop:config>
    <aop:pointcut id="ssi" expression="within(demo.SomeServiceImpl)"/>
    <aop:aspect ref="advice">
        <!-- 目标方法正常结束, 执行切面 -->
        <!-- after-returning后置通知如果加了参数, 需要将参数配置进来 -->
        <aop:after-returning method="afterReturning" pointcut-ref="ssi"
                             returning="obj"/>

        <!-- 目标方法发生异常, 执行切面 -->
        <!-- after-throwing后置通知如果加了参数, 需要将参数配置进来 -->
        <aop:after-throwing method="afterThrowing" pointcut-ref="ssi"
                            throwing="e"/>

        <!-- 不论目标方法正常结束还是发生异常结束, 都会执行 -->
        <aop:after method="afterAdvice" pointcut-ref="ssi"/>

        <!-- 如果三种after都存在, 执行顺序取决于配置顺序 -->
    </aop:aspect>
</aop:config>

3. 环绕通知

public class ServiceAdvice {
    // 关于环绕通知方法的几点事项
    // 1. 方法返回值类型必须是Object
    // 2. 方法必须throws Throwable
    // 3. 方法必须要有ProceedingJoinPoint类型参数
    // 4. 环绕通知可以控制目标方法是否执行以及执行时机
    public Object around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("目标方法执行之前的操作...");

        // 通过参数的proceed()方法控制目标方法的执行
        // proceed()方法返回值就是目标方法的返回值
        Object res = pj.proceed();
        System.out.println(res);

        System.out.println("目标方法执行之后的操作...");
        return res;
    }
}
<bean class="demo.SomeServiceImpl" id="service"/>
<bean class="demo.ServiceAdvice" id="advice"/>

<aop:config>
    <aop:pointcut id="ssi" expression="within(demo.SomeServiceImpl)"/>
    <aop:aspect ref="advice">
        <aop:around method="around" pointcut-ref="ssi"/>
    </aop:aspect>
</aop:config>

当几种通知都存在时, 执行顺序取决于配置顺序

不论怎样, 前置通知和环绕通知的前置操作一定在目标方法执行前执行

后置通知和环绕通知的后置操作, 一定在目标方法执行之后执行

4.切点表达式

Spring AOP使用AspectJ切点表达式定义切点

  1. within(包名.类名): 切入类中的所有方法

  2. execution(表达式)

    表达式语法: modifiers? ret-type declearing-class?.methodName(param) throws?

    语法中的符号

    ?: 有或没有

    *: 任意多个

    ..: 任意多层目录|任意多个参数

    • modifiers: 方法的访问修饰符

    • ret-type: 方法返回值类型, 必须

    • declearing-class: 方法的声明类

    • methodName: 方法名

    • prarm: 方法参数

    • throws: 方法声明的异常

    • public void s(..)

      向所有public void s方法切入, 不论在哪个类中

    • * com.itany..service.impl.*ServiceImpl.*(..)

      切入到service.impl包下所有以ServiceImpl结尾的类中任意方法

    • * com.itany..service.impl.*ServiceImpl.*(java.lang.String)

    • * com.itany..service.impl.*ServiceImpl.*(java.lang.String, com.itany..entity.User)

十,注解AOP

目标类

public interface SomeService {
    void doSome();
    void doOther();
}
@Service("someService")
public class SomeServiceImpl implements SomeService {
    public void doSome() {
        System.out.println("SomeServiceImpl.doSome()");
    }

    public void doOther() {
        System.out.println("SomeServiceImpl.doOther()");
    }
}

切面类

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component // 注册成容器中的bean
@Aspect // 将容器中的bean注册成切面
public class ServiceAdvice {

    // 注册切点
    @Pointcut("within(demo.SomeServiceImpl)")
    public void pointcut() {
    }

    // 切点id就是上面注册切点的方法
    // 注意: 表示切点的方法小括号也要带上
    @Before("pointcut()")
    public void beforeAdvice() {
        System.out.println("前置通知...");
    }

    @After("pointcut()")
    public void afterAdvice() {
        System.out.println("后置通知...");
    }

    @AfterReturning("pointcut()")
    public void afterReturning() {
        System.out.println("正常结束的后置通知...");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        System.out.println("异常结束的后置通知...");
    }

    @Before("pointcut()")
    public void beforeAdvice(JoinPoint jp) {
        System.out.println("带参的前置通知...");
    }

    @AfterReturning(value = "pointcut()", returning = "obj")
    public void afterReturning(Object obj) {
        System.out.println("带参的正常结束后置通知...");
    }

    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void afterThrowing(Exception e) {
        System.out.println("带参的异常后置通知...");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知的前置操作...");
        Object res = pjp.proceed();
        System.out.println("环绕通知的后置操作...");
        return res;
    }
}

配置类

@Configuration
@ComponentScan("demo")
// 启用注解AOP的自动代理
// 如果没有该注解, 切面不生效
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {}

运行

ApplicationContext ac = new AnnotationConfigApplicationContext(demo.AppConfig.class);
SomeService some = (SomeService) ac.getBean("someService");
some.doSome();
some.doOther();

十一,Spring与JDBC的整合

相关依赖

<!-- Spring核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- IOC容器的核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- Spring上下文依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- spring-aop核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- spring-aop对AspectJ表达式的依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
<!-- spring-jdbc依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- mysql驱动依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>
<!-- dbcp数据源依赖 -->
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>

配置

<?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: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://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-cache.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 读取外部配置文件 -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:datasource.properties"/>
    </bean>

    <!-- 配置数据源 -->
    <!-- 1. Spring内置数据源 -->
    <!--<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>-->
    <!-- 2. 配置第三方数据源, 如dbcp, druid, c3p0 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事务aop -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 统一配置: 为所有方法添加事务 -->
            <!--
                <tx:method name="*" propagation="REQUIRED"/>
            -->
            <!-- propagation: 事务传播策略(事务嵌套问题) -->
            <!-- REQUIRED -->
            <!--     当前方法必须要有事务, 如果已开启了事务就使用当前事务, 不再另外开启事务 -->
            <!--     如果没有事务, 则另外开启事务 -->
            <!-- REQUIRED_NEW: 需要事务, 始终开启新事务 -->
            <!-- MANDATORY: 需要事务, 如果没有, 报错 -->
            <!-- SUPPORTS: 需要事务, 如果没有不会开启新事务, 相当于只是一个建议 -->
            <!-- NEVER: 不要事务, 如果已有事务, 报错 -->
            <!-- NOT_SUPPORTED: 不支持事务, 如果有事务, 忽略 -->
            <!-- NESTED: 不需要事务, 如果已有事务, 相当于REQUIRED -->

            <!-- 分开配置 -->
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="update*" propagation="REQUIRED"
                       rollback-for="java.lang.Exception"
                       no-rollback-for="java.lang.NullPointerException"/>
            <!-- read-only: 只读事务 -->
            <!-- rollback-for: 当遇到指定类型的异常(或子异常)时回滚 -->
            <!-- no-rollback-for: 当遇到指定类型的异常(或子异常)时不回滚 -->
            <!-- isolation: 事务的隔离级别, 实际开发中通常都不配置该项, 因为事务都通过代码控制 -->
        </tx:attributes>
    </tx:advice>

    <!-- 配置事务通知切面 -->
    <aop:config>
        <aop:pointcut id="pc" expression="execution(* ..service.impl.*Impl.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
    </aop:config>

</beans>

dao中使用Spring内置的JdbcTemplate

  • 方式1

    public class SomeDaoImpl implements SomeDao {
        private JdbcTemplate jdbcTemplate;
    
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        public void test() {
            jdbcTemplate.update(" insert into t_xxx (xxx, xxx) values (?, ?) ", xxx, xxx);
        }
    }
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="someDao" class="demo.SomeDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    
  • 方式2

    public class SomeDaoImpl2 extends JdbcTemplate implements SomeDao {
        public void test() {
            update(" insert into t_xxx (xxx, xxx) values (?, ?) ", xxx, xxx);
        }
    }
    
    <bean id="someDao2" class="demo.SomeDaoImpl2">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    

Spring与MyBatis整合

依赖

<!-- Spring核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- IOC容器的核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- Spring上下文依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- spring-aop核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- spring-aop对AspectJ表达式的依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
<!-- spring-jdbc依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.0.RELEASE</version>
</dependency>
<!-- mysql驱动依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>
<!-- 阿里巴巴的数据源: druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
<!-- MyBatis依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.1</version>
</dependency>
<!-- MyBatis分页插件: pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.8</version>
</dependency>
<!-- MyBatis与Spring的整合依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
</dependency>

配置

<?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: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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-cache.xsd">

    <!-- 配置注解扫描(扫包), 配合Spring注解使用 -->
    <context:component-scan base-package="demo.service"/>

    <!-- 指明数据库信息properties文件 -->
    <context:property-placeholder location="classpath:datasource.properties"/>

    <!-- 数据源(阿里巴巴的druid) -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- MyBatis与Spring的整合配置 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>
        <!-- 将独立的MyBatis配置文件引入 -->
        <!--<property name="configLocation" value="classpath:mybatis-config.xml" />-->
        <!-- 配置MyBatis插件 -->
        <property name="plugins">
            <list>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!-- 配置sql方言 -->
                        <value>helperDialect=mysql</value>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    <!-- MyBatis对dao接口的扫包配置 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="demo.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!-- 事务管理器(MyBatis与JDBC的管理器是一样的) -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 使用Spring的数据库访问异常体系 -->
    <bean id="persistenceExceptionTranslationPostProcessor"
          class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

    <!-- 配置注解版事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

事务注解

// 关于事务注解: @Transactional
// 加在方法上: 对方法进行事务控制
// 加在类上: 对类中所有方法进行事务控制
@Service
@Transactional(propagation = Propagation.REQUIRED,
        rollbackFor = java.lang.Exception.class)
public class SomeServiceImpl implements SomeService {

    /*@Transactional(propagation = Propagation.REQUIRED,
            rollbackFor = java.lang.Exception.class,
            noRollbackFor = java.lang.NullPointerException.class,
            readOnly = true)*/
    @Transactional(readOnly = true)
    public Object query() {
        return null;
    }

    /*@Transactional(propagation = Propagation.REQUIRED,
            rollbackFor = java.lang.Exception.class)*/
    public void update() {}
}
.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>
        <!-- 将独立的MyBatis配置文件引入 -->
        <!--<property name="configLocation" value="classpath:mybatis-config.xml" />-->
        <!-- 配置MyBatis插件 -->
        <property name="plugins">
            <list>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!-- 配置sql方言 -->
                        <value>helperDialect=mysql</value>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    <!-- MyBatis对dao接口的扫包配置 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="demo.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!-- 事务管理器(MyBatis与JDBC的管理器是一样的) -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 使用Spring的数据库访问异常体系 -->
    <bean id="persistenceExceptionTranslationPostProcessor"
          class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

    <!-- 配置注解版事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

事务注解

// 关于事务注解: @Transactional
// 加在方法上: 对方法进行事务控制
// 加在类上: 对类中所有方法进行事务控制
@Service
@Transactional(propagation = Propagation.REQUIRED,
        rollbackFor = java.lang.Exception.class)
public class SomeServiceImpl implements SomeService {

    /*@Transactional(propagation = Propagation.REQUIRED,
            rollbackFor = java.lang.Exception.class,
            noRollbackFor = java.lang.NullPointerException.class,
            readOnly = true)*/
    @Transactional(readOnly = true)
    public Object query() {
        return null;
    }

    /*@Transactional(propagation = Propagation.REQUIRED,
            rollbackFor = java.lang.Exception.class)*/
    public void update() {}
}
发布了32 篇原创文章 · 获赞 1 · 访问量 4804

猜你喜欢

转载自blog.csdn.net/weixin_42426385/article/details/104280783
02