从0到1学习Spring框架

1.Spring简介

1.1.什么是Spring
  • Spring是一个轻量级的框架,能够简化企业级应用开发,减少代码量。

Spring的核心框架是AOP(面向切面)与IOC(控制反转),IOC说白了就是跟容器要对象,DI(依赖注入)就是给属性赋值,AOP就是根据动态代理分为切面,切入点和通知。Spring还提供对JDBC的轻量级封装,还提供声明事务。Spring还根据MVC设计模式开发出SpringMVC框架。

(1)Spring框架目标

  • 使用声明式事务,向EJB挑战
  • 框架整合,像胶水一样整合多个框架.

(2)什么是JavaEE

  • JAVAEE:Java Enterprise Edition(java企业级版本)是Sun制定的一套java开发规范

  • 是由一系列的JSR组成

(3)什么是JSR

  • Java Specification Requests Java规范提案

(4)JAVAEE容器和组件

容器

Applet Container

Web Container

Application Client Container

EJB Container

组件

Applet

JSP Servlet

Java Bean

EJB JavaBean

(5)Spring特点

  • Ioc:解耦使用IoC机制避免硬编码造成程序耦合
  • AOP:Aspect Orentied Programing 面向切面的编程
  • 声明式事务管理
  • 对JDBC进行轻量级封装,更加灵活的操作数据库
  • Spring提供MVC模式支持:SpringMVC
  • 提供文件上传,定时器常用工具类
  • 对其他优秀框架的支持(集成其他框架)
1.2.Spring框架结构

在这里插入图片描述

(1)Spring的核心功能

  • IoC容器
  • Bean生命周期管理
  • SpEL
  • AOP容器
  • 注解体系
  • 数据验证

(2)Spring的好处

  • 方便解耦
  • AOP编程支持
  • 声明式事务的支持
  • 方便程序的测试
  • 方便集成各种优秀框架
  • 降低JavaEE API的使用难度

2.Spring资源管理

2.1.Spring资源管理简介

(1)Spring资源管理的特点

  • 隐藏底层的实现
  • 新增资源存在判断、资源操作权限相关的功能
  • 支持通配符获取资源

(2)Spring管理哪些资源

  • UrlResource
  • ClassPathResource
  • FileSystemResource
  • ServletContextResource
  • InputStreamResource
  • ByteArrayResource

(3)资源协议与路径

  • classpath:从当前jvm的classpath根路径开始获取资源
  • file:从操作系统的路径获取资源
  • http(s):从互联网获取资源
  • 无标记:根据应用上下文获取资源

(4)Spring使用什么访问底层资源

Spring使用Resource接口访问底层资源

(5)Spring使用什么接口加载资源

Spring使用ResourceLoader接口加载资源

IOC容器实现了ResourceLoader接口,可以随时使用它的**getResource(location)**加载资源

2.2.Spring资源管理案例
  • 创建maven工程,写HelloService
public class HelloService {

    public void sayHello(String name){

        System.out.println(name+":你好!");

    }

}
  • 引入spring-framework依赖
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.15.RELEASE</version>
    </dependency>
</dependencies>
  • 在main/resources下创建applicationContext.xml文件
<?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="helloService" class="com.tjetc.service.HelloService">

    </bean>

</beans>
  • 编写测试类
public class Test {

    public static void main(String[] args) {

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

        HelloService helloService = (HelloService) context.getBean("helloService");
        helloService.sayHello("李祥");

    }

}

运行结果

李祥:你好!

3.Spring容器创建

3.1.IOC容器创建的三种方法
  • 使用ClassPathXmlApplicationContext创建IOC容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  • 使用FileSystemXmlApplicationContext创建容器

    此时,applicationContext.xml 的路径要写磁盘的物理路径

FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\ideaworkspaces\\0919-spring\\01-spring\\src\\main\\resources\\applicationContext.xml");
  • 使用XmlWebApplicationContext创建容器

    使用XmlWebApplicationContext时要导入Tomcat,和spring-webmvc依赖

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.2.8.RELEASE</version>
</dependency>
 XmlWebApplicationContext context = new XmlWebApplicationContext();

        //设置配置文件位置
        context.setConfigLocation("classpath:applicationContext.xml");

        //调用refresh()方法
        context.refresh();

        HelloService helloService = context.getBean("helloService",HelloService.class);

        helloService.sayHello("李祥");
3.2.从IOC容器getBean的三种方法
  • 容器对象.getBean(“bean的id”),精确定位,需要强制转换
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

HelloService helloService = (HelloService) context.getBean("helloService");

helloService.sayHello("李祥");
  • 容器对象.getBean(“bean的id”,bean.class),精确定位,不需要强制转换
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

HelloService helloService = context.getBean("helloService",HelloService.class);

helloService.sayHello("李祥");
  • 容器对象.getBean(bean.class),不需要强制转换
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

HelloService helloService = context.getBean(HelloService.class);

helloService.sayHello("李祥");
3.3.Spring实例化bean的3种方法
  • 使用默认的构造方法创建bean对象

容器实例化会调用bean的默认无参数的构造方法,代码请看入门案例

  • 静态工厂方法创建bean对象
  • 创建工厂类HelloServiceFactory
public class HelloServiceFactory {

    public static HelloService helloServiceFactory(){  //注意是静态方法

        System.out.println("helloServiceFactory");

        return new HelloService();

    }

}
  • applicationContext.xml文件编写
 <bean id="helloServiceFactory" class="com.tjetc.factory.HelloServiceFactory" factory-method="helloServiceFactory">
 </bean>
  • 测试代码
ClassPathXmlApplicationContext context =  new ClassPathXmlApplicationContext("applicationContext.xml");

HelloService helloService = context.getBean(HelloService.class);

helloService.sayHello("李祥");
  • 实例工程方法创建bean

  • 创建工厂类HelloServiceFactory

public class HelloServiceFactory {

    public HelloService helloServiceFactory(){ //注意是非静态方法

        System.out.println("helloServiceFactory");

        return new HelloService();

    }

}
  • applicationContext.xml文件
<bean id="helloServiceFactory" class="com.tjetc.factory.HelloServiceFactory">

</bean>

<bean id="helloService" factory-bean="helloServiceFactory" factory-method="helloServiceFactory">

</bean>
  • 测试代码
ClassPathXmlApplicationContext context =  new ClassPathXmlApplicationContext("applicationContext.xml");

HelloService helloService = context.getBean(HelloService.class);

helloService.sayHello("李祥");

4.SpringIOC依赖注入

4.1.SpringIOC依赖注入简介

(1)SpringIOC的特点

  • Spring是轻量级容器+组件的管理模式

(2)什么是控制反转

  • 创建对象的权利由应程序创建改为由容器创建,控制权的转移称为控制反转.

(3)什么是依赖注入

  • 把容器创建好的依赖对象注入进来的过程称为依赖注入.

(4)依赖注入的目标

  • 提升组件重用的概率
  • 为系统搭建一个灵活、可扩展的平台

(5)Spring Framework的 IoC 容器操作基于两个包

  • org.springframework.beans 和 org.springframework.context

一个业务系统,由很多bean对象组成,bean之间存在调用关系,那么这些bean对象是如何协同工作的呢? 对象之间的调用,存在依赖关系,依赖对象的注入,称为依赖注入.

4.2.SpringIOC构造器注入

构造器注入bean子节点constructor-arg节点.可以使用constructor-arg节点属性index,name,type

基本类型注入:使用value

引用类型注入:使用ref

  • index构造方法参数的索引

准备一个实体类

public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

applicationContext.xml中配置

<bean id="student" class = "com.tjetc.domain.Student">
    <constructor-arg index="0" value="张三"></constructor-arg> <!--构造器注入用constructor-arg-->
    <constructor-arg index="1" value="18"></constructor-arg>
</bean>

测试代码

public static void main(String[] args) {

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

    Student student = context.getBean(Student.class);

    System.out.println(student);
    }
  • name构造方法的名称

实体类和测似代码不动,只要改变配置文件即可

<bean id="student" class = "com.tjetc.domain.Student">
    <constructor-arg name="name" value="张三"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
</bean>
  • type构造方法参数的类型

实体类和测似代码不动,只要改变配置文件即可

<bean id="student" class = "com.tjetc.domain.Student">
   <constructor-arg type="java.lang.String" value="张三"></constructor-arg>
    <constructor-arg type="int" value="16"></constructor-arg>
</bean>

注意如果是引用类型的bean用ref=“bean的id”

4.3.SpringIOC属性set方法注入

name是setXxx() 方法后单词的首字母变小写后的单词,不是属性名。

value代表的是值

ref代表的是引用类型的bean的id 的值

实体类中必须要有setter 、getter方法,以及无参构造

public Student() {
}

public Student(String name, int age) {
    this.name = name;
    this.age = age;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

applicationContext.xml配置文件

<bean id="student" class = "com.tjetc.domain.Student">
    <property name="name" value="张三"></property>
    <property name="age" value="13"></property>
</bean>

测试代码

public static void main(String[] args) {

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

    Student student = context.getBean(Student.class);

    System.out.println(student);
    }

set注入与构造器注入的对比

Set注入模式代码更加简洁

构造器注入对依赖关系的表达更加清楚

Set注入可以避免循环依赖问题

4.4.SpringIOC集合属性的注入
  • list、数组基本类型语法
<property name="hobbies">
      <list>
          <value></value>
          <value></value>
          <value></value>
          <value></value>
      </list>
</property>
  • list、数组引用类型语法
<property name="books">
     <list>
          <ref bean="book1"/>
          <ref bean="book2"/>
          <ref bean="book3"/>
     </list>
</property>
  • set基本数据类型语法
<property name="hobbies">
      <set>
           <value></value>
           <value></value>
           <value></value>
           <value></value>
           <value>读书</value>
      </set>
</property>
  • set引用数据类型语法
<property name="books">
     <set>
          <ref bean="book1"/>
          <ref bean="book2"/>
          <ref bean="book3"/>
     </set>
</property>
  • map基本类型语法
<property name="hobbies">
	 <map>
		<entry key="chi" value=""></entry>
		<entry key="he" value=""></entry>
		<entry key="wan" value=""></entry>
	 </map>
</property>
  • map引用类型语法
<property name="books">
	 <map>
		 <entry key="xi" value-ref="book1"></entry>
		 <entry key="dong" value-ref="book2"></entry>
		 <entry key="nan" value-ref="book3"></entry>
	 </map>
</property>
  • properties语法
<property name="properties">
	 <props>
		   <prop key="pk1">pv1</prop>
		   <prop key="pk2">pv2</prop>
		   <prop key="pk3">pv3</prop>
		   <prop key="pk4">pv4</prop>
	 </props>
</property>
4.5.SpringIOC空间命名

(1)p空间命名

先要引入p空间命名

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

基本数据类型

p:属性=值

 <bean id="student" class = "com.tjetc.domain.Student" p:name="张三" p:age="12">
 </bean>

引用数据类型

p:属性-ref=另一个bean的id的值

<bean id="book" class="com.tjetc.domain.Book" p:name="西游记" p:price="30">
	</bean>
	<bean id="student" class="com.tjetc.domain.Student" p:name="张三" p:book-ref="book">
	</bean>

(2)c空间命名

先要引入c空间命名

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

基本数据类型

c:构造方法参数名=值

<!--用属性名称注入-->
<bean id="student" class = "com.tjetc.domain.Student" c:name="张三" c:age="12">
</bean>
<!--用索引下标注入-->
<bean id="student" class = "com.tjetc.domain.Student" c:_0="张三" c:_1="12">
</bean>

引用数据类型

c:构造方法参数名-ref=另一个bean的id的值

<bean id="book" class="com.tjetc.domain.Book" p:name="西游记" p:price="30">
	</bean>
	<bean id="student" class="com.tjetc.domain.Student" c:name="李四" c:book-ref="book">
	</bean>
4.6.depends-on属性

B中没有A,在applicationContext.xml中配置depends-on=“a”,则先实例化A,再实例化B,销毁是先销毁B,再销毁A

依赖对象先创建后销毁.

public class Student {

    private String name;

    public Student() {
        System.out.println("Student()");
    }

    public void init(){

        System.out.println("Student.init()");

    }

    public void destroy(){

        System.out.println("Student.destroy()");

    }
}
public class MyClass {

    public String cname;

    public MyClass() {
        System.out.println("MyClass()");
    }

    public void init(){

        System.out.println("MyClass.init()");

    }

    public void destroy(){

        System.out.println("MyClass.destroy()");

    }

}

applicationContext.xml文件

 <bean id="student" class="com.tjetc.domain.Student" depends-on="myClass" init-method="init" destroy-method="destroy">

    </bean>
    <bean id="myClass" class="com.tjetc.domain.MyClass" init-method="init" destroy-method="destroy">

    </bean>

测试代码

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

        context.close();

运行结果

MyClass() MyClass.init() Student() Student.init()

Student.destroy() MyClass.destroy()

4.7.延迟加载lazy-init

ApplicationContext的默认行为就是在创建IOC容器时将所有singleton 的bean提前进行实例化。

系统默认配置是lazy-init=“false”

当配置lazy-init="true"后,当第一次调用bean对象时,才进行实例

  • lazy-init="true"创建IOC容器时没有实例化bean,当第一次调用context.getBean(C.class)才进行实例化bean
<bean id="c" class="com.tjetc.domain.C" lazy-init="true"></bean>
  • lazy-init="true"只对当前的bean有效,对其他的bean不起作用
<bean id="c" class="com.tjetc.domain.C" lazy-init="true"></bean>
	<bean id="d" class="com.tjetc.domain.D"></bean>
  • beans节点设置 default-lazy-init=“true” ,让所有的bean都设置成懒加载.
<beans default-lazy-init="true" xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        ">
	<bean id="c" class="com.tjetc.domain.C"></bean>
	<bean id="d" class="com.tjetc.domain.D"></bean>
</beans>
  • default-lazy-init=“true” 所有bean懒加载的前提下,设置某个bean不懒加载 bean设置lazy-init=“false”
<bean id="d" class="com.tjetc.domain.D" lazy-init="false"></bean>

5.SpringIOC自动注入

5.1.Spring容器管理注解

Spring自动扫描,会把以下注解的bean纳入Spring容器管理

@Component不好分层时用该注解

@Controller控制层使用该注解

@Service业务层使用该注解

@Repository dao层使用该注解

5.2.AutoWired注入
  • 添加context命名空间,1拖2
<?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"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
		//http://www.springframework.org/schema/context
        //https://www.springframework.org/schema/context/spring-context.xsd
        ">
 
</beans>
  • applicationContext.xml文件配置扫描基本包
<context:component-scan base-package="com.tjetc"></context:component-scan>
  • @AutoWired自动注入
@Repository
public class UserDao {

    public void login(){

        System.out.println("用户登录");

    }

}
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public void login(){
        userDao.login();
    }


}
ClassPathXmlApplicationContext context =  new ClassPathXmlApplicationContext("applicationContext.xml");

UserService bean = context.getBean(UserService.class);

bean.login();

运行结果

用户登录

5.3.接口多个实现类的注入方式
  • 准备UserDao接口,和两个实现类
public interface UserDao {

    void login();

}
@Repository
public class UserMySqlDaoImpl implements UserDao {
    public void login() {
        System.out.println("UserMySqlDaoImpl.login()");
    }
}
@Repository
public class UserOracleDaoImpl implements UserDao {

    public void login() {
        System.out.println("UserOracleDaoImpl.login()");
    }
}
  • 注意在UserService注入的时候,要在@Autowired下写@Qualifer(“bean的名字”)注解
@Service
public class UserService {

    @Autowired
    @Qualifier("userMySqlDaoImpl") //指定是哪个实现类
    private UserDao userDao;

    public void login(){
        userDao.login();
    }

}
  • 测试代码
ClassPathXmlApplicationContext context =  new ClassPathXmlApplicationContext("applicationContext.xml");

UserService bean = context.getBean(UserService.class);

bean.login();
  • 运行结果

UserMySqlDaoImpl.login()

5.4.Spring的5种自动装配模式

no:默认情况,不自动装配,手动设置bean

byName:根据bean 的名字自动装配

byType:根据bean的数据类型自动装配

constructor:根据构造函数的参数的数据类型,进行byType模式的自动装配

autodetect:如果发现默认的构造函数,用constructor模式,否则,用byType模式

  • 准备实体类Person、Customer
public class Customer {

    private Person person;


    public Customer(Person person) {
        System.out.println("调用构造注入");
        this.person = person;

    }

    public Customer() {
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "person=" + person +
                '}';
    }
}
public class Person {

    private String name;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 配置applicationContext.xml

no方式

<bean id="person" class = "com.tjetc.domain.Person">
   <property name="name" value="李祥"></property>
</bean>

<bean id="customer" class="com.tjetc.domain.Customer" autowire="no">
   <property name="person" ref="person"></property>
</bean>

byName方式

<bean id="person" class = "com.tjetc.domain.Person">
   <property name="name" value="李祥"></property>
</bean>

<bean id="customer" class="com.tjetc.domain.Customer" autowire="byType">
</bean>

byType方式

<bean id="person" class = "com.tjetc.domain.Person">
   <property name="name" value="李祥"></property>
</bean>

<bean id="customer" class="com.tjetc.domain.Customer" autowire="byType">
</bean>

constructor方式

<bean id="person" class = "com.tjetc.domain.Person">
   <property name="name" value="李祥"></property>
</bean>

<bean id="customer" class="com.tjetc.domain.Customer" autowire="constructor">
</bean>
5.5.注入方式总结

在这里插入图片描述

6.Spring Bean的管理

6.1Bean的scope属性

(1)singleton单例(默认)

当scope的取值为singleton时 Bean的实例化个数:1个 Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例

Bean的生命周期:

对象创建:当应用加载,创建容器时,对象就被创建了

ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);

对象运行:只要容器在,对象一直活着

对象销毁:当应用卸载,销毁容器时,对象就被销毁

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton">
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserDao userDao1 = (UserDao) app.getBean("userDao");

        UserDao userDao2 = (UserDao) app.getBean("userDao");

        System.out.println(userDao1);

        System.out.println(userDao2);

打印的userDao1、userDao2地址相同

(2)prototype多例

Bean的实例化个数:多个 Bean的实例化时机:当调用getBean()方法时实例化Bean

对象创建:当使用对象时,创建新的对象实例

UserDao userDao1 = (UserDao) app.getBean(“userDao”);

对象运行:只要对象在使用中,就一直活着

对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype">
    ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

    UserDao userDao1 = (UserDao) app.getBean("userDao");

    UserDao userDao2 = (UserDao) app.getBean("userDao");

    System.out.println(userDao1);

    System.out.println(userDao2);

打印的userDao1、userDao2地址不同

(3)request ,session和global session

这三个类型是spring2.0之后新增的,他们不像singleton和prototype那么通用,因为他们只适用于web程序,通常是和XmlWebApplicationContext共同使用

<bean id ="requestPrecessor" class="...RequestPrecessor"   scope="request" />

Spring容器,即XmlWebApplicationContext 会为每个HTTP请求创建一个全新的RequestPrecessor对象,当请求结束后,,该对象的生命周期即告结束。当同时有10个HTTP请求进来 的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且他们相互之间互不干扰,从不是很严格的意义上 说,request可以看做prototype的一种特例,除了场景更加具体之外,语意上差不多。

<bean id ="userPreferences" class="...UserPreferences"   scope="session" />

Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例,他比request scope的bean会存活更长的时间,其他的方面真是没什么区别。

<bean id ="userPreferences" class="...UserPreferences"   scope="globalsession" />

global session只有应用在基于porlet的web应用程序中才有意义,他映射到porlet的global范围的session,如果普通的servlet的web 应用中使用了这个scope,容器会把它作为普通的session的scope对待。

6.2.Spring bean生命周期回调

(1)xml文件配置方式

  • 准备一个被spring容器管理的类
public class A {

    public A(){
        System.out.println("A()...");
    }

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

    public void destroy(){
        System.out.println("A.destroy()...");
    }

}
  • 在配置文件里配置bean、初始化方法、销毁方法
<bean id="a" class="com.tjetc.domain.A" init-method="init" destroy-method="destroy"></bean>
  • 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

A bean = context.getBean(A.class);

System.out.println(bean);
context.close();
  • 运行结果

A()... A.init()... com.tjetc.domain.A@66a3ffec A.destroy()...

(2)JSR250注解@PostConstruct@PreDestroy

  • spring容器管理的类加上@Component,在初始化方法上加上@PostConstruct注解,在销毁方法上加上@PreDestroy注解
@Component
public class A {

    public A(){
        System.out.println("A()...");
    }

    @PostConstruct
    public void init(){
        System.out.println("A.init()...");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("A.destroy()...");
    }

}
  • applicationContext.xml
  <context:component-scan base-package="com.tjetc"></context:component-scan>
  • 测试代码不变
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

A bean = context.getBean(A.class);

System.out.println(bean);
context.close();
  • 运行结果

A()... A.init()... com.tjetc.domain.A@44a3ec6b A.destroy()...

(3)接口InitializingBean和DisposableBean

  • spring容器管理的类要实现InitializingBean和DisposableBean接口
@Component
public class A implements InitializingBean, DisposableBean {

    public A(){
        System.out.println("A()...");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("A.afterPropertiesSet()...");
    }

    public void destroy() throws Exception {
        System.out.println("A.destroy()...");
    }
}
  • applicationContext.xml配置基本扫描包
<context:component-scan base-package="com.tjetc"></context:component-scan>
  • 测试代码不变
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

A bean = context.getBean(A.class);

System.out.println(bean);
context.close();
  • 运行结果

A()... A.afterPropertiesSet()... com.tjetc.domain.A@4516af24 A.destroy()...

6.3.钩子关闭IOC容器
  • 使用钩子,在非WEB环境下,可以优雅的关闭IOC容器。

  • 如富客户端的桌面环境,可以向JVM注册一个钩子。即使程序非正常退出,钩子函数也会被执行,这样在钩子函数中做环境清理工作,如关闭非托管资源,就是非常有效的方法。

  • 注册一个shutdown hook,需要调用ConfigurableApplicationContext接口中的registerShutdownHook()方法。

 @Test
	public void testHook() throws Exception {
		Runtime.getRuntime().addShutdownHook(new Thread(){//2.
			@Override
			public void run() {
				System.out.println("钩子函数清除垃圾");
			}
		});
		
		ConfigurableApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
		context.registerShutdownHook();//1.在容器关闭时使用上面的钩子函数释放资源
		context.close();
		System.out.println("我要正常关闭了");
	}
6.4.BeanPostProcessor接口

bean 的后置处理接口

IOC容器生成bean对象后,在bean初始化前后,你可以通过BeanPostProcessor接口定制你的业务逻辑,如日志跟踪等。

配置BeanPostProcessor后Bean的使用过程如下:

  • 写一个类实现InitializingBean, DisposableBean接口
@Component
public class A implements InitializingBean, DisposableBean {

    public A(){
        System.out.println("A()...");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("A.afterPropertiesSet()...");
    }

    public void destroy() throws Exception {
        System.out.println("A.destroy()...");
    }
}
  • 写一个类LogBean实现BeanPostProcessor接口,重写2个方法
@Component
public class LogBean implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("LogBean.postProcessBeforeInitialization():"+bean+":"+beanName);
        return null;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("LogBean.postProcessAfterInitialization():"+bean+":"+beanName);
        return null;
    }
}
  • 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

A bean = context.getBean(A.class);

System.out.println(bean);
context.close();
  • 运行结果

A()... LogBean.postProcessBeforeInitialization():com.tjetc.domain.A@10dba097:a A.afterPropertiesSet()... LogBean.postProcessAfterInitialization():com.tjetc.domain.A@10dba097:a com.tjetc.domain.A@10dba097 A.destroy()...

6.5.FactoryBean接口
  • FactoryBean就是对一个复杂Bean的包装,可以在FactoryBean中进行初始化,然后把初始化的值传给它包装的对象。

  • FactoryBean接口在Spring framework框架自身,有大量的实现,如用于创建动态代理对象的ProxyFactoryBean。

  • 实现FactoryBean中的getObject()方法,返回真正需要的对象。

  • 首先准备一个类,用于存放数据库连接属性

public class JDBCTest {

    private String url;
    private String username;
    private String 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;
    }
}
  • 加入maven依赖,mysql,spring-jdbc
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

配置数据源和基本扫描包在applicationContext.xml

<context:component-scan base-package="com.tjetc"></context:component-scan>

<bean id = "dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource">

    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql:///test"></property>
    <property name="username" value="root"></property>
    <property name = "password" value="123456"></property>

</bean>
  • 写一个工厂类实现FactoryBean,InitializingBean接口,在初始化中完成属性的赋值
@Component
public class MyFactoryBean implements FactoryBean<JDBCTest>, InitializingBean {

    @Autowired
    private DriverManagerDataSource dataSource;
    private JDBCTest jdbcTest;

    public JDBCTest getObject() throws Exception {
        return jdbcTest;
    }

    public Class<?> getObjectType() {
        return JDBCTest.class;
    }

    public boolean isSingleton() {
        return true;
    }


    public void afterPropertiesSet() throws Exception {

        Connection connection = dataSource.getConnection();

        System.out.println("链接对象为:"+connection);

        jdbcTest = new JDBCTest();
        jdbcTest.setUrl(dataSource.getUrl());
        jdbcTest.setUsername(dataSource.getUsername());
        jdbcTest.setPassword(dataSource.getPassword());
    }
}
  • 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

JDBCTest jdbcTest = (JDBCTest) context.getBean("myFactoryBean");

System.out.println(jdbcTest.getUrl());
System.out.println(jdbcTest.getUsername());
System.out.println(jdbcTest.getPassword());
  • 运行结果

链接对象为:com.mysql.jdbc.JDBC4Connection@4b168fa9 jdbc:mysql:///test root 123456

6.6.JSR注解

(1)Spring与JSR330对应注解

Springjavax.inject.*
@Autowrid@Inject
@Component@Named/@ManagedBean
@Scope(“singleton”)@Singleton
@Qualifier@Qualifier/@Named
@Value
@Required
@Lazy
ObjectFactoryProvider

(2)@Inject 是JSR 330的注解,在使用@Autowired地方,可以使用 @Inject代替

@Service
public class UserService {

    @Inject
    private UserDao userDao;

    public void login(){
        userDao.login();
    }

}

(3)@Named或者@ManagedBean,代替Component

@Named  //@ManagedBean(需要javaee7.0 maven依赖)
public class MyFactoryBean implements FactoryBean<JDBCTest>, InitializingBean {

    @Autowired
    private DriverManagerDataSource dataSource;
    private JDBCTest jdbcTest;

    public JDBCTest getObject() throws Exception {
        return jdbcTest;
    }

    public Class<?> getObjectType() {
        return JDBCTest.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public void afterPropertiesSet() throws Exception {

        Connection connection = dataSource.getConnection();

        System.out.println("链接对象为:"+connection);

        jdbcTest = new JDBCTest();
        jdbcTest.setUrl(dataSource.getUrl());
        jdbcTest.setUsername(dataSource.getUsername());
        jdbcTest.setPassword(dataSource.getPassword());
    }
}

(4)@Resource代替@Inject、@Autowired

@Resource可以应用在属性、Set方法上,注入数据。

使用@Resource代替@Inject、@Autowired

与@Autowired相反,@Resource默认的装配方式是byName

如果不写name属性,用@Resource按名称装配,如果找不到就回退到按类型装配

如果写name属性,用@Resource(name=“userDaoMysql”)按名称装配,如果找不到就不能回退到按类型装配了

@Service
public class UserService {

    @Resource
    private UserDao userDao;

    public void login(){
        userDao.login();
    }

}

(5)@Required

必须输入,只能用在属性setter,配置文件配置,不推荐使用

(6)@Configuration @Bean

Configuration //配置类,相当于applicationContext.xml

@Bean //

@Configuration
public class MyConfig {

    @Bean
    public UserDao userDao(){

        return new UserMySqlDaoImpl();
    }

}

(7)@Primary

多个相同对象使用@Primary优先采用哪一个对象

@Repository
@Primary
public class UserOracleDao implements UserDao {

	@Override
	public void login() {
		System.out.println("UserOracleDao.login()...");
	}

}

7.Spring表达式语言(SpEL)

7.1.SpEL简介

(1)什么是表达式语言

SpEL:Spring Expression Language, Spring 表达式语言

(2)SpEL特点

SpEL是强大的表达式语言。

支持运行时查询、操纵一个对象图功能。

SpEL语言的语法类似于EL,提供了更多的功能。

SpEL是一个基于技术中立的API,允许需要时与其他表达式语言集成。

SpEL与Spring不是直接绑定关系,它可以独立存在,并应用到其它平台。

7.2.SpEL基本语法

XML配置文件中使用:#{表达式}

Bean注解中使用:@Value(“#{表达式}”)

引用其他对象属性:#{对象名.属性}

(1)算数运算符

  • 算数运算符:+, -, *, /, %, ^

1.xml中使用:#{表达式}

  • 写一个student实体类
public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 然后在applicationContext.xml中配置属性
<bean id="student" class="com.tjetc.domain.Student" >
    <property name="name" value="#{'张三'}"></property>
    <property name="age" value="#{10*2}"></property>
</bean>
  • 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

Student student = context.getBean(Student.class);

System.out.println(student);
  • 运行结果

Student{name='李四', age=18}

2.Bean注解中使用 :@Value(“#{表达式 }”)

  • 在实体类中用@Value(“#{10+8}”)注解赋值,加上@Component纳入spring容器管理
@Component
public class Student {
    @Value("李四")
    private String name;
    @Value("#{10+8}")
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • applicationContext.xml文件,配置扫描包
<context:component-scan base-package="com.tjetc"></context:component-scan>
  • 其他的代码不变

3.以用其他对象的属性:#{对象名.属性}

<bean id="a" class="com.tjetc.domain.A">
	 <property name="s" value="#{b.firstName}"></property>
	</bean>
	<bean id="b" class="com.tjetc.domain.B">
	  <property name="firstName" value=""></property>
	  <property name="lastName" value="无忌"></property>
</bean>

4.使用类的静态变量:#{T(类的全路径名).静态变量名}

<bean id="a" class="com.tjetc.domain.A">
	 <property name="s" value="#{b.firstName}"></property>
	 <property name="d" value="#{T(java.lang.Math).PI}"></property>
</bean>

5.使用类的静态方法:#{T(类的全路径名).方法名(参数)}

<bean id="a" class="com.tjetc.domain.A">
	 <property name="s" value="#{b.firstName+'  '+b.lastName}"></property>
	 <property name="d" value="#{T(java.lang.Math).max(3.1,12.3)}"></property>
</bean>

6.加号还可以作为字符串拼接

<bean id="a" class="com.tjetc.domain.A">
	 <property name="s" value="#{b.firstName+'  '+b.lastName}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B">
	 <property name="firstName" value=""></property>
	 <property name="lastName" value="无忌"></property>
</bean>

7.使用类的非静态方法#{bean的id.方法名(参数)}

public class B {
	
	public int sum() {
		return 10+20;
	}

}
<bean id="a" class="com.tjetc.domain.A">
	<property name="i" value="#{b.sum()}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B"></bean>

(2)比较运算符

<,>==,>=,<=,!=,lt(小于),gt(大于),eq(等于),le(小于等于),ge(大于等于)

 <property name="b" value="#{1 lt 1}"></property>

A [i=0, d=0.0, s=null, b=false]

(3)逻辑运算符号

and, or, not(!)

<property name="b" value="#{not(1==1 or 2==3)}"></property>

A [i=0, d=0.0, s=null, b=false]

(4)三目运算符

#{条件表达式?’true’:’false’}

 <property name="s" value="#{2>1?'大于':'不大于'}"></property>

A [i=0, d=0.0, s=大于, b=false]

(5)正则表达式

表达式说明
.除了换行符之外的任意字符
*匹配前面的子表达式零次或多次
/…/代表一个模块的开启和结束
+匹配前面的子表达式一次或多次
?匹配前面的子表达式零次或一次
{m}正好出现m次
{m,}至少m次
{,n}至少n次
{m,n}至少m次,至多n次
\d数字
\w字母数字下划线,单词
^匹配输入字符串的开始位置
$匹配输入字符串的结束位置
\s任何空白字符
\S任何非空白子符
\d匹配一个数字字符。等价于[0-9]
\D匹配一个非数字字符。等价于[ ^ 0-9]
\W匹配任何非单词字符。
  • 语法
    • #{变量名或值 matches ‘正则表达式’}
  • 例子
<bean id="a" class="com.tjetc.domain.A">
	 <property name="b" value="#{b.firstName matches '1[3578]\d{9}'}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B"></bean>

A [i=0, d=0.0, s=大于, b=true]

8.SpringAOP

8.1.SpringAOP简介

(1)什么是AOP

  • AOP:Aspect Oriented Programming 面向切面的编程

(2)AOP与OOP

  • AOP:Aspect Oriented Programming 面向切面的编程

  • OOP:Object Oriented Programming 面向对象的编程

(3)AOP与OOP的区别

  • 面向目标不同:简单来说OOP是面向名词领域,AOP面向动词领域。

  • 思想结构不同:OOP是纵向结构,AOP是横向结构。

  • 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程的某个步骤或阶段。

  • OOP与AOP联系:

    • 两者之间是一个相互补充和完善的关系。
  • AOP的优点:

    • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • Spring IOC容器不依赖AOP,如果不需要可以不导入AOP相关包。

(4)Spring AOP提供了两种模式

  • 基于XML的模式

  • 基于@AspectJ注解模式

(5)基于Spring 的 AOP,重要应用有哪些

  • 用AOP声明性事务代替EJB的企业服务

  • 用AOP做日志处理

  • 用AOP做权限控制,如Spring Security

(6)AOP中的专业术语

  • 连接点Joinpoint

    • 方法的位置
    • 程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。 Spring仅支持方法的连接点。
  • 切点Pointcut

    • 定位到方法的条件
    • 每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点。但在这为数从多的连接点中,如何定位到某个感兴趣的连接点上?AOP通过“切点”定位特定连接点。
  • 增强Advice

    • 增强是织入到目标类连接点上的一段程序代码。增强既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以SPRING所提供的增强接口都是带方位名的:BeforeAdvice,AfterReturningAdivce,throwsAdvice等等。
  • 目标对象Target

    • 增强逻辑的织入的目标类(被代理的目标类)
  • 引介Introduction

    • 引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口。通过AOP的引介功能,我们可以动态地为该业务类添加接口的实例逻辑,让业务类成为这个接口的实现类。
  • 织入Weaving

    • 织入是将增强添加对目标类具体连接点上的过程。
    • 根据不同的实现技术,AOP有三种织入方式:
    • 编译期织入,这要求使用特殊的JAVA编译器
    • 类装载期织入,这要求使用特殊的类加载器;
    • 动态代理织入,在运行期为目标类添加增强生成的方式。
  • 代理Proxy

    • 一个类被AOP增强后,就产出了一个结果类,它是整合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类。所以我们可以采用调用原类相同的方式调用代理类。
  • 切面Aspect

    • 切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SPRINGAOP就是负责实施切面的框架,它将切面所定义的横切逻辑到切面所指定的连接点中。

(6)通知的类型(5种)

  • before:前置通知(应用:各种校验)
    • 在方法执行前执行,如果通知抛出异常,阻止方法运行
  • afterReturning:后置通知(应用:常规数据处理)
    • 方法正常返回后执行,如果方法中抛出异常,通知无法执行
    • 必须在方法执行后才执行,所以可以获得方法的返回值。
  • around:环绕通知(应用:十分强大,可以做任何事情)
    • 方法执行前后分别执行,可以阻止方法的执行
    • 必须手动执行目标方法
  • afterThrowing:抛出异常通知(应用:包装异常信息)
    • 方法抛出异常后执行,如果方法没有抛出异常,无法执行
  • after:最终通知(应用:清理现场)
    • 方法执行完毕后执行,无论方法中是否出现异常
8.2.AOP编程XML方式声明

(1)XML配置文件的方式声明切面

  • 添加maven依赖
<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-context</artifactId>
  		<version>5.0.15.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.aspectj</groupId>
  		<artifactId>aspectjweaver</artifactId>
  		<version>1.9.5</version>
  	</dependency>
  • 写业务类(不写注解)
public class UserService {

    public String login(String name){

        System.out.println(name+":用户登录--login()...");

        return name;
    }

}
  • 写切面类
public class TransactionPoint {


    //增强部分
    public void before(JoinPoint joinPoint){

        Object[] args = joinPoint.getArgs();

        for (Object arg : args) {
            System.out.println("参数:"+arg);
        }

        System.out.println("前置增强");
    }

    public void afterReturning(){
        System.out.println("后置增强");
    }

    public void after(){
        System.out.println("最终增强");
    }

    public void afterThrowimg(){
        System.out.println("例外增强");
    }

    /*
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        Object proceed = null;
        try{
            System.out.println("环绕增强开始");
            proceed = pjp.proceed();
            System.out.println("环绕增强结束");
        }catch (Exception e){
            System.out.println("环绕例外增强");
             e.printStackTrace();
        }finally {
            System.out.println("环绕最终增强");
        }

        return proceed;
    }*/

}

  • applicationContext.xml配置bean和切面
<bean id="userService" class = "com.tjetc.service.UserService"></bean>

        <bean id="transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean>

        <!-- aop配置 -->
        <aop:config>
                <!--配置切面  -->
                <aop:aspect id="myaspect" ref="transactionPoint">
                        <!-- 切点 -->
                        <aop:pointcut expression="execution(* com.tjetc.service..*.*(..))" id="mycut"/>
                        <!-- 增强 -->
                        <aop:before method="before" pointcut-ref="mycut"/>
                        <aop:after-returning method="afterReturning" pointcut-ref="mycut"/>
                        <aop:after method="after" pointcut-ref="mycut"/>
                        <aop:after-throwing method="afterThrowimg" pointcut-ref="mycut"/>
                </aop:aspect>
        </aop:config>
  • 测试代码
 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = context.getBean(UserService.class);

        userService.login("LiXiang");
  • 运行结果

参数:LiXiang 前置增强 LiXiang:用户登录--login()... 后置增强 最终增强

(2)配置切面

1.切面的普通类:<bean id = "transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean>
2.<aop:config>子节点<aop:aspect id = "myaspect" ref = "personAspect">启动和增强</aop:aspect>

(3)配置切点

1.切点配置的位置
(1)aop:config下,aop:config下所有的切面都能使用该切面
(2)aop:aspect下,只能是该切面能使用该切点
2.<aop:pointcut expression=”execution(* com.tjetc.service..*.*(..))” id=”mycut”>

(4)配置增强

1.增强的配置位置
	aop:aspect下
2.增强5个
    (1)前置增强
    <aop:before method=”before” pointcut-ref=”mycut”>
    (2)后置增强
    <aop:after-returning method=”afterReturning” pointcut-ref=”mycut”>
    (3)例外增强
    <aop:after-throwing method=”afterThrowing” pointcut-ref=”mycut”>
    (4)最终增强
    <aop:after method=”after” pointcut-ref=”mycut”>
    (5)环绕增强
    <aop:around method=”around” pointcut-ref=”mycut”>

(5)后置增强的返回值

  • 第一步:在配置文件的aop:after-returning添加属性returning=”变量名”

  • 第二步:在切面类的afterReturning(Object 变量名)方法添加参数Object 变量名

  • 第三步:测试调用有返回值的方法

 <aop:after-returning method="afterReturning" pointcut-ref="mycut" returning="res"/>
public void afterReturning(JoinPoint jp,Object res) {
		System.out.println("后置通知:res="+res);
	}

(6)异常增强得到异常对象

  • 第一步:在applicationContext.xml配置aop:after-throwing的属性throwing=”ex”

  • 第二步:在切面类的afterThrowing(Exception ex)

  • 第三步:在业务类的login()方法抛出异常

  • 第四步:测试调用login()方法

<aop:after-throwing method="afterThrowing" pointcut-ref="mycut" throwing="ex"/>
public void afterThrowing(Exception ex) {
		System.out.println("例外通知:ex="+ex);
	}

(7)在增强里接受参数

  • 使用JoinPoint接口的getArgs()接受参数

  • 第一步:直接在切面类的增强方法里写JoinPoint jp参数

  • 第二步:在方法体写jp.getArgs();得到数组,遍历数组得到每一个参数的值输出.

public void before(JoinPoint jp) {
		Object[] args = jp.getArgs();
		for (Object object : args) {
			System.out.println("接收参数:"+object);
		}
		System.out.println("前置通知");
	}
8.3.AOP编程注解方式声明

(1)AOP注解方式编程

@AspectJ是一种风格样式,可以把普通的java类声明为一个切面

  • applicationContext.xml中添加AOP命名空间(1+2)
xmlns:aop="http://www.springframework.org/schema/aop"
 
http://www.springframework.org/schema/aop 
https://www.springframework.org/schema/aop/spring-aop.xsd
  • 启动@Aspect注解支持
<aop:aspectj-autoproxy/>
  • 启动注解扫包描机制
<context:component-scan base-package="com.tjetc"/>
  • 添加aspectjweaver的maven依赖
<dependency>
  		<groupId>org.aspectj</groupId>
  		<artifactId>aspectjweaver</artifactId>
  		<version>1.9.5</version>
</dependency>
  • 写切面类
@Component
@Aspect
public class TransactionPoint {

    //切点
    @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
    private void anyMethod(){}  //方法签名返回void类型


    //增强部分
    @Before("anyMethod()")
    public void before(){
        System.out.println("前置增强");
    }

    @AfterReturning("anyMethod()")
    public void afterReturning(){
        System.out.println("后置增强");
    }

    @After("anyMethod()")
    public void after(){
        System.out.println("最终增强");
    }

    @AfterThrowing("anyMethod()")
    public void afterThrowimg(){
        System.out.println("例外增强");
    }

    @Around("anyMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知开始");
        Object proceed = pjp.proceed();
        System.out.println("环绕通知结束");
        return proceed;
    }

}
  • 写业务类
@Service
public class UserService {

    public void login(){

        System.out.println("用户登录--login()...");

    }

}
  • 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

UserService userService = context.getBean(UserService.class);

userService.login();
  • 运行结果

环绕通知开始 前置增强 用户登录--login()... 后置增强 最终增强 环绕通知结束

(2)测试异常

  • 将UserService中加入除0异常
@Service
public class UserService {

    public void login(){

        System.out.println("用户登录--login()...");
        System.out.println(1/0);

    }

}
  • 测试运行

前置增强 用户登录--login()... 例外增强 最终增强 Exception in thread "main" java.lang.ArithmeticException: / by zero

没有走后置增强,直接走的例外增强。

(3)环绕增强单独使用

  • 切点类中只写around增强
@Component
@Aspect
public class TransactionPoint {

    //切点
    @Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
    private void anyMethod(){}  //方法签名返回void类型

    @Around("anyMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        Object proceed = null;
        try{
            System.out.println("环绕增强开始");
            proceed = pjp.proceed();
            System.out.println("环绕增强结束");
        }catch (Exception e){
            System.out.println("环绕例外增强");
             e.printStackTrace();
        }finally {
            System.out.println("环绕最终增强");
        }

        return proceed;
    }

}
  • 测试运行

环绕增强开始 用户登录--login()... 环绕增强结束 环绕最终增强

(4)联合使用pointcut表达式

在一个切面类中可以声明多个切点表达式,把几个切点签名用&& || !连起来使用

@Component
@Aspect//切面类=切点+增强
public class TransactionPrint {
    //切点
	@Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
	private void anyMethod1(){} //方法签名,返回值void
	@Pointcut("execution(* com.tjetc.dao..*.*(..))") //定位到连接点的条件
	private void anyMethod2(){} //方法签名,返回值void
	@Pointcut("anyMethod1() || anyMethod2()") //定位到连接点的条件
	private void anyMethod(){} //方法签名,返回值void

(5)声明Advice

本类的方法直接写方法名()

	@Before("anyMethod()")
	public void before() {
		System.out.println("前置增强");
	}

非本类的方法写类的全路径名.方法名() (确保方法签名是public能访问的;否则报错)

@Component
@Aspect
public class Transcation2 {
	
	@Before("com.tjetc.aspect.TransactionPrint.anyMethod()")
	public void before() {
		System.out.println("前置增强2");
	}

}

(6)@AfterReturning返回值

第一步:在@AfterReturning添加returning属性retruning=”方法参数的名称”

第二步:在方法里写一个参数,名称是returning属性的值,接收返回值

   @AfterReturning(value = "anyMethod()",returning = "result")
    public void afterReturning(Object result){
        System.out.println("返回值:"+result);
        System.out.println("后置增强");
    }

(7)@AfterThrowing异常

当异常发生时异常通知如何得到异常信息?

实现步骤:

第一步:在@AfterThrowing添加属性throwing=”方法参数的名称”

第二步:在方法里写一个参数,名称是throwing属性的值,接收异常对象

@AfterThrowing(value="anyMethod()",throwing="ex")
	public void afterThrowing(Exception ex) {
		System.out.println("异常通知,ex="+ex);
	}

(8)在增强里接收参数

使用JoinPoint接口的getArgs()接收参数

    @Before("anyMethod()")
    public void before(JoinPoint joinPoint){

        Object[] args = joinPoint.getArgs();

        for (Object arg : args) {
            System.out.println("参数:"+arg);
        }

        System.out.println("前置增强");
    }
8.4.JDK动态代理

*(1)JDK动态代理是java.lang.reflect.包提供的方式,它必须借助一个接口才能产生代理对象,所以要先定义接口,代码如下:

public interface HelloWord {

    void sayHelloWord();

}

(2)然后提供HelloWord的实现类

public class HelloWordImpl implements HelloWord {
    @Override
    public void sayHelloWord() {

        System.out.println("Hello Word");

    }
}

(3)这时就可以进行JDK动态代理了,先建立起代理对象和真实服务对象的关系,然后实现代理逻辑,代理类要实现java.lang.reflect.InvocationHandler接口,重写invoke()方法

public class JDKProxyExample implements InvocationHandler {

    //真实对象
    private Object target = null;

    /**
     *建立代理对象和真实对象的代理关系,并返回代理对象
     * @Param target 真实对象
     * @return 代理对象
     */

    public Object bind(Object target){

        this.target = target;
        return      Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);

    }

    /**
     * 代理方法逻辑
     * @param proxy 代理对象
     * @param method 代理方法对象
     * @param args 当前方法参数
     * @return 代理结果返回
     * @throws Throwable 异常
     */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("前置增强");
        //参数一:真实对象  参数二: 方法参数
        Object invoke = method.invoke(target, args);
        System.out.println("后置增强");

        return invoke;
    }

}

**第1步,建立代理对象和真实对象的关系.**这里是使用了bind方法创建代理对象,方法里面首先用类的属性target保存了真实对象,然后通过如下代码建立并生成了代理对象.

Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);

​ 其中newProxyInstance方法的三个参数:

  • ​ 第1个:类加载器,我们采用了target本身的类加载器.
  • ​ 第2个:把生成的动态代理对象下挂在哪些接口下,这个写法就是放在target实现接口下.
  • ​ 第3个:定义实现方法逻辑的代理类.

**第2步,实现逻辑方法.**invoke方法可以实现代理逻辑.

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

​ invoke方法的三个参数含义如下:

  • proxy,代理对象,就是bind方法生成的对象
  • method:代理方法对象
  • args:调度方法的参数

当我们使用了代理对象调度方法后,他就会进入到invoke方法里面

Object invoke = method.invoke(target, args);

这行代码相当于调用真实的对象方法,只是通过反射实现而已.

第3步,测试JDK动态代理

public static void main(String[] args) {
    JDKProxyExample jdk = new JDKProxyExample();
    //绑定关系,因为挂在接口上的实现类,所以声明代理对象HelloWord proxy
    HelloWord bind = (HelloWord) jdk.bind(new HelloWordImpl());
    //此时bind是一个代理对象,调用代理方法
    bind.sayHelloWord();
}

运行结果:

前置增强 Hello Word 后置增强

此时,在调度打印Hello Word之前和之后都可以加入相关的逻辑,甚至可以步调度Hello Word的打印.

8.5.CGLIB动态代理

JDK动态代理必须提供接口才嫩不过使用,在一些不能提供接口的环境中,只能采用其他的第三方方技术,比如CGLIB动态代理。它的优势在于不需要提供接口,只需要一个非抽象类就可以实现动态代理。

(1)我们还是用HelloWordImpl这个实现类,这次不需要接口

public class HelloWordImpl {
 
    public void sayHelloWord() {

        System.out.println("Hello Word");

    }
}

(2)采用CGLIB动态代理技术,实现MethodInterceptor接口

public class CGLIBProxyExample implements MethodInterceptor {
    	//真实类对象
        private HelloWordImpl target = null;

        public CGLIBProxyExample() {
            super();
            // TODO Auto-generated constructor stub
        }

        public CGLIBProxyExample(HelloWordImpl target) {
            super();
            this.target = target;
        }

        /**
         * 生成CGLIB代理对象
         */
        public HelloWordImpl bind(){
            Enhancer enhancer = new Enhancer();
            //指定父类,即目标类。 因为cglib原理 子类增强父类,参数为真实类的class对象
            enhancer.setSuperclass(HelloWordImpl.class);
            //设置回掉接口.参数为代理类对象
            enhancer.setCallback(this);
            //生成并返回代理对象
            return (HelloWordImpl)enhancer.create();
        }

        /**
         * @param proxy 代理对象
         * @param method 方法
         * @param args 方法参数
         * @param methodProxy 方法代理
         * return 代理逻辑返回
         * @throws Throwable 异常
         */

        @Override
        public Object intercept(Object proxy, Method method, Object[] args,
                                MethodProxy methodProxy) throws Throwable {

            System.out.println("前置增强");

            Object o=methodProxy.invokeSuper(proxy, args);
            System.out.println("后置增强");

            return o;
        }

    }

}

这里面用了CGLIB的加强者Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置那个类为它的代理类。最后调用create()方法。

(3)测试CGLIB动态代理

	public static void main(String[] args) {

		HelloWordImpl hello = new HelloWordImpl();

		HelloWordImpl h = new CGLIBProxyExample(hello).bind();

		System.out.println(h);

		h.sayHelloWord();
	}

运行结果:

前置增强 Hello Word 后置增强

掌握了JDK动态代理就很容易掌握CGLIB动态代理,因为二者是相似的。他们都是用getProxy方法生成代理对象的,制定代理的逻辑类。而二者的区别就在于一个要实现接口,一个不需要实现接口。

9.Spring事务管理

9.1.事务的分类
  • 本地事务:local transaction 使用单一资源管理器,管理本地资源。
  • 全局事务:global transaction 通过事务管理和多种资源管理器,管理多种不同的资源。
  • 编程式事务:通过编码方式,开启事务、提交事务、回滚事务。
  • 声明性事务:通过xml配置或注解,实现事务管理,Spring AOP 和 EJB都是声明性事务。
  • JTA事务:Java Transaction API ,使用javax.transaction.UserTransaction接口,访问多种资源管理器。
  • CMT事务:Container Management transaction ,通过容器自动控制事务的开启,提交和回滚。

事务策略接口:PlatformTransactionManager

事务状态接口:TransactionStatus

事务定义接口:TransactionDefinition

9.2.Spring声明性事务
XML管理声明性事务

(1)pom.xml中添加依赖

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

(2)配置文件db.properties配置数据源

jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc.mysql:///cc
jdbc.username = root
jdbc.password = 123456

(3)applicationContext.xml文件

<!--配置基本扫描包-->
<context:component-scan base-package = "com.tjetc"></context:component-scan>
<!--加载db.properties获取四大金刚的值-->
<context:properties-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean id = "dataSource" class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
    <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>
<!--配置数据源事务管理器-->
<bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name ="dataSource" ref = "dataSource"></property>
</bean>
<!--配置事务增强:tx:advice对目标的哪些方法使用事务增强-->
<tx:advice>
    <tx:attributes>
    	<tx:method name = "add*" propagation = "REQUIRED" rollback-for = "Throwable"/>
        <tx:method name = "update*" propagation = "REQUIRED" rollback-for = "Throwable"/>
        <tx:method name = "del*" propagation = "REQUIRED" rollback-for = "Throwable"/>
        <!--*代表除了上面方法之外的其他方法-->
        <tx:method name = "*" propagation = "REQUIRED" read-only = "true"/>
    </tx:attributes>
</tx:advice>

tx:method属性

属性是否需要?默认值描述
name事务属性关联的方法名,通配符(*)可以用来指定一批关联到相同的事务属性的方法。
propagationREQUIRED事务传播行为
isolationDEFAULT事务隔离级别
timeout-1事务超时的时间(以秒为单位)
read-onlyfalse事务是否只读?
rollback-for将触发进行回滚的Exception(s)
no-rollback-for不被触发进行回滚的Exception(s)

(4)Student实体类

public class Student {

    private int id;
    private String name;
    private int age;
    
}

(5)dao层

@Repository
public class StudentDao extends JdbcDaoSupport {

    @Resource
    private JdbcTemplate jdbcTemplate;

    public boolean add(Student student){

        String sql = "insert into student(name,age) values(?,?)";
        int i = jdbcTemplate.update(sql, student.getName(), student.getAge());

        System.out.println("受影响的行数:"+i);

        return i>0;
    }


}

(6)service层

@Service
public class StudentService {

    @Autowired
    private StudentDao studentDao;

    public void add(Student student){


        studentDao.add(student);
        System.out.println(1/0);
        studentDao.add(student);


    }

}

(7)测试代码

public static void main(String[] args) {

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

    StudentService studentService = context.getBean(StudentService.class);

    studentService.add(new Student("张三",18));


}
注解方式管理声明式事务

(1)pom.xml、db.properties配置文件同上

(2)applicationContext.xml文件配置tx:annotation-driven注解

 <!-- 配置扫描包 -->
    <context:component-scan base-package="com.tjetc"></context:component-scan>
    <!-- 加载从db.properties取得4大金刚的值 -->
    <context:property-placeholder location="classpath:db.properties"/>
    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <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>
    <!-- 配置数据源事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"></property>
    </bean>
   <!--tx:annotation-driven 代表可以采用@Transactional注解方式使用事务-->
<tx:annotation-driven transaction-manager = "txManager"/>

(3)业务层的类上或者方法上写@Transactional注解

  • 写在类上代表类的所有方法都是用事务

  • 写在方法上值堆该方法使用事务

@Service
// @Transactional(rollbackFor=Throwable.class)//写在类上代表类的所有方法都使用事务
public class StudentService {
	@Autowired
	private StudentDao studentDao;
     @Transactional(rollbackFor=Throwable.class)//写在方法只对该方法使用事务
	public void add(Student student) {
			studentDao.add(student);
		System.out.println(1/0);
			studentDao.add(student);
		throw new ArrayIndexOutOfBoundsException("异常回滚测试...");
	}
}

(4)测试代码

public static void main(String[] args) {
		//实例化容器对象
    	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    	//从容器得到bean
    	StudentService studentService = context.getBean(StudentService.class);
    	//调用方法
    	studentService.add(new Student("张三", 20));
	}
9.3.事务传播特性
  • PROPAGATION_REQUIRED , required , 必须 【默认值】
    • 支持当前事务,A如果有事务,B将使用该事务。
    • 如果A没有事务,B将创建一个新的事务。
  • PROPAGATION_SUPPORTS ,supports ,支持
    • 支持当前事务,A如果有事务,B将使用该事务。
    • 如果A没有事务,B将以非事务执行。
  • PROPAGATION_MANDATORY,mandatory ,强制
    • 支持当前事务,A如果有事务,B将使用该事务。
    • 如果A没有事务,B将抛异常。
  • PROPAGATION_REQUIRES_NEW , requires_new ,必须新的
    • 如果A有事务,将A的事务挂起,B创建一个新的事务
    • 如果A没有事务,B创建一个新的事务
  • PROPAGATION_NOT_SUPPORTED ,not_supported ,不支持
    • 如果A有事务,将A的事务挂起,B将以非事务执行
    • 如果A没有事务,B将以非事务执行
  • PROPAGATION_NEVER ,never,从不
    • 如果A有事务,B将抛异常
    • 如果A没有事务,B将以非事务执行
  • PROPAGATION_NESTED ,nested ,嵌套
    • A和B底层采用保存点机制,形成嵌套事务。
    • 如果当前存在事务,则在嵌套事务内执行。
    • 如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @Autowired
    private StudentDao studentDao;
    
    @Transactional(rollbackFor=Throwable.class)
	public void methodA() {
		System.out.println("methodA()...");
		studentDao.add(new Student("李四", 21));
		serviceB.methodB();
	}
}
@Service
public class ServiceB {
	@Autowired
	private StudentDao studentDao;

	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
	public void methodB() {
		System.out.println("methodB()...");
		studentDao.add(new Student("赵六", 22));
	}
}

事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。

9.4.事务的四个关键属性
  • 原子性:事务时一个原子操作,有一系列动作完成。事务的原子性确保动作要么全部完成,要么完全不起作用。

  • 一致性:一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。

  • 隔离性:可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏。

  • 持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写到持久化存储器中

9.5.事务隔离级别
  • Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。

  • Read Commited:读已提交数据(会出现不可重复读和幻读)

  • Repeatable Read:可重复读(会出现幻读)

  • Serializable:串行化

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网小阿祥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值