1、Spring

1.1简介

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。

◆目的:解决企业应用开发的复杂性

◆功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能

◆范围:任何Java应用

EJB(很复杂的大型框架)

Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。

  • 历史:
    • 2002年,推出了interface21(interface21是Spring的前身),2004年3.24诞生了Spring发布了1.0版本。
    • Rod JohnSon是Spring的创始人,他是音乐学博士(大佬)
  • Spring理念:使现有的技术更加容易使用,本身是一种融合剂,整合现有框架技术。

  • 两套框架:

    • SSH:Struct+Spring+Hibernate!
    • SSM:SpringMvc+Spring+Mybatis
  • 官网:https://spring.io/projects/spring-framework

  • 下载地址:https://repo.spring.io/release/org/springframework/spring/

  • GitHub地址:https://github.com/spring-projects/spring-framework

  • Maven地址:

    <!-- Spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.8.RELEASE</version>
    </dependency>
    <!-- JDBC-->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.8.RELEASE</version>
    </dependency>
    
    

轮子理论:不要重复造轮子!(不要在框架上重复这个框架)

1.2 Spring优点

  1. 开源免费框架(容器)
  2. 轻量级(很小),非入侵式(不会改变原来代码的情况)的
  3. 控制反转(IOC),面向切片(AOP)
  4. 支持事务处理,对框架整合的支持。

Spring是一个轻量级的控制反转和面向切面编程的开源框架

1.3 组成

1. 核心容器SpringCore: 核心容器提供Spring框架的基本功能。—-它主要的组件就是BeanFactory,是工厂模式的实现。同时BeanFactory适用控制反转(IOC)思想将应用程序的配置和依赖性规范与实际的应用程序分开。

2. Spring Context: Spring上下文是一个配置文件,主要向框架提供上下文信息。

3. SpringAop: 通过配置管理特性,SpringAOP模块直接将面向切面地编程功能集成到了Spring框架中,所以,它可以很容易地使Spring框架管理的任何对象支持AOP。SpringAOP模块也是基于Spring的应用程序中的对象提供了事务管理服务。—–比较强大的功能

4. SpringDAO: 它主要和dao层相关联,可以用该结构来管理异常处理和不同数据库供应商抛出的错误信息。*其中异常层次结构简化了错误处理,并且极大地降低了需要编写地异常代码数据(例如打开和关闭连接)。*

5. Spring ORM : Spring框架中插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

6. SpringWEB模块: Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

7. SpringMVC:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、FreeMarker、Velocity、Tiles(jsp布局)、iText(报表处理) 和 poi。

1.4 拓展

现代化开发java就是基于Spring的

  • SpringBoot:
    • 一个快速开发的脚手架
    • 基于它可以快速开发单个微服务
    • 约定大于配置
  • SpringCloud
    • 基于SpringBoot实现的

很多公司使用SpringBoot进行快速开发,学习SpringBoot前提:需要完全掌握Spring和SpringMvc

在Spring发展了多年之后,融合的框架实在太多了,虽然配置很简单,但是蚁多咬死象,配置变得十分繁琐,人称配置地狱

2、IOC

只导入SpringWeb包有一个好处,可以直接自动导入其他所有包

在以往我们写普通的MVC程序时,需要按照以下步骤进行编写

  1. UserDao接口
  2. UserDaoImpl实现类
  3. UserService接口
  4. UserServiceImpl业务实现类

这样做有一个弊端,请看这个例子:

我们想要通过SqlSever查询用户,首先我们要编写Dao接口:

public interface UserDao {
    void getUser();
}

再写他的实现类:

public class UserDaoImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("通过SqlServer获得了用户数据");
    }
}

做好之后再去写Service层

Service接口:

public interface UserService {
   void getUser();
}

Service接口实现:

我们在这里新建封装好的Dao层

public class UserServiceImpl implements UserService {
    @Override
    public void getUser() {
        UserDao userDao = new UserDaoImpl();
        userDao.getUser();
    }
}

这时候,我们的客户突然发过来一个请求,他希望使用Mysql的方式进行查询数据,好吧,我们只好照做

我们根据上面的流程,首先要多写一个Dao实现类

public class UserDaoMysqlImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("Mysql获取用户数据");
    }
}

之后还要在Service层的实现类里更改代码(最恶心最麻烦最头疼的部分)

public class UserServiceImpl implements UserService {
    @Override
    public void getUser() {
        //UserDao userDao = new UserDaoImpl();
        //这里要新创建一个Mysql的实现类
        UserDao userDao = new UserDaoMysqlImpl();
        userDao.getUser();
    }
}

但是停一下朋友,如果客户再次更改需求了呢,要求用Orcal查询用户了呢,难道我们每一次都要去这样更改Dao层代码么??

显然这是一种不好的程序格式,因此我们需要进行Set注入

我们在service的实现类里加上

public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
}

这样,当我们的用户再次更改业务需求,例如更换了十万种数据库,我们都不需要更改底层代码,我们只需要在使用时使用Set加上需要的Dao层实现类的Class对象就好了。

public void testIOC(){
    UserServiceImpl userService = new UserServiceImpl();
    userService.setUserDao(new UserDaoOrcalImpl());
    userService.getUser();
}

我们将创建对象的控制权从程序员的手中推给了运行时的代码,这就是控制反转(IOC)


IOC本质:是一种将程序主动权交给用户的一种设计思想

DI(依赖注入):是实现IOC的一种方法

3、HelloSpring

  1. 创建一个pojo
    public class Hello {
       private String str;
    
       public String getStr() {
           return str;
       }
    
       public void setStr(String str) {
           this.str = str;
       }
    
       @Override
       public String toString() {
           return "Hello{" +
                   "str='" + str + '\'' +
                   '}';
       }
    }
    
  2. 去写beans.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
           https://www.springframework.org/schema/beans/spring-beans.xsd">
       <bean id="hello" class="com.zhaox.pojo.Hello">
           <property name="str" value="Spring"/>
       </bean>
    </beans>
    
  3. 在测试中获取Spring上下文对象
    public class MyTest {
       public static void main(String[] args) {
           //获取spring的上下文对象
           ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
           /*
           * 我们的对象现在都在Spring中管理,我们要使用可以直接去里面取出来
           * */
           Hello hello = (Hello)context.getBean("hello");
           System.out.printf(hello.toString());
       }
    }
    
  4. hello是谁创建的

    是由Spring创建的

  5. hello对象的属性是怎么设置的

    是由Spring容器设置的

所谓的控制反转

控制:在使用Spring后,由Spring来创建对象

反转:程序本身不再创建对象,而变为被动的接收对象

依赖注入:利用Set方法进行注入

3.1改造之前的代码

  1. 先创建beans.xml

  2. 在beans.xml中设置bean

    <bean id="MysqlImpl" class="com.zhaox.dao.UserDaoMysqlImpl"/>
    <bean id="OrcalImpl" class="com.zhaox.dao.UserDaoOrcalImpl"/>
    <bean id="UserServiceImpl" class="com.zhaox.service.UserServiceImpl">
      <property name="userDao" ref="MysqlImpl"/>
    </bean>
    

    注意此处bean中的属性用的是ref,代表的引用,引用Spring容器中创建好的对象

3.2IOC创建对象的方式

  1. 使用无参构造创建对象,是默认的方式,必须使用无参构造函数

  2. 有参构造有三种方式

    1. 下标赋值
      <!--第一种方式,通过下标复赋值-->
      <bean id="User" class="com.zhaox.pojo.User">
        <constructor-arg index="0" value="zhaox"/>
      </bean>
      
    2. 类型
      <!--第二种方式,通过类型创建,不建议使用-->
      <bean id="User" class="com.zhaox.pojo.User">
        <constructor-arg type="java.lang.String" value="zhaox"/>
      </bean>
      
    3. 参数名
      <!--第三种方式,直接通过参数名设置-->
      <bean id="User" class="com.zhaox.pojo.User">
        <constructor-arg name="name" value="zhaox"/>
      </bean>
      

    User的实例化是在bean的创建时完成的!

4、Spring配置

4.1别名

可以把Spring中存在的bean起别名

 <!--给bean对象设置别名asdasd-->
<alias name="User" alias="asdasd"/>xml

4.2Bean的配置

bean标签中的参数:

id:bean的唯一标识符

class:bean所对应的全限定名(包名+类名)

name:也是别名,相比较于alias更高级,可以同时取多个别名(可以通过逗号,分号,空格等分割)

4.3import

如果一项目是团队开发的,每个人负责不同的类开发,需要不同的beans.xml

这时我们可以创建一个总的applicationContext.xml通过import标签,合并为一个总的。

5、依赖注入(DI)

5.1构造器注入

就是通过有参构造器注入,在前面的helloSpring中已经实现过了构造器注入

  1. 下标赋值
    <!--第一种方式,通过下标复赋值-->
    <bean id="User" class="com.zhaox.pojo.User">
       <constructor-arg index="0" value="zhaox"/>
    </bean>
    
  2. 类型
    <!--第二种方式,通过类型创建,不建议使用-->
    <bean id="User" class="com.zhaox.pojo.User">
       <constructor-arg type="java.lang.String" value="zhaox"/>
    </bean>
    
  3. 参数名
    <!--第三种方式,直接通过参数名设置-->
    <bean id="User" class="com.zhaox.pojo.User">
       <constructor-arg name="name" value="zhaox"/>
    </bean>
    

User的实例化是在bean的创建时完成的!

5.2Set方式注入

  • 环境搭建:先构建pojo,beans.xml,还有测试类
    • pojo

    • 复杂类型:

      private String address;
      
      public String getAddress() {
        return address;
      }
      
      public void setAddress(String address) {
        this.address = address;
      }
      
    • public class Student {
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobbys;
        private Map<String,String> card;
        private Set<String> games;
        private Properties info;
        private String wife;
      }
      
    • 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
            https://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--第一种,普通值注入,直接使用value-->
        <bean id="student" class="com.zhax.pojo.Student">
            <property name="name" value="zhaox"/>
        </bean>
    </beans>
    
    • 测试类
    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            Student student = (Student)context.getBean("student");
            System.out.printf(student.getName());
        }
    }
    

完善的注入信息:

<bean id="student" class="com.zhax.pojo.Student">
    <!--第一种,普通值注入,直接使用value-->
    <property name="name" value="zhaox"/>
    <!--第二种bean注入,ref-->
    <property name="address" ref="address"/>
    <!--数组注入,ref-->
    <property name="books">
        <array>
            <value>三国演义</value>
            <value>水浒传</value>
            <value>西游记</value>
            <value>红楼梦</value>
        </array>
    </property>
    <!--List注入-->
    <property name="hobbys">
        <list>
            <value>听音乐</value>
            <value>打游戏</value>
            <value>敲代码</value>
        </list>
    </property>
    <!--mao注入-->
    <property name="card">
        <map>
            <entry key="身份证" value="12356"/>
            <entry key="学生证" value="12121212121"/>
            <entry key="银行卡" value="12315135"/>
        </map>
    </property>
    <!--Set注入-->
    <property name="games">
        <set>
            <value>使命召唤</value>
            <value>我的世界</value>
            <value>COC</value>
        </set>
    </property>
    <!--空值注入-->
    <property name="wife">
        <null/>
    </property>
    <!--properties注入-->
    <property name="info">
        <props>
            <prop key="学号">1718020221</prop>
            <prop key="班级">软工171</prop>
        </props>
    </property>
</bean>

5.3拓展方式注入

可以通过两种不同的标签进行注入

5.3.1P命名空间

在xml文件加入P标签

xmlns:p="http://www.springframework.org/schema/p"
<!--P命名空间注入,通过Set方法注入-->
<bean id="user" class="com.zhax.pojo.User" p:name="zhaox" p:age="18"/>

5.3.2C命名空间

在xml文件加入C标签

xmlns:c="http://www.springframework.org/schema/c"
<!--C命名空间注入,通过构造器注入-->
<bean id="user2" class="com.zhax.pojo.User" c:age="18" c:name="狂神"

p命名空间就是通过Set方法注入

C命名空间就是通过构造器注入

两者都简化了我们的操作,可以直接通过一个标签解决问题

6、Bean的作用域

在bean标签后面可以加上scope

  1. singleton单例模式(默认)

    单例模式下的所有对象指向的都是一个对象

    <bean id="xx" class="xxxr"  scope="singleton"/>
    
  2. prototype原型模式

    每次从容器中getbean都是产生了一个新对象

    <bean id="xx" class="xxxr"  scope="prototype"/>
    
  3. request

  4. session

  5. application

  6. websocket

3、4、5、6只在web开发中使用

7、Spring中三种装配方式

Spring中三种装配方式:

  1. 在xml中显示配置
  2. 在java中显示配置
  3. 隐式的自动装配【*】

这里我们主要将自动装配

7.1自动装配

自动装配是Spring满足bean依赖的一种方式

Spring会在上下文环境中自动寻找,并自动给bean装配属性

7.2搭配环境

  1. people(一个人有两个宠物),cat,dog;编写实体类
    public class People {
       private String name;
       private Cat cat;
       private Dog dog;
       }
    
    public class Dog {
       public void shout(){
           System.out.println("wang");
       }
    }
    
    public class Cat {
    
       public void shout(){
           System.out.println("miao");
       }
    }
    
  2. 在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
           https://www.springframework.org/schema/beans/spring-beans.xsd">
    
       <bean id="cat" class="com.zhaox.pojo.Cat"/>
       <bean id="dog" class="com.zhaox.pojo.Dog"/>
       <bean id="people" class="com.zhaox.pojo.People">
           <property name="name" value="zhaox"/>
           <property name="cat" ref="cat"/>
           <property name="dog" ref="dog"/>
       </bean>
    </beans>
    

7.3ByName自动装配

在上面例子中,dog和cat都需要注入,那有没有一种办法能够自动注入呢

我们可以通过自动装配的方式实现。

首先在people的装配中删去dag和cat的装配,同时在bean标签中添加autowirs

<bean id="people" class="com.zhaox.pojo.People" autowire="byName">
    <property name="name" value="zhaox"/>
</bean>

注意:通过byName的方式自动装配,是查询了set方法后的字段,属性名为小写,set方法后的名字就是大写,但是Spring 会自动把大写转为小写,再去spring容器中查找对应id,所以一定要注意驼峰命名

7.4ByType自动装配

我们也可以通过类型来实现自动装配,但注意:前提是对象的每个类型只有一个值

<bean id="people" class="com.zhaox.pojo.People" autowire="byType">
    <property name="name" value="zhaox"/>
</bean>

小结

Byname原理:
在容器上下文中查找,对象set方法后面的值对应的beanID

​ 要保证所有bean的id唯一,bean需要和自动注入的set方法后的名字一致。

ByType原理:
在容器上下文中查找,对象属性类型相同的beanID

​ 保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致。

7.5注解实现自动装配

<?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">

    <context:annotation-config/>

</beans>    

大多数情况,我们都是使用注解开发,而不是用XML,因为XML是比较麻烦的

使用注解需要:

  1. 导入约束
    xmlns:context="http://www.springframework.org/schema/context"
    
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd">
    
  2. 配置注解的支持
    <context:annotation-config/>
    

例子:

   <bean id="dog333" class="com.zhaox.pojo.Dog"/>
   <bean id="people" class="com.zhaox.pojo.People" />
   @Autowired
   @Qualifier(value = "dog333")
   private Dog dog;

注意,我们需要将@Autowired注解放在People类的属性上,而不是Dao类和Cat类的属性上,同时我们也需要将Dog和Cat类都在配置文件里注册为bean

小结:

resource和autowired的区别:

  • 都用来自动装配
  • 都放在属性字段上
  • resouce默认通过byname方式实现,如果找不到名字通过byType实现

8、使用注解开发

平时开发中,配置文件其实并没有写这么多,更多是使用注解开发。

在Spring4之后如果想使用注解开发,必须导入AOP的包。

需要导入context注解 ,增加注解支持

  1. 导入约束
    xmlns:context="http://www.springframework.org/schema/context"
    
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd
    
  2. 配置注解的支持
    <context:annotation-config/>
    
  3. 包扫描,可自动扫面具体包下的类并进行标签装配
    <!--指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.zhaox"/>
    
  4. 整合
    <?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">
    
    <context:component-scan base-package="com.zhaox"/>
       <context:annotation-config/>
    
    </beans> 
    

使用注解开发:

  1. bean
    @Component:
    组件,放在pojo类上,说明这个类被spring管理了,等价于<bean id = "user class="com.zhaox.pojo.user"/>
    
  2. 属性注入
    @Value("zhaox")
    相当于<property name = "name" value="zhaox"/>
    为bean注入属性
    基本放在set方法上,或者属性上
    注意,基本只有简单类型可以注入,如果是复杂类型,还是推荐配置文件注入
    
  3. 衍生的注解

    @Component有一些衍生注解,在web开发中,我们会按照三层架构分层

  • dao【@Repository】
  • service【@Service】
  • controller【@Controller】

    四个注解功能都是一样的,都是将类注册到Spring中装配(成为bean)

  1. 自动装配

    @autowire:
    实现自动装配
    直接在属性上用,也可以在set方式上使用(甚至可以不用编写set方法)
    这个标签内的属性required可以为true或false
    如果为false说明这个对象可以为空,否则不允许为空
    
    @Qualifier:
    如果自动装配环境复杂,比如上面猫狗例子中有多个猫狗,通过id无法分清谁是谁,这时我们可以通过这个标签
    指腚唯一的bean对象
    
    @Resource:
    同样实现自动装配
    这是java的原生注解,几乎兼容了spring的自动装配注解
    此注解会先去通过id查找注入
    如果id无对应,则可以通过类型注入(类型必须唯一)
    此注解内也有属性name,可以给name赋值,这样就可以解决类型不唯一,名字也都不匹配的问题。
    
  2. 作用域
    @Scope("singleton")
    @Scope("prototype")
    可以设置某个类的作用域
    
  3. 小结

    xml与注解相比:

  • xml更加万能,适用于任何场景,维护方便
  • 注解不是自己的类无法使用,维护相对复杂

    最佳:xml负责bean,注解只负责注入属性

8.1 基于java实现xml配置Spring

可以不使用Spring的XMl配置了,全权交给java来做

在spring4之后,它变成了核心功能

首先是xml配置文件的变化:

在首行加入@Configuration,代表这是一个配置类(配置类的本质也是一个@Component)

@Configuration
public class ZhaoxConfig {
    @Bean
    //方法名字就是 bean 标签中的id,方法的返回值就是
    public User user(){
        return new User();
    }
}

然后是实体类的变化:

依旧是在类上加入Component,依旧是使用Value注册值

@Component
public class User {
    @Value("zhaox")
    private String name;

    public String getName() {
        return name;
    }

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

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

最后是测试类的变化:

ApplicationContext context = new AnnotationConfigApplicationContext(ZhaoxConfig.class);

我们不再需要CPX获取上下文环境了,取而代之的是AnnotationConfigApplicationContext来获取环境

9、代理模式

为什么要学习代理模式?

因为他就是SpringAOP的底层!【SpringAOP和SpringMVC时面试必问!】

代理模式的分类:

  • 静态代理
  • 动态代理

9.1静态代理

角色分析:

  • 抽象角色:一般使用接口或者抽象类来实现
  • 真实角色:被代理人
  • 代理角色:代理真实角色
  • 客户:访问代理对象的人

代码步骤:

  • 接口
    public interface Rent {
      public void rent();
    }
    
  • 真实角色(房东)
    //房东
    public class Host implements Rent{
      @Override
      public void rent() {
          System.out.println("房东出租房子");
      }
    }
    
  • 代理角色(中介)
    public class Proxy implements Rent{
      //多用组合,少用继承
      private Host host;
    
      public Proxy(Host host) {
          this.host = host;
      }
      public Proxy() {
      }
    
      @Override
      public void rent() {
          host.rent();
          System.out.println("代理已经帮房东租房子了");
      }
    
      //看房
      public void seeHouse(){
          System.out.println("代理带你看房");
      }
    
      //收中介费
      public void fare(){
          System.out.println("代理收中介费");
      }
      //签合同
      public void hetong(){
          System.out.println("代理签租赁合同");
      }
    
    }
    
  • 客户
    public class Client {
      public static void main(String[] args) {
          Proxy proxy = new Proxy(new Host());
          //代理,中介帮房东租房子,代理角色会有附加操作,比如收中介费,签合同
          //不用面对房东直接找中介租房子
          proxy.seeHouse();
          proxy.hetong();
          proxy.fare();
          proxy.rent();
      }
    }
    

代理模式的好处:

  • 可以使真实角色的操作更加纯粹

  • 公共业务交给代理角色,实现业务分工

  • 公共业务发生扩展的时候方便集中管理

    • 不在原来的impl实现的原因:
    1. 为了保证角色单一
    2. 不要改动原有业务代码

代理模式的缺点:

  • 一个真实角色就会产生一个代理角色
  • 代码量翻倍,开发效率变低

9.2动态代理

为什么需要动态代理

首先先明确为什么要有动态代理,因为之前我们所说过的静态代理模式缺点

一个真实角色就会产生一个代理角色

这实在是太费劲了,试想一下如果你拥有成万的真实角色,而我们需要手动一个个敲出他们的代理角色,不寒而栗兄弟们

动态代理如何解决这个问题的

先明确问题:

  • 真实角色需要一个代理角色
  • 需要手动创建代理角色
  • 不想手动创建
  • 创建代理需要new
  • new出一个角色需要用Class对象进行初始化
  • 希望直接通过Class对象创建对象
  • 不写哪来的Class对象?

这里先小扩展一下这里的Class对象与初始化之间的小知识点


一个对象的出生
类的加载

众所周知,在java中,万物皆是对象,其中类的加载是指jvm将class文件放入内存,也相当于把class中的类,一个个放在内存里(摆立整的!)

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆区生成一个代表这个类的java.lang.Class对象.(JVM规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在方法区中

注意:在此时会扫描到我们的代码中是否有静态变量或者是静态方法等等这些静态数据结构,还未分配内存。

类的元数据才是存在方法区的。

方法区生成全部静态数据,并在堆中生成一个Class类的对象,代表这是一个类

注意:每一个类都是java.lang.class对象!
写好的类的数据结构被存放在方法区,然后生成一个Class对象存放在堆,之后new出来的新对象都是通过这个堆里的Class对象来的

类的链接

将Java类的二进制代码合并到JVM的运行状态之中的过程。

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  • 准备:正式为类变量(static) 分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。准备阶段主要为类变量分配内存并设置默认的初始值。这些内存都在方法区分配。注意此时就会为我们的类变量也就是静态变量分配内存,但是*普通成员*变量还没。但如果一个变量是常量(被 static final 修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

实际上Java代码编译成字节码之后,最开始是没有构造方法的概念的,只有类初始化方法 对象初始化方法 。

类初始化
  • 执行类构造器< clinit> ()方法的过程【初始化方法】方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  • 虚拟机会保证一 个类的 ()方法在多线程环境中被正确加锁和同步。
  • 初始化时候才会为我们的普通成员变量赋值。

到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。
Java程序对类的使用方式可分为主动使用与被动使用。只有当对类的首次主动使用的时候才会导致类的初始化,所以主动使用又叫做类加载过程中“初始化”开始的时机。
类的主动使用包括以下六种:

类主动使用的六种情况
创建类的实例,也就是new的方式
访问某个类或接口的静态变量,或者对该静态变量赋值(凡是被final修饰不不不其实更准确的说是在编译器把结果放入常量池的静态字段除外)
调用类的静态方法
反射(如 Class.forName(“com.gx.yichun”))
初始化某个类的子类,则其父类也会被初始化
Java虚拟机启动时被标明为启动类的类( JavaTest ),还有就是Main方法的类会首先被初始化
类初始化方法

编译器会按照其出现顺序,收集:类变量(static变量)的赋值语句静态代码块,最终组成类初始化方法。类初始化方法一般在类初始化的时候执行。

对象初始化方法(构造方法)

编译器会按照其出现顺序,收集:成员变量的赋值语句普通代码块,最后收集构造函数的代码,最终组成对象初始化方法,值得特别注意的是,如果没有监测或者收集到构造函数的代码,则将不会执行对象初始化方法。对象初始化方法一般在实例化类对象的时候执行。


这里我们可以看到,一个代理对象要想诞生,首先就需要Class对象,因为Class对象中包含我们所需要的信息,包括构造函数,方法,字段等等

那我们可不可以从别的地方COPY过来一个Class对象呢??

答案是可以的,让我们回看到静态代理模式中去:

  • 房东,中介,一个真实角色,一个代理角色,两者有一个共同的接口

这个接口中的信息,就有我们所需要的信息。

但是别忘了,接口是无法创建对象的,怎么办?

知乎上有一个生动形象的例子

用通俗的话说,getProxyClass()这个方法,会从你传入的接口Class中

“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象的。

打个比方,一个大内太监(接口Class),空有一身武艺(类信息),但是无法传给后人。

现在江湖上有个妙手神医(Proxy类),发明了克隆大法(getProxyClass),不仅能克隆太监的一身武艺,还保留了小DD(构造器)...(这到底是道德の沦丧,还是人性的扭曲,欢迎走进动态代理)
链接:https://www.zhihu.com/question/20794107/answer/658139129

这里的大太监就是我们的接口,我们将接口的信息彻底复制下来,形成一个新的Class对象,这样我们就可以通过Class对象获得他的构造方法,从而能够新建对象了。

现在,我们已经能够自动创建一个代理对象了,但是桥豆麻袋。

结束了么??

显然还没有,我们在这里还要探讨一下这个代理对象是如何实现调用真实角色的方法

调用真实角色的方法

我们可以看见在创建代理对象的方法时后面多了一个InvocationHandler和其invoke方法。

这个InvocationHandler其实就是辅助我们去执行对应真实角色方法的一个处理器

我们在根据代理Class的构造器创建对象时,传入InvocationHandler。

通过构造器传入一个引用,那么必然有个成员变量去接收。

所以在我们这个代理内部里,其实有一个InvocationHandler对象,在我们执行对应方法的时候,其实就是这个对象在调用它的invoke方法

这个方法中包含三个参数:

  • proxy参数传递的即是代理类的实例
  • method是调用的方法,即需要执行的方法
  • args是方法的参数

我们根据调用方法的反射去执行方法,内部传入真实角色的对象和方法参数,就可以执行这个方法了。

9.3静态代理与动态代理与AOP

静态代理与动态代理

代理,指的就是将原本真实角色要做的事情交给别人去做,同时在前或后做一些别的事情,比如日志

静态代理与动态代理最大的区别,就是静态代理类一个是事先写好的,程序员已经编写好了对应真实角色的代理,等着去调用就行了。

这种静态代理最大的弊端就是会成倍的增加代码量,比如输出一句正在调用什么方法,你需要每个方法里都写一句输出,为了解决这个困难,可以使用动态代理。

动态代理,就是将代理类的生成自动化,使用反射的机制生成代理类,我们用JDK的动态代理来举个例子:

InvocationHandler在这个接口中,我们使用newProxyInstance方法来生成代理类,其中我们只需要提供对应的类加载器,真实角色所实现的接口,还有生成代理类方法的类本身这三个参数,就可以直接创建一个代理类,我们通过这个代理类来执行真是角色的一些方法,这时会调用反射机制也就是invoke方法,我们可以在invoke方法里写上一些附加的供能

动态代理与AOP

为什么我们说aop也实现了动态代理呢?

仔细看Spring的AOP实现。

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}

首先写好服务接口,然后写一个impl实现类,这个实现类对应我们的真实角色。

在spring中,找到切点,并且对着特定的切点下特定的通知(方法),这个过程很像JDK动态代理的Invoke

<aop:config>
    <!--切入点:需要在哪里执行我们的方法expression:表达式,execution(要执行的位置)-->
    <aop:pointcut id="pointcut" expression="execution(* com.zhaox.service.UserServiceImpl.*(..))"/>
    <!--执行环绕增加-->
    <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>

</aop:config>

接下来在测试类中,我么就要去找到我们的代理了,那么谁是我们的代理呢?在JDK动态代理中,我们有单独的方法来创建代理,具体的方式也是通过多态,得到一个类型为接口类型的代理

 Rent proxy = (Rent) proxyInvocationHandler.getProxy();

但是在Spring中,我们是无需建立对象的,需要的对象都变成了bean,我们只需要获得UserServiceImpl的bean,并转为接口类型

UserService bean = context.getBean("userService",UserService.class);

总结:

SpringAOP与动态代理的关系:

  1. 两者都不需要手动创建代理类
  2. 两者都是通过反射获取方法及对应类的名字
  3. 都是通过多态上转型为对应接口

总的来说,AOP其实是实现了动态代理的一种方式,这种方式的核心思想就是动态的创建代理,通过反射得到方法及类的信息,并进行一些操作

10、AOP

10.1 什么是AOP

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

面向切片编程

10.2AOP在Spring中的作用

提供声明式事务,允许用户自定义切面

  • 横切关注点:我们需要关注的部分,比如上方的例子中,日志和验证等方法就是跨越了应用程序多个模块的方法,他们就是需要注意的横切关注点。
  • 切面:将横切关注点模块化形成的
  • 通知:切面要完成的工作,即切面的方法
  • 目标:被通知对象(接口或者一个方法
  • 代理:通知之后创建的对象(代理类
  • 切入点:在哪里执行
  • 连接点:在哪里执行

10.3使用Spring实现AOP

导入AOP约束及依赖
  1. AOP依赖
    <dependencies>
       <dependency>
           <groupId>org.aspectj</groupId>
           <artifactId>aspectjweaver</artifactId>
           <version>1.9.6</version>
       </dependency>
    </dependencies>
    
  2. AOP约束
    xmlns:aop="http://www.springframework.org/schema/aop"
    
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd
    

第一种方式

我们使用原生springAPI接口

我们首先写两个日志类

public class BeforeLog implements MethodBeforeAdvice {
    @Override
    //method:要执行的目标对象的方法
    // object:参数
    // object:就是target
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {

    //returnValue:返回值
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+method.getName()+"返回结果为:"+returnValue);
    }
}

他们分别实现了MethodBeforeAdvice接口和AfterReturningAdvice接口,这会在下面进行AOP配置并使用advisor时自动寻找到插入的地方

<!--使用原生的api接口-->
<!--配置AOP-->
<aop:config>
    <!--切入点:需要在哪里执行我们的方法expression:表达式,execution(要执行的位置)-->
    <aop:pointcut id="pointcut" expression="execution(* com.zhaox.service.UserServiceImpl.*(..))"/>
    <!--执行环绕-->
    <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>

</aop:config>

首先定义一个切入点,这个切入点就是我们需要找的要在那个方法前或者后进行插入,这里使用了execution表达式,很简单格式是【类型】 【方法全路径】【方法名】【参数】

这里*代表所有.代表任意

切入点:poincut,决定在哪个方法执行切入execution(类型 方法的全路径(参数))

通知:advisor,将写好的类,切入到切入点上

第二种方式

还记得我们之前说过切面的概念,每一次去调用Spring的API果然太麻烦了,这里我们可以自己自定义一个切面。

public class DiyPointCut {
    public void before(){
        System.out.println("方法执行强");
    }
    public void after(){
        System.out.println("方法执行后");
    }
}

然后在配置文件里将这个类注册为bean

<bean id="diy" class="com.zhaox.diy.DiyPointCut"/>

在AOP配置里选择切面,同样声明切入点,然后这次会发现多了一些标签,包括before,after之类的,在里面引用方法就好了

<aop:config>
    <aop:aspect ref="diy">
    <!--切入点-->
        <aop:pointcut id="point" expression="execution(* com.zhaox.service.UserServiceImpl.*(..))"/>
    <!--通知-->
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
    </aop:aspect>
</aop:config>

注解实现AOP

见过前两种方式,我们自然会想到另一种方式——注解。

没错,与装配bean一样,AOP也有自己的注解开发方式。

首先我们需要在配置文件种开启AOP注解支持

<aop:aspectj-autoproxy/>

同时我们需要将自定义的类注册到bean里

<bean id="annotationPointCut" class="com.zhaox.diy.AnnotationPointCut"/>

如下是自定义类的文件

@Component
@Aspect
public class AnnotationPointCut {
    @Before("execution(* com.zhaox.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("====方法执行前====");
    }
    @After("execution(* com.zhaox.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("====方法执行后====");
    }

}

这里我们使用Aspect声明其为切面,在方法前使用before或者after声明切入点(还是使用execution表达式)

关于第二种方式与第三种方式

这两种方式有个弊端,就是没法像第一种方式那样使用反射获取方法及调用对象的信息

我们可以通过Around(环绕增强)方法来实现:(这里使用注解来演示)

@Around("execution(* com.zhaox.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {

    Object proceed = joinPoint.proceed();
}

这个环绕增强与第一种方法相差无几,这里是通过注册了这个类在其内部实现方法执行

joinPoint.proceed();//实现真实角色的方法执行

在这句话之前之后可以加入一些函数

另外我们可以通过这个接入点获取一些关于方法的信息

System.out.println(joinPoint.getSignature().getName());//

11、整合Mybatis

  1. 导入依赖
    1. mysql
      <!--spring操作数据库需要一个Spring-jdbc-->
      <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>8.0.21</version>
      </dependency>
      
    2. junit
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
      </dependency>
      
    3. mybatis
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
      </dependency>
      
    4. spring
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.8.RELEASE</version>
      </dependency>
      
    5. aop
      <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
      </dependency>
      
    6. Spring-mybatis
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.5</version>
      </dependency>
      
    7. 整合
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
      </dependency>
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
      </dependency>
      <!-- Spring-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.8.RELEASE</version>
      </dependency>
      <!--spring操作数据库需要一个Spring-jdbc-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.8.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
      </dependency>
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.5</version>
      </dependency>
      
  2. 编写配置文件

  3. 测试

11.1 回忆Mybatis

  1. 编写实体类
  2. 编写核心配置文件
  3. 编写接口
  4. 编写Mapper.xml
  5. 测试

11.2 Mybatis-Spring

首先要说:

JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,

它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序

如果一个项目中如果需要多个连接,如果一直获取连接,断开连接,这样比较浪费资源,如果创建一个池,用池来管理Connection,这样就可以重复使用Connection。

有了池我们就不用自己来创建Connection,而是通过池来获取Connection对象。

当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了。

这里我们常用的连接池有两种,分别是:DBCP连接池和C3P0连接池

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = context.getBean("userMapper", UserMapper.class);    

//关于这里为什么用UserMapper接口而不是实现类,因为我们是面向接口编程,用接口来上转型
  • 整理思路:
    1. 首先,相比较原来单独mybatis,使用Spring是为了节省我们新建对象的麻烦,那么为了省去这些麻烦,比如SqlSessionFactory,读取配置文件,创建Sqlssion等,我们需要将他们配置到Spring的配置文件里

    2. Spring配置文件首先需要两个东西,第一个是DataSouece,spring操作数据库需要一个Spring-jdbc包,这一步的目的是将原来mybatis核心配置文件里的环境配置挪到这里

      <!--DateSource:使用Spring的数据源替换Mybatis的配置   c3p0,dbcp druid
       这里使用Spring提供的jDBC
      -->
      <bean id="dataSouece" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
       <property name="url"
                 value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
       <property name="username" value="root"/>
       <property name="password" value="zhaosiyu123"/>
      </bean>
      
    3. 第二个东西,是SqlSessionFactory,使用SqlSessionFactoryBean包,这里来绑定上方的数据源以及mybatis的核心配置文件,以及Mapper映射器,也可以在这里绑定
      <!--SqlSessionFactory-->
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
       <property name="dataSource" ref="dataSouece"/>
           <!--绑定Mybatis的配置文件-->
       <property name="configLocation" value="classpath:mybatis-config.xml"/>
       <property name="mapperLocations" value="classpath:com/zhaox/mapper/*.xml"/>
      </bean>
      
    4. 有了SqlSessionFactory,我们接下来就需要创建SqlSession,SqlSession在Spring里是一个SqlSessionTemplate对象,他没有set方法,所以只能用构造函数注入,参数就是SqlSessionFactory,相当于原来mybatis里sqlSessionFactory.openSession();这一步。
      <!--就是我们使用的SqlSessionTemplate-->
      <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
       <!--只能使用构造器注入,因为他没有Set方法-->
       <constructor-arg index="0" ref="sqlSessionFactory"/>
      </bean>
      
    5. 在原来进行测试的时候,我们还是需要新建mapper类,因此在Spring里我们就需要新建一个专门用来操作mapper的类,也就是mapper实现类。我们通过在实现类里设置一个SqlSessionTemplate属性,并设置set方法,方便我们在bean里注册(初始化时为其赋值)
      public class UserMapperImpl implements UserMapper {
       //我们的所有操作原来都使用SqlSession来来执行,现在都使用SqlSessionTemplate
       private SqlSessionTemplate sqlSession;
      
       public void setSqlSession(SqlSessionTemplate sqlSession) {
           this.sqlSession = sqlSession;
       }
      
       public List<User> selectUser() {
           UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           return mapper.selectUser();
       }
      }
      
    6. 万事俱备,只欠东风,准备测试类直接调用方法就可以了

  • 简化操作:

    1. DateSource,SqlSessionFactory(设置,别名优化最好留在Mybatis)
    2. 使用XXXXTemplate:创建SqlSession(以后会遇到,Redis,Thyme等)
    3. 将Mybatis的配置和工具类里SqlSessionFactory的一些操作全部丢给Spring,让Spring得到一个整合的SqlSessionTemplate,在这个类里之前的全部动作都被整合
    4. 创建一个接口实现类,这个类里有之前的SqlSessionTemplate,还需要一个Set方法方便注入,这样可以直接得到SqlSessionTemplate
    5. 获得Mapper,使用Mapper直接使用方法
  • Mybatis与Mybatis-Spring的区别:
    1. 后者可以将Mybatis 的配置文件完全挪走
    2. 后者可以完全不需要新建对象,只需要再Spring中配置bean就可以了
    3. 但是这就需要我们新建一个实现类,因为原来我们调用得到的mapeer是一个接口类型,而Spring是不支持创建一个接口bean的!所以我们需要创建一个实现类,封装mapper的操作,然后再由Spring注册这个实现类,通过实现类来进行方法的调用

11.3Mybatis-Spring(使用)

在上面的整合中,我们需要在实现类里注入一个SqlsessionTemplate,这有一些麻烦,这里我们可以让实现类继承一个类SqlSessionDaoSupport。

继承之后我们可以直接使用这个父类的方法得到SqlSession,当然sqlSessionFactory也是需要注入的

<bean id="userMapper2" class="com.zhaox.mapper.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

这样我们就不需要SqlsessionTemplate来获得SqlSession了。

12、事务

12.1回顾事务

  • 要么都成功,要么都失败
  • 因为涉及到数据的一致性问题,十分重要
  • 确保完整性,一致性

事务的ACID:

  • 原子性(事物不可再分)
  • 一致性(从一个状态切换到另一个状态)
  • 隔离性(一个操作不会影响另一个操作)
    • 多个业务操作一个资源,要保证事务互相隔离,防止数据损坏
  • 持久性(造成的结果是永久的)
    • 事务一旦提交,无论发生什么问题,结果都不会再受影响,被持久的写到存储器中

这四个性质保证了事务的ACID

为什么需要事务?

  • 如果不配置事务,可能存在数据不一致的问题
  • 如果不在Spring中配置事务,我们就需要在代码中手动配置事务。
  • 事务在开发中十分重要,涉及到数据的一致性和完整性。

12.2Spring中的事务管理

  • 声明式事务
  • 编程式事务(会改变原有代码)

开启事务管理器

<!--配置声明式事务-->
<bean id="transactionManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSouece"/>
</bean>

结合AOP实现AOP织入(可以看作切面方法或者环绕增强的方法)

<!--结合AOP实现AOP织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManger">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播特性:propagation-->
    <!--这里可以写add*,delete*等-->
    <tx:attributes>
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="select*" read-only="true"/>
    </tx:attributes>
</tx:advice>

配置事务切入

<!--配置事务切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.zhaox.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

步骤总结:

  1. 先导入tx和aop的约束和依赖
  2. 首先开启事务管理器
  3. 进行AOP织入,对哪些方法怎么使用事务进行配置(propagation)
  4. 接下来就是进行事务切入,确定要切入的方法,确定切入的tx和切入点

在测试类里事务不成功的问题

在测试事务的时候发现事务不会回滚,排查了半天并没有发现哪里出现了问题

在搜索引擎查了半天,最后一篇博客的这一句话点醒了我

Spring中的事务基于AOP实现,则事务类必须被Spring管理,进行代理,才能支持事务。

对啊,如果这个类没有被spring管理,那根本不可能出现什么切片,切入点,因为这个类“跳出三界外,不在五行中”啊

回想以下AOP,我们需要这个类被Spring管理,然后在一个切入点,加上我们自定义的切面方法或者环绕增强。

现在我们在测试类里写各种数据库操作,可是测试类是没有被管理的啊,这个切入点根本不成立,我们的测试类根本没有被事务管理“包住!

于是我将插入删除写在了查询上,再进行测试,果然事务成功了

Sprng中的propagation七种事务配置

propagation传播

在声明式的事务处理中,要配置一个切面,其中就用到了propagation,表示打算对这些方法怎么使用事务,是用还是不用,其中propagation有七种配置,REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。默认是REQUIRED

REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。

REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

注解

  1. @autowire:
    实现自动装配
    直接在属性上用,也可以在set方式上使用(甚至可以不用编写set方法)
    这个标签内的属性required可以为true或false
    如果为false说明这个对象可以为空,否则不允许为空
    
  2. @Nullable :
    可以让属性为空,即使为空也不报错
    
  3. @Qualifier:
    如果自动装配环境复杂,比如上面猫狗例子中有多个猫狗,通过id无法分清谁是谁,这时我们可以通过这个标签
    指腚唯一的bean对象
    
  4. @Resource:
    同样实现自动装配
    这是java的原生注解,几乎兼容了spring的自动装配注解
    此注解会先去通过id查找注入
    如果id无对应,则可以通过类型注入(类型必须唯一)
    此注解内也有属性name,可以给name赋值,这样就可以解决类型不唯一,名字也都不匹配的问题。
    
  5. @Component:
    组件,放在pojo类上,说明这个类被spring管理了,等价于<bean id = "user class="com.zhaox.pojo.user"/>
    
  6. @Value("zhaox")
    相当于<property name = "name" value""zhaox/>
    为bean注入属性
    基本放在set方法上,或者属性上
    注意,基本只有简单类型可以注入,如果是复杂类型,还是推荐配置文件注入
    
  7. ```作用域设置
    @Scope("singleton")
    @Scope("prototype")
    可以设置某个类的作用域
    ```

  8. @Configuration:
    当使用java类进行xml配置的时候可以使用这个注解进行注册bean,在config类上加这个注解
    
  9. @ComponentScan("com.zhaox")
    这个注解用于扫描包,在不使用配置文件配置applicationContext.xml时,可以用这个标签进行扫描
    
  10. @import
    这个注解类似于Spring配置中的import标签
    

死代码

基础beans.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

测试类获得容器上下文

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

通过C命名空间注入配置

在xml文件加入C标签C

xmlns:c="http://www.springframework.org/schema/c"
<!--C命名空间注入,通过构造器注入-->
<bean id="user2" class="com.zhax.pojo.User" c:age="18" c:name="狂神"

通过P命名空间注入配置

在xml文件加入P标签

xmlns:p="http://www.springframework.org/schema/p"
<!--P命名空间注入,通过Set方法注入-->
<bean id="user" class="com.zhax.pojo.User" p:name="zhaox" p:age="18"/>

导入Context约束(使用注解开发)

  1. 导入约束
    xmlns:context="http://www.springframework.org/schema/context"
    
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd
    
  2. 配置注解的支持
    <context:annotation-config/>
    
  3. 整合
    <?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">
    
       <context:annotation-config/>
    
    </beans> 
    

导入AOP约束及依赖(以及开启AOP使用注解开发)

  1. AOP依赖
    <dependencies>
       <dependency>
           <groupId>org.aspectj</groupId>
           <artifactId>aspectjweaver</artifactId>
           <version>1.9.6</version>
       </dependency>
    </dependencies>
    
  2. AOP约束
    xmlns:aop="http://www.springframework.org/schema/aop"
    
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd
    
  3. 开启注解开发
    <aop:aspectj-autoproxy/>
    

导入tx约束(事务)

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd

配置事务

开启事务管理器

<!--配置声明式事务-->
<bean id="transactionManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSouece"/>
</bean>

结合AOP实现AOP织入

<!--结合AOP实现AOP织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManger">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播特性:propagation-->
    <!--这里可以写add*,delete*等-->
    <tx:attributes>
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="select*" read-only="true"/>
    </tx:attributes>
</tx:advice>

配置事务切入

<!--配置事务切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.zhaox.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

醉后不知天在水,满船清梦压星河