文章目录

  • 1、 Spring 简介
  • 1.1 Spring 的优点
  • 1.2 Spring的组成
  • 2、IOC思想
  • 我的一点拙见:
  • 3、Spring 初体验
  • 3.1 导入maven依赖
  • 3.2 在resources文件夹下建立applicationContext.xml文件
  • 3.3 书写代码进行测试
  • 4、IOC创造对象的方式
  • 5、Spring配置
  • 5.1 别名
  • 5.2 Bean的配置
  • 5.3 import
  • 5.4 依赖注入(DL)
  • 什么是依赖注入
  • 5.5 拓展注入
  • 6、bean的作用域
  • 7、Bean的自动装配(自动注入)(自动依赖autowired)
  • 7.1 隐式的自动装配
  • 7.2 注解实现spring的自动装配
  • 7.3 使用spring注解进行开发
  • 8、使用java代码配置spring
  • 9、静态代理模式
  • 9.1 静态代理
  • 9.2 动态代理
  • 10、AOP的实现方式
  • 10.1 AOP的几大常用概念
  • 10.2 AOP的java代码
  • execution表达式
  • 方式一,通过Spring的API接口
  • 方式二,自定义切入类
  • 方式三,通过spring注解
  • 11、整合Mybatis
  • 12、声明式事务


1、 Spring 简介

  • Spring 提供了企业级开发java项目中所需要的所有东西
  • Spring 从Framework 5.1 开始,需要JDK 8 以上的版本支持
  • Spring是开源的,于2003年问世,是JavaEE的补充
  • Spring保持了强大的向后兼容性。

1.1 Spring 的优点

  1. Spring是一种非侵入式(non-invasive)框架,它可以使应用程序代码对框架的依赖最小化。
  2. 方便解耦、简化开发
  • Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理,大大的降低了组件之间的耦合性。
  1. 支持AOP , 提高了程序的复用性。
  • Spring提供了对AOP的支持,它允许将一些通用任务,如安全、事物、日志等进行集中式处理,从而提高了程序的复用性。
  1. 方便程序的测试
  • Spring提供了对Junit4的支持,可以通过注解方便的测试Spring程序。
  1. 方便集成各种优秀框架
  • Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持。
  1. 降低Jave EE API的使用难度。
  • Spring对Java EE开发中非常难用的一些API(如JDBC、JavaMail等),都提供了封装,使这些API应用难度大大降低。

1.2 Spring的组成

spring由几十个组件构成,分别被整合在核心容器(Core Container)、数据访问/集成部分(Data Access/Integration)、AOP(Aspect Oriented Programming)、Aspects/设备支持(Instrumentation)、Web、报文发送(Messaging)、Test等模块中。

总结:Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

两大部分:IOC和AOP

2、IOC思想

什么是控制反转?

  • 由程序员主动的new对象,转换成 交给一个创建该对象的工厂来创建
  • 由主动转换为被动
  • 将创建对象的权力交给程序(或者是其他人) 来创建对象的过程叫做控制反转

什么是描述?

描述就是XML文件或者注解,是指用来定义或对某个对象或功能的解释

为什么需要IOC思想

在任何一个有实际开发项目,我们会使用很多类来描述它们特有的功能,并且通过类与类之间的相互协作来完成特定的业务逻辑。

这个时候,每个类都需要管理与自己有交互的类的引用和依赖,代码会变的异常难以维护和极度的高耦合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xh6YwGor-1639967248902)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1614659912659.png)]

IOC思想的本质

而IOC的出现正是用来解决这个问题:

==我们通过IOC将这些相互依赖对象的创建、协调工作交给spring容器去处理。==每个对象只需要关注其自身的业务逻辑关系就可以了。

在这样的角度上来看,获得依赖的对象的方式,进行了反转,变成了由spring容器控制对象,并处理对象业务

IOC通俗讲解

通俗点说许多应用都是通过彼此间的相互合作来实现业务逻辑的,如类A要调用类B的方法,以前我们都是在类A中,通过自身new一个类B,然后在调用类B的方法,现在我们把new类B的事情交给spring来做,在我们调用的时候,容器会为我们实例化。

我的一点拙见:

IOC就像一个工厂,假设在A对象中需要调用B对象的方法,此时需要在A对象中new出B对象,B对象中的方法才能调用,那么A对象就可以称为**“离不开B对象”**,那么这样的离不开会导致A对象和B对象之间存在高耦合性,为了解耦,使用IOC容器作为所有对象之间的一个调度人,通过调度人可以很方便的调节对象之间的关系。

例如:A对象需要B对象的一个方法,那么IOC中创建出B对象,A对象将数据传递给B对象,B对象执行其中的方法后,再将方法获得的数据传递给A对象,那么这两个对象实际上是平行的关系,可以互相离开。

3、Spring 初体验

3.1 导入maven依赖

<dependency>  
    <groupId>org.springframework</groupId> 
    <artifactId>spring-webmvc</artifactId> 
    <version>5.2.7.RELEASE</version>
</dependency>

这个包包含了spring的大部分依赖。导入这一个包其他常用的就已经自动依赖了

3.2 在resources文件夹下建立applicationContext.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    <!--一个Bean元素就代表了一个类,将类注册到容器中-->
    <bean  name="user" class="com.hr.pojos.User" >
        <!--依赖注入,这个代表了对象的属性,赋值本质上是通过set方法赋值,所以set方法不能有一丝差错-->
        <property name="username" value="我是由spring创建的一个对象"/>
    </bean>
    <bean name="Book" class = "com.hr.pojos.Book">
    <!--设置当前属性的引用值是一个对象,对象要用ref,不能用value-->
        <property name="bookUser" ref="user"/>
    </bean>
</beans>

要想进行属性注入,必须在那个类中要有set方法!!!

name和id的区别,id只能代表有一个,而name可以有多个

3.3 书写代码进行测试

import com.hr.pojos.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void Test(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        User user = (User) app.getBean("user");
        System.out.println(user.getUsername());
    }
}

控制台结果:

我是由spring创建的一个对象

Process finished with exit code 0

4、IOC创造对象的方式

  • 默认通过无参构造器构造对象,如果一个javaBean没有无参构造器,那么将直接报错
  • 最基本的对象创建方式,只需要有一个无参构造函数和字段的set方法。本质上就是使用无参构造器创建对象,然后使用set方法为字段赋值。
  • 可以通过有参构造器构造对象
    根据参数类型赋值==(不推荐)==
<bean id="user" class="com.hr.User">
        <constructor-arg type="java.lang.String" value="张三"/>
        <constructor-arg type="int" value="18"/>
        <constructor-arg type="com.hr.Book" ref="dog"/>
</bean>

根据参数位置赋值

<bean id="user" class="com.hr.User">
        <constructor-arg index="0" value="张三"/>
        <constructor-arg index="1" value="18"/>
        <constructor-arg index="2" ref="dog"/>
</bean>

直接根据参数名(构造器里的参数名)设置

<bean id="user" class="com.hr.User">
        <constructor-arg name="name" value="张三"/>
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="book" ref="dog"/>
</bean>

总结:Spring创建对象的方式,当一个bean在spring中注册后,就会在容器中通过无参构造器实例化出这个对象(此时如果在无参构造器中输出语句,会在控制台中看到),这个对象始终存在,需要时可以随时召唤,并且召唤的对象始终如一,不会发生变化**(默认是单例,可以通过配置改Bean的作用域)**

5、Spring配置

5.1 别名

给当前javaBean对象起一个别名,并且只能一对一,说实话,用途不大,不如用bean的name配置

<alias name="user" alias="user2"/>

即可通过user2召唤出这个对象

5.2 Bean的配置

  • id:Bean唯一标识,可以通过bean召唤对象
  • class:bean对象所在的类
  • name:同别名,而且name可以取多个别名
<bean  name="user,user33" class="com.hr.pojos.User" />

5.3 import

有三个人写了不同的三个applicationContext.xml,每个 applicationContext.xml 文件中有不同的javaBean,可以将三个applicationContext.xml 导入到一个applicationContext.xml文件中,可以通过import,

例:

<import resource = "applicationContext.xml-zhangsan"/>

5.4 依赖注入(DL)

其实就是通过xml文件给对象中的属性或对象赋值,大概有三种方式:

  1. 构造器注入
  2. set方法注入【非常重要】
  3. 拓展方式注入
什么是依赖注入

其实就是set注入,依赖:bean对象的创建都依赖于容器。注入:bean对象中的所有属性,都由IOC容器来注入

为各种java类型实现注入,例如String、int、String[]、List、Map<string,string>等等

下面以一个具体的实例来观察spring是如何对各种java属性进行注入的。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
    <!--一个Bean元素就代表了一个类,将类注册到容器中-->

    <bean id="refermy" class="com.hr.pojos.Refer"/>

    <bean  id="Uility" name="uility" class="com.hr.pojos.Uility" >
        <!--第一种,最简单,简单的类型直接通过property进行注入-->
        <property name="name" value="实体姓名"/>
        <!--第二种,注入一个自定义的对象,需要在上方已经创建好自定义对象的Bean-->
        <property name="refer" ref="refermy"></property>
        <!--第三种,数组注入-->
        <property name="book">
            <array>
                <value>第一本书</value>
                <value>第二本书</value>
                <value>第三本书</value>
            </array>
        </property>
        <!--第四种,list注入-->
        <property name="hobbys">
            <list>
                <value>唱歌</value>
                <value>跳舞</value>
                <value>打豆豆</value>
            </list>
        </property>
        <!--第五种,map注入-->
        <property name="map">
            <map>
                <entry key="一键关键字" value="一键值"></entry>
                <entry key="二键关键字" value="二键值"></entry>
            </map>
        </property>
        <!--第六种,set集合注入-->
        <property name="games">
            <set>
                <value>一个游戏</value>
                <value>两个游戏</value>
            </set>
        </property>
        <!--第七种,空值注入-->
        <property name="isnull">
            <null/>
        </property>
        <!--第八种,Properties属性注入-->
        <property name="properties">
            <props>
                <prop key="username">root</prop>
                <prop key="password">123.com</prop>
                <prop key="url">jdbc:mysql://localhost:3306/bdcs</prop>
                <prop key="driver">com.mysql.jdbc.Driver</prop>
            </props>
        </property>
        <!--第九种-->
    </bean>

</beans>

配置完成后进行junit测试

@Test
    public void testSpring(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

        Uility uility = (Uility) app.getBean("uility");
        System.out.println(uility.getRefer());
        System.out.println(uility.getName());
        //System.out.println(uility.getBook());
        System.out.println(uility.getBook()[0]);
        System.out.println(uility.getBook()[1]);
        System.out.println(uility.getBook()[2]);
        System.out.println(uility.getGames());
        System.out.println(uility.getHobbys());
        System.out.println(uility.getIsnull());
        System.out.println(uility.getMap());
        System.out.println(uility.getProperties());

    }

控制台结果

Refer{age=0, name='null'}
实体姓名
//[Ljava.lang.String;@33065d67
第一本书
第二本书
第三本书
[一个游戏, 两个游戏]
[唱歌, 跳舞, 打豆豆]
null
{一键关键字=一键值, 二键关键字=二键值}
{password=123.com, driver=com.mysql.jdbc.Driver, url=jdbc:mysql://localhost:3306/bdcs, username=root}

进程已结束,退出代码 0

5.5 拓展注入

通过p命名空间和c命名空间注入

  • p命名空间:本质上就是将标签化成了bean标签上的属性,需要在beans标签的属性中写入以下代码:xmlns:p=“http://www.springframework.org/schema/p”
  • ,直接通过这种方式进行注入
  • c命名空间:本质上就是将constructor-arg构造器函数化成了bean标签上的属性,需要在beans标签的属性中写入以下代码:xmlns:c=“http://www.springframework.org/schema/c”
  • ,直接通过这种方式进行构造器注入

6、bean的作用域

bean scope:其实就是决定了一个spring在实例化这个对象“出生”的时候,是如何产生的,是可以在在堆中创建不同的对象,还是在堆中这个对象始终如一的存在(单例模式),还是其他的决定bean所在作用域的方式…

<bean id="refermy" class="com.hr.pojos.Refer" scope=""/>
  • singleton:单例模式,只有一个对象存在
<bean id="refermy" class="com.hr.pojos.Refer" scope="singleton"/>
  • prototype:原型模式,允许多个对象存在
  • request:在一次请求中创建,请求失效后,这个bean对象也跟着失效
  • session:在一个session中创建,session失效后,这个session对象也失效
  • application:在整个应用层面一直存活

7、Bean的自动装配(自动注入)(自动依赖autowired)

在Spring中有三种装配的方式:

  1. 在xml中直接通过property标签进行配置
  2. 用java代码进行配置
  3. 隐式的自动装配bean**(重要)**
7.1 隐式的自动装配

Spring会在xml上下文中自动寻找相应的name,并自动给bean装配属性

正常代码

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

      <bean name="driver" class="com.hr.beans.Driver"/>
      <bean name="passenger" class="com.hr.beans.Passenger"/>

      <bean name="bus" class="com.hr.beans.Bus">
            <property name="driver" ref="driver"></property>
            <property name="passenger" ref="passenger"></property>
      </bean>

</beans>

自动装配的代码

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

      <bean name="driver" class="com.hr.beans.Driver"/>
      
		<!--自动装配模式设置为通过name属性寻找上下文中的bean-->
      <bean name="bus" class="com.hr.beans.Bus" autowire="byName">
        <!--
			<property name="driver" ref="driver"></property>
            <property name="passenger" ref="passenger"></property>
		-->
      </bean>
		<!--写到这里也可-->	
      <bean name="passenger" class="com.hr.beans.Passenger"/>
</beans>

byName的原理:会自动查找这个bean属性中和其set属性后面值相同的bean,然后注入

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

      <bean name="driver" class="com.hr.beans.Driver"/>

		<!--byType通过set属性返回的类型来自动注入-->
      <bean name="bus" class="com.hr.beans.Bus" autowire="byType">
            <!--<property name="driver" ref="driver"></property>
            <property name="passenger" ref="passenger"></property>-->
      </bean>
      <bean name="passenger" class="com.hr.beans.Passenger"/>

</beans>

byType的原理:通过这个bean属性返回的数据类型和xml文件中相同类型的bean进行配对

优点:

  • 可以不用name属性

缺点:

  • byType时必须保证只有一种类型的bean,不能有两种相同类型的bean,即class名唯一
7.2 注解实现spring的自动装配

@Autowired*的使用来消除 setget方法

如果想要用注解,必须添加注解支持,在application-config.xml文件中进行配置下面这句话,同时spring版本必须大于2.5

<context: annotation-config/>

xml代码实例

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

    <!--开启注解的支持-->
    <context:annotation-config/>

    <bean class="java.lang.String" name="hobby"></bean>
    <!--给基本类型也要配置bean,以便下述的自动装配-->
    <bean class="com.hr.beans.Age" name="age"></bean>
    <bean class="com.hr.beans.Sex" name="sex"></bean>
   <bean class="com.hr.beans.User" name="user"></bean>
</beans>

注解:

@Autowired:可以在属性名上使用,也可以在set方法中使用,使用**@Autowired**的好处是,可以不用给此属性设置set方法,但前提是,此属性在spring的配置文件中有配置,即是是基本数据类型,也要配置,否则还是无法省略set方法

package com.hr.beans;

import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;

public class User implements Serializable {

    @Autowired
    private Age age;
    @Autowired
    private Sex sex;
    @Autowired
    private String hobby;//基本数据类型也必须在xml文件中进行bean的配置


    public Age getAge() {
        return age;
    }

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

    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }

    public void getData(){
        age.setAge(10);
        sex.setSex("女");
        System.out.println("这个用户的年龄是"+age.getAge()+",性别是"+sex.getSex());
    }
}

**科普:**使用@Nullable注解可以允许被标记的属性为空

其中,@Autowired()有一个参数require,当require为false时**@Autowired(require =false)**,说明此对象可以为空,否则不允许为空

注意:@Autowired自动装配的对象,是根据其类型和bean中的字段类型相同才能成功映射,否则失败

默认按类型匹配,类型匹配不成功时才会用name属性进行匹配

如果有多个相同类型的bean时,想要具体选择某个时,可以通过@Qualifier(“name值”)

@Autowired
    @Qualifier("age2")
    private Age age;
<bean class="com.hr.beans.Age" name="age"></bean>
    <bean class="com.hr.beans.Age" id="age2"></bean>

@Resouce注解更为高级,它集合了@Autowired和@Qualifier的功能,当按照bean的名字查找不到时就会按照类型查找


7.3 使用spring注解进行开发

前提:要是非常简单的可以使用注解,复杂的建议还是使用xml文件进行配置

在spring4之后,使用注解开发就必须导入aop的包,还是相同的,要想使用注解,就必须导入注解的支持

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

    <!--指定那个包下的注解会生效-->
    <context:component-scan base-package="com.hr.beans"/>
    <!--开启注解的支持-->
    <context:annotation-config/>

</beans>

然后就可以写java代码了

package com.hr.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.Serializable;
@Component//这里相当于<bean class="com.hr.beans.User" name="user"/>
public class User implements Serializable {

    @Autowired
//    @Qualifier("age2")
    private Age age;
    @Autowired
    private Sex sex;
    @Value("跳舞")//这里相当于bean中的property注入
    private String hobby;

    public void getData(){
        age.setAge(10);
        sex.setSex("女");
        System.out.println("这个用户的年龄是"+age.getAge()+",性别是" +
                ""+sex.getSex()+",爱好是"+hobby);
    }
}

衍生的注解

spring为了用户更好的实现mvc三层架构,为了让用户实现起来更加一目了然,给@Component注解起了一些不同的名字

  • dao层下的Component名字改为了 【@Repository】
  • service层下的Component名字改为了 【@Service】
  • controller层下的Component名字改为了 【@Controller】

这四个注解的功能完全相同

还有一个表示bean作用域的注解 :@Scope(),注解不同的值代表了不同的作用域范围

如果是singleton表示单例,如果是prototype表示原型

总结

spring使用的最佳方式:注册bean还交给xml文件去做,属性的注入交给注解去做

8、使用java代码配置spring

暂定~新特性,原理就是用一个java的class文件替代了xml文件,直接用java代码和注解结合的方式配置spring,关键的两个注解,一个是@Configuration一个是@bean

9、静态代理模式

什么是代理模式: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法 。


Spring AOP的底层其实就是代理模式。

代理模式分类:

  • 静态代理
  • 动态代理

9.1 静态代理

这里拿房东和中介举例,一个房东,只需将卖房和租房的任务交给中介,中介负责买房的任务,如果有人想要访问房东,那么只需要和中介产生联系即可,即中介成为了房东和租房客之间的一个桥梁。

中介,就是代理人。

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.

静态代理的代码过程:

  1. 抽象角色
  2. 真实角色
  3. 代理角色
  4. 客户端访问代理角色

代理模式的好处:

  • 可以使真正的角色更加纯粹,不需要有其他的任务,任务全部交给中介去做
  • 实现了任务的分工
  • 当任务发生拓展时,可以交给中介去做

缺点:

  • 一个真正的角色就会有一个代理人,所以代码量会变多

静态代理的深层次之我的理解:在代码方面,假设一个类中有很多的属性和方法,这是已经写好的业务逻辑代码,此时想要拓宽或增加业务逻辑代码,可以在不改变原型的基础上再创建一个新类,这个新类可以有原型的对象,然后通过调用方法实现原型逻辑的同时再去实现需要拓展的业务,这样既不改变原型的代码模式,又实现了需求,方便后悔的同时还使代码更加简洁明了,耦合性降低了。

9.2 动态代理

动态代理的本质是反射,或者说,动态代理的代理角色都是反射形成的。

动态代理分为三大类:

  1. 基于接口的动态代理——>jDK动态代理
  2. 基于类的动态代理——>cglib
  3. java字节码实现——>javasist

学习动态代理有两个类,一个Proxy类,一个InvocationHandler接口

  • Proxy类的作用:用来通过反射动态生成代理类
  • InvocationHandler接口:调用代理类中相应的方法

Rent,接口

package com.hr.pojos;

public interface Rent {

    public void rent();

    public void modify();
}

Host类,实类

package com.hr.pojos;

public class Host implements Rent{

    @Override
    public void rent() {
        System.out.println("我要租房了!!!");
    }

    @Override
    public void modify() {
        System.out.println("我要修改房子价钱!!!");
    }
}

集代理人生成,和代理人生成器一体的工具类

package com.hr.pojos;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
这里相当于写成了一个创建代理对象的工具类,不过有一定的限制,因为是工具类,所以只有许多类都有的部分,没法单独为某个角色拓宽相应的功能,method固定死了,只能执行方法,不能干其他事情
 */
public class MyInvocationHandler  implements InvocationHandler {

//    真正的角色(即要为目标生成代理类的角色)
    private Object target;

    public void setRent(Object target) {
        this.target = target;
    }

    //    生成相应对象的代理对象,三个参数,1、要生成代理的对象的ClassLoader 2、真正角色继承的接口,注意,一定要是接口【非常重要!!!!!!!!!!!!!!!!!】 3、要用那个处理器
    public Object getProxy(){
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
        return  proxy;//这个返回的对象即是目标对象的代理对象
    }

//    invoke方法用来调用生成代理的方法,共有三个参数,参数一:那个代理对象 参数二:要调用那个
//    代理对象的方法  参数三:代理对象方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);//这个result即是方法执行后的返回值
        return result;
    }
}

上面的代码是最重要的,其中invoke方法又采用了java中的一种模式,叫做适配器模式,适配器模式在javaGUI中曾采用过,即相应的动作会触发这个模式,当代理对象调用方法时,就会将proxy调用的方法当作参数传递到invoke方法中

测试环境

public static void main(String[] args) {
        MyInvocationHandler invent = new MyInvocationHandler();
        Rent host = new Host();

        invent.setRent(host);

        Rent proxy = (Rent) invent.getProxy();//这个proxy即是host的代理人,这里返回的是接口类型

        proxy.modify();
        proxy.rent();
        System.out.println(proxy.getClass()+":"+host.getClass());
    }

**总结:**代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理

10、AOP的实现方式

AOP本质上就是一种使类与类之间解耦的技术,这种技术暂且称之为**“横切”**

它剖解开封装的对象内部,将那些影响了多个类的公共部分封装成一个模块,这个模块可以称之为**“Aspect”“切面”**

所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

==AOP的作用:==将核心代码和非核心代码进行分离,在核心代码中切入非核心代码,主要项目中就是完成事物和日志的记录的

10.1 AOP的几大常用概念

  • 横切关注点:跨越应用程序多个模块的方法或功能。通俗来讲,就是与我们业务逻辑无关的,但是我们还都需要关注的部分(即没有他也可以,但是没有他会影响开发过程),这种模块就叫做**“横切关注点”**
  • 例如:日志,安全,缓存,事务等等
  • 切面(Aspect):横切关注点 被抽象出来的那个公共类
  • 通知(Advice):切面必须完成的工作,其为公共类中的一个方法
  • 目标(Target):即要被代理的对象
  • 代理(Proxy):即代理对象
  • 切入点(PointCut),连接点(JointPoint):即在何处执行

10.2 AOP的java代码

Spring只支持XML方式而没有实现注解的方式(注解方式也叫AspectJ方式)的AOP,所以要使用@Aspect注解,只能引入AspectJ相关的 jar 包:aopalliance-1.0.jar 和 aspectjweaver.jar

具体maven的依赖如下:

<!-- Spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.8.RELEASE</version>
    </dependency>

    <!--spring aop + aspectj-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.9</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
</dependency>

ok,包也导完了,那么接下来就是spring的几大实现aop的方式

execution表达式

在实现aop的过程中,首先要了解一些知识点,比如execution表达式

execution (* com.sample.service.impl…*(…))

execution()是最常用的切点函数,其语法如下所示:

整个表达式可以分为五个部分:

1、execution(): 表达式主体。

2、第一个星号:表示返回类型,星号表示所有的类型。

3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

4、第二个号**:表示类名,*星号表示所有的类。

5、*(…):最后这个星号表示方法名,*星号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。


方式一,通过Spring的API接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toTUY6tg-1639967248903)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1615602151728.png)]

package com.hr.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"返回的结果为:"+returnValue);
    }
}
package com.hr.log;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
      <!--注册bean,接口无法注册为bean-->
      <bean id="userService" class="com.hr.Entitys.UserServiceImpl"></bean>
      <bean id="log" class="com.hr.log.BeforeLog"></bean><!--切面-->
      <bean id="afterLog" class="com.hr.log.AfterLog"/><!--切面-->

      <aop:config>
            <aop:pointcut id="pointcut" expression="execution(* com.hr.Entitys.UserServiceImpl..* (..))"></aop:pointcut>

            <!--执行环绕增加,将log类中重写的方法代码切入到UserServiceImpl的所有方法执行之前执行
				将afterLog类中重写的方法代码切入到UserServiceImpl的所有方法执行完后在执行
			-->
         	 <!--这个类继承自那个类,就决定了通知的执行顺序-->
            <aop:advisor advice-ref="log" pointcut-ref="pointcut"></aop:advisor>
            <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"></aop:advisor>
      </aop:config>
</beans>

方式二,自定义切入类

第二种切入的方式没有第一种强大,因为第一种可以操作被切入对象的方法

package com.hr.aop_class;

public class AopDemo1 {
    void before(){
        System.out.println("这里是aop的before方法,在执行具体方法之前执行,可以设立日志");
    }

    void after(){
        System.out.println("这个是aop的after方法,在执行具体方法之后执行,可以设立总结");
    }

}
<!--第二种,自定义类-->
      <bean id="aop" class="com.hr.aop_class.AopDemo1"></bean>

      <aop:config>
      <!--自定义切面,哪个类当作切面切入,ref要引用的类-->
            <aop:aspect ref="aop"><!--切面,这里的ref=aop引用的是上面自定义好的类-->
            <!--切入点,即设立被切入的类的具体方法-->
                  <aop:pointcut id="pointcut" expression="execution(* com.hr.Entitys.UserService..*(..))"/>
            <!--通知,即切面中的方法要在何时执行,是在被切面之前执行还是之后?-->
                  <aop:before method="before" pointcut-ref="pointcut"></aop:before>
           <!--这里的spring标签决定了执行时间,method的方法必须和类中的方法名保持一致-->
                  <aop:after method="after" pointcut-ref="pointcut"></aop:after>
            </aop:aspect>
      </aop:config>
</beans>

方式三,通过spring注解

代码结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WeZFkOjR-1639967248905)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616921834053.png)]

首先,需要开启注解支持

aop:aspectj-autoproxy/

然后需要指定切面类,利用@Aspect标签指定切面类,利用注解实现何时执行切面的方法

xml文件中的配置

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

    <bean id="logaop" class="com.hr.logs.MyLog"/>
    <bean id="myservice" class="com.hr.services.MyService"/>
    <aop:aspectj-autoproxy/>
    <!--<aop:config>
        <aop:aspect ref="logaop">
            <aop:pointcut id="pc" expression="execution(* com.hr.services..*(..))"/>
            <aop:before method="beforeExecuteAdvice" pointcut-ref="pc"/>
            <aop:after-returning method="afterReturnAdvice" pointcut-ref="pc"/>
        </aop:aspect>
    </aop:config>-->
</beans>

java代码中的切面配置

package com.hr.logs;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyLog {

    @Before("execution(* com.hr.services..*(..))")
    void beforeExecuteAdvice(){
        System.out.println("方法执行前,您需要知悉一些内容");
    }
    @AfterReturning("execution(* com.hr.services..*(..))")
    void afterReturnAdvice(){
        System.out.println("方法执行后,您还需要总结");
    }

    @Around("execution(* com.hr.services..*(..))")
    void aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {//环绕执行的方法,通过参数pjp可以获得执行方法的信息
        System.out.println("around方法执行前");
        pjp.proceed();
        System.out.println("around方法执行后");
    }
}

myservice中的代码

package com.hr.services;

public class MyService {

    public void executeDemo(){
        System.out.println("这个代码正在执行中,请稍等三秒钟");
        for (int i = 3; i > 0; i--) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("执行剩余时间:"+ i);
        }
    }
}

测试类中的写法

@org.junit.Test
    public void testAop(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyService myservice = applicationContext.getBean("myservice", MyService.class);
        myservice.executeDemo();
    }

结果

around方法执行前
方法执行前,您需要知悉一些内容
这个代码正在执行中,请稍等三秒钟
执行剩余时间:3
执行剩余时间:2
执行剩余时间:1
方法执行后,您还需要总结
around方法执行后

11、整合Mybatis

首先需要导入一些额外的包

spring操作数据库的话,还需要一个spring-jdbc用来操作数据库

<dependency>
	<groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.9RELEASE</version>
</dependency>

spring和Mybatis整合,需要一个mybatis-spring

<dependency>
	<groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.2</version>
</dependency>

12、声明式事务