Spring详解(控制反转(IOC)与面向切面编程(AOP))

Spring 概念

1、spring于2002年出现,为解决企业开发的难度,减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2、spring的核心技术
(1)IOC:控制反转(Inversion Of Control)
(2)AOP:面向切面编程(Aspect Oriented Programming)
作用:能够实现模块之间、类之间的解耦合
3、依赖
classA 中 使用classB中的属性或方法,叫做classA依赖classB,spring可以对依赖进行管理。
4、spring的优点
(1)轻量:spring使用的jar包较小,spring的核心功能在3M左右。
(2)针对接口编程、解耦合:spring提供了IOC控制反转,由容器管理对象,对象之间的依赖关系,原来在程序代码中的对象的创建方式,现由容器完成对象之间的解耦合。
5、spring框架的体系结构
在这里插入图片描述

控制反转(Inversion Of Control,简称:IOC)

1、IOC是什么?

(1)IOC是一种概念、一种理论思想。描述的是把对象的创建、赋值、管理工作交给代码之外的容器实现的,也就是对象的创建由其他外部资源完成的。
控制:创建对象,对象的属性赋值,对象之间的关系管理。
正转:由开发人员使用new关键字创建对象,由开发人员主动管理。Person p = new Person();
反转:把原来开发人员管理、创建的对象的权利转移给代码之外的容器实现。由容器代替开发人员管理对象、创建对象、给属性赋值。
容器:是一个服务器软件,一个框架。
(2)为什么要使用IOC?
目的就是减少对代码的改动,也能够实现不同的功能,实现解耦合
(3)Java中创建对象的方式有哪些?
a、构造方法:new关键字
b、反射机制
c、序列化
d、克隆
e、IOC:容器创建对象
f、动态代理

2、IOC思想的体现(Tomcat容器对Servlet对象的管理)

Servlet
(1)创建类继承HttpServlet
(2)在web.xml注册Servlet,使用如下代码

<servlet-name>myServlet</servlet-name>
<servlet-class>com.packagename.controller.MyServlet</servlet-class>

(3)没有创建对象,没有使用new关键字MyServlet ms = new MyServlet();
(4)Servlet是Tomcat服务器创建的,Tomcat也成为容器。Tomcat作为容器,里面存放的有Servlet对象、listener对象、Filter对象

3、IOC的技术实现

(1)DI(依赖注入,Dependency Injection)是IOC的技术实现
(2)DI:依赖注入,只需要在程序中提供使用的对象名称就可以拿到对象就是DI,至于对象如何在容器中创建、管理、赋值、查找都有容器内部实现
(3)spring使用DI实现了IOC的功能,spring底层创建对象使用的是反射机制
(4)spring是一个容器,管理对象,给属性赋值,底层是反射创建对象

4、由spring的实现对象的创建、管理
(1)创建一个maven项目
在这里插入图片描述
(2)在pom.xml文件中引入spring的jar包

<!--spring的依赖-->
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
</dependency>

(3)在service(业务层)中创建SomeService及其实现类SomeServiceImpl

public interface SomeService {
    void doSome();
}
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("执行了SomeServiceImpl的doSome()方法");
    }
}

(4)在resources目录下创建spring的beans.xml(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">


    <!--告诉spring创建对象
        声明bean。就是告诉spring要创建某个类的对象
        id:对象的自定义名称,唯一值。spring通过这个名称找到对象
        class:类的全限定名称(不能是接口,因为spring底层是通过反射机制创建对象,必须使用类)
    -->

    <!--
        1、spring就完成SomeService someService = new SomeServiceImpl();
        2、spring是把创建好的对象放入map中,spring框架有一个存放对象的map。
            2.1、springMap.put(key,value);其中:key是id的值,value是对象
            2.2、例如:springMap.put("someService",new SomeServiceImpl());
        3、一个bean标签声明一个对象。
    -->
    <bean id="someService" class="com.spring.service.SomeServiceImpl"/>

</beans>
<!--
     spring 的配置文件
     1、beans:是根标签,spring把java对象当做bean
     2、spring-beans.xsd 是约束文件,和mybatis中的dtd是一样的,xsd约束能力较强
-->

(5)在单元测试类中,可以使用如下代码进行测试

    /*spring获取beans.xml对象的方法测试*/
    /**
     * 1、spring 默认创建对象的时间:在创建spring容器时,会创建配置文件中所有的对象
     * 2、spring 创建对象默认调用的是无参构造方法
     * 3、spring 可以创建自定义类或者非自定义类的对象
     */
    @Test
    public void test02() {
        //使用spring容器创建对象
        //1、指定spring配置文件的名称
        String config = "beans.xml";
        //2、创建表示spring容器的对象,ApplicationContext
        //ApplicationContext就是表示Spring容器,通过容器就可以获取对象了
        //ClassPathXMLApplicationContext:表示从类路径中加载spring的配置文件
        //调用容器
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        //从容器中获取对象,调用对象的方法
        //getBean("配置文件中的bean的id的值")
        //获取对象
        SomeService someService = (SomeService) context.getBean("someService");
        //使用spring创建好的对象
        someService.doSome();
    }

依赖注入(Dependency Injection,简称:DI)

1、在spring的配置文件中,给Java对象的属性赋值。
2、依赖注入(DI):表示创建对象,给属性赋值。
3、DI的实现语法有两种(注入就是赋值的意思):
(1)在spring配置文件中,使用标签和属性来完成,叫做基于XML的DI实现。
(2)使用spring中的注解,完成属性赋值,叫做基于注解的DI实现。
4、DI的语法分类:
(1)set注入(设值注入):spring调用类的set方法,在set方法可以实现属性的赋值。(80%左右都是使用set注入)
(2)构造注入:spring调用类的构造方法,创建对象。在构造方法中完成赋值。
5、spring容器中存放对象的容器使用的是Map

基于XML配置文件的DI(依赖注入,Dependency Injection)

1、在spring配置文件中,使用标签和属性来完成,叫做基于XML的DI实现。
2、set注入(设值注入):spring调用类的set方法,在set方法可以实现属性的赋值。

set注入(设值注入)

1、简单类型的属性的set注入

<bean id="xx" class="yyy">
        <property name="属性名字" value="此属性的值"/>
             //一个property只能给一个属性赋值
        <property....>
</bean>

2、引用类型的属性的set注入

<bean id="xxx" class="yyy">
       <property name="属性名称" ref="bean的id(对象的名称)"/>
</bean>

1、创建项目
在这里插入图片描述

2、引入spring依赖

<!--spring的依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

3、创建Student类

public class Student {
    private String name;
    private int age;
    private School school;

    public void setName(String name) {
        System.out.println("setName:" + name);
        this.name = name;
    }

    public void setAge(int age) {
        System.out.println("setAge:" + age);
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

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

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

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

4、DI注入:applicationContext.xml配置文件
注:spring在调用默认的无参构造方法后,开始通过set注入,即对象的属性赋值,必须有set方法。

<?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">
    <!--声明student对象
        注入:就是赋值的意思
        简单类型:spring中规定基本数据类型和字符串都是简单类型
        DI:给属性赋值
        1、set注入(设置注入):spring调用类的set方法,可以在set方法中完成属性赋值
            (1)简单类型的set注入
            <bean id="xx" class="yyy">
                <property name="属性名字" value="此属性的值"/>
                //一个property只能给一个属性赋值
                <property....>
            </bean>
            (2)引用类型的set注入
             <bean id="xxx" class="yyy">
                <property name="属性名称" ref="bean的id(对象的名称)"/>
             </bean>
    -->
    <!--如果是简单类简单类型的属性,value可以直接赋值-->
    <bean id="myStudent" class="com.spring.ba01.Student">
        <property name="name" value="李四"/>  <!--setName("李四")-->
        <property name="age" value="20"/>   <!--setAge(20)-->
        <!--引用类型属性的set注入-->
        <property name="school" ref="mySchool"/>
    </bean>
    <!--声明school对象-->
    <bean id="mySchool" class="com.spring.ba01.School">
        <property name="name" value="厦门大学"/>
        <property name="address" value="厦门"/>
    </bean>
</beans>

构造注入

1、构造方法注入 ,spring调用类的s构造方法,可以在构造方法中完成属性赋值
2、 构造注入使用标签
3、标签:一个表示构造方法的一个参数
4、标签属性:
(1) name:表示构造方法的形参名
(2)index:表示构造方法的参数的位置,参数从左往右位置是0,1,2的顺序
(3)value:表示构造方法的形参类型是简单类型,使用value
(4)ref:构造方法的形参类型是引用类型,使用ref
:

	<!--使用构造注入
      	name的名称要和构造方法的形参名称一致
	-->
   <bean id="myStudent" class="com.spring.ba01.Student">
       <constructor-arg name="name" value="张三"/>
       <constructor-arg name="age" value="20"/>
       <constructor-arg name="school" ref="mySchool"/>
   </bean>
   <!--声明school对象-->
    <bean id="mySchool" class="com.spring.ba01.School">
        <property name="name" value="厦门大学"/>
        <property name="address" value="厦门"/>
    </bean>

spring的自动注入(引用类型的自动注入,简化引用类型的赋值操作)

1、概念:spring的自动注入只针对于一用类型的自动注入。
2、引用类型的自动注入:spring框架根据某些规则给引用类型赋值。不用手动给引用类型赋值,即不需要使用一下标签对引用类型的属性赋值:<property name="school" ref="mySchool"/>
3、常用的规则有:byNamebyType
(1)byName(按名称注入):Java类中引用类型和spring容器(配置文件)标签的id的名称一样,且数据类型是一致的,这样的容器的bean,spring是能够赋值给引用类型的。语法如下:

<bean id="xx" class="yyy" autowire="byName">
	简单类型的属性赋值
</bean>

(2)byType(按类型注入):Java类中引用类型的数据类型和spring容器(配置文件)中的class属性是同源关系的,这样的bean能够赋值给引用类型。语法如下:

同源关系
同源就是一类的意思:
1、java类中的引用类型的属性的数据类型的bean的class的值是一样的
2、java类中的引用类型的属性的数据类型和bean的class的值是父子类关系的
3、java类中的引用类型的属性的数据类型和bean的class的值是接口和实现类关系的
注意:在byType中,在xml配置文件中声明bean只能有一个符合条件的。

<bean id="xx" class="yyy" autowire="byType">
	简单类型的属性赋值
</bean>

spring的配置文件

:建议使用多个配置文件来完成spring的配置
1、多个配置文件的优势
(1)每个配置文件小,配置文件读写或加载速度快
(2)避免多人竞争带来的冲突(大型项目)
2、多个配置文件的分配方式
(1)按功能模块分配:一个模块一个配置文件
(2)按类的功能分配:数据库相关配置、事务功能配置、service功能配置
3、将分散的配置文件引入一个汇总配置文件,使用如下的标签:

<import resource="配置文件(通常在类路径下:[classpath:spring.xml)"/>

基于注解(annotation)的DI(依赖注入,Dependency Injection)

1、通过spring的注解完成java对象的创建和属性的赋值。代替xml文件
2、实现步骤
(1)加入依赖
(2)创建类,在类中加入注解
(3)创建spring的配置文件:声明组件扫描器的标签,指明注解在你的项目中位置
(4)使用注解创建对象,创建容器ApplicationContext
3、学习的注解:
(1)@Component
(2)@Repository
(3)@Service
(4)@Controller
(5)@Value
(6)@Autowired
(7)@Resource

@Component

1、@Component的作用

/**
 * 1、@Component 创建对象的,等同于<bean></bean>的功能
 * 2、属性:value 就是指定对象的名称,也就是bean的id的值value的值是唯一的,
 * 创建spring的对象在整个spring的容器中就一个;如果没有指定value属性,
 * 默认对象名称为类名 的首字母小写,并按照驼峰命名法命名。
 * 3、位置:在类的上面
 * 4、@Component(value = "myStudent"),也可以省略value,即@Component("myStudent")等同于
 * <bean id="myStudent" class="com.spring.anno.ba01.Student">...</bean>
 * 5、spring中和@Component功能一致,创建对象的注解还有:
 * (1)@Repository(应用在持久层类上面的注解)	放在dao层的实现类上面,表示创建dao对象,dao对象是能够访问数据库的
 * (2)@Service(应用在业务层类的上面的注解) 放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务处理等功能的
 * (3)@Controller(应用在控制器类上面的注解) 放在控制器(处理器)类上面,创建控制器对象的,控制器对象能够接受用户提交的参数,显示请求的处理结果。
 * 注:以上三个注解的语法和@Component一样的,都能够创建对象,但是这三个注解他还有额外的功能。 
 * @Repository、@Service、@Controller是给项目的对象分层的。
 */

2、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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    <!--声明组件扫描器(component-scan),组件就是Java对象
        base-package:指定在你的项目中的包名
        component-scan的工作方式:spring会扫描遍历base-package指定的包,
        把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,
        或者给属性赋值。
    -->
    <context:component-scan base-package="com.spring.ba01"/>

    <!--扫描指定多个包的三种方式-->
    <!--第一种方式:使用多次组件扫描器component-scan,指定不同的包-->
    <context:component-scan base-package="com.spring.ba01"/>
    <context:component-scan base-package="com.spring.ba02"/>
    
    <!--第二种方式:使用分隔符(;或,)分隔多个包名-->
    <context:component-scan base-package="com.spring.ba01;com.spring.ba02"/>
    
    <!--第三种方式:指定父包-->
    <context:component-scan base-package="com.spring"/>

</beans>

@Repository

@Repository(应用在持久层类上面的注解) 放在dao层的实现类上面,表示创建dao对象,dao对象是能够访问数据库的

@Service

@Service(应用在业务层类的上面的注解) 放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务处理等功能的

@Controller

@Controller(应用在控制器类上面的注解) 放在控制器(处理器)类上面,创建控制器对象的,控制器对象能够接受用户提交的参数,显示请求的处理结果。

@Value

1、@value:简单类型的属性赋值
(1)属性:value 是String类型的,表示简单类型的属性值
(2)位置:

1、在属性定义的上面,无需set方法,推荐使用。@Value(value = "值")

@Value(value = "张三")
private String name;

2、在set方法上面@Value(value = "值")

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

(3)@Value注解的书写格式:

//带属性value
@Value(value = "值")
//不带属性value
@Value("值")

(4)在@Value注解中使用$符号引入值(简单类型)

1、首先创建test.properties属性配置文件

student.name=张三
student.age=20

2、在spring配置文件中加载属性配置文件如下:

<context:property-placeholder location="classpath:test.properties"/>

3、在@Value中使用

@Value(value = "${student.name}")
private String name;

@Autowired

1、引用类型属性赋值
(1)@Autowired:spring框架提供的注解实现引用类型的属性赋值
(2)spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byNamebyType
(3)@Autowired:默认使用的是byType自动注入
(4)属性:

1、required,是一个boolean类型的,默认为true。
(1)required=true:表示引用类型赋值失败,程序报错,并终止执行
(2)required=false:引用类型如果赋值失败,程序正常执行,引用类型是null

(5)位置:

1、在属性定义的上面,无需set方法,推荐使用

@Autowired
private School school;

2、在set方法上面

(6)使用ByName的方式,需要做的是:

1、在属性上面加@Autowired
2、在属性上面加@Qualifier(value = “bean的id”):表示使用指定bean名称完成赋值

@Autowired
@Qualifier(value = "mySchool")
private School school;

@Resource

1、引用类型属性赋值
@Resource:来自JDK中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值,使用的也是自动注入原理,支持byName、byType;默认使用的是byName
2、位置:
(1)在属性定义的上面,无需set方法,推荐使用
(2)在set方法上面
3、默认使用的是byName:先使用byName自动注入,如果byName赋值失败,在使用byType
4、@Resource只使用byName方式注入,需要添加一个属性 name:name的值是bean的id(名称),如下:

@Resource(name = "mySchool")

什么时候使用注解或XML配置文件

1、不经常修改的时候使用注解
2、经常需要修改的时候使用XMl配置文件

面向切面编程(Aspect oriented programming,简写:AOP)

AOP的概念和作用

AOP:(面向切面编程):AOP就是动态代理的规范化,把动态代理的实现步骤、方式都定义好了,让开发人员统一的方式实现动态代理。AOP底层,就是采用动态代理的模式实现的,采用了两种代理:JDK动态代理CGLIB动态代理
(1)JDK动态代理:使用JDK中的Proxy、Method、InvocationHandler创建代理对象。JDK动态代理要求目标类必须实现接口。
(2)CGLIB动态代理:第三方工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象,要求目标类不能是final定义的,方法也不能是final定义的。
动态代理的作用
(1)在目标类源代码中不改变源代码的情况下,增加功能
(2)减少代码冗余、或减少重复代码
(3)专注业务逻辑代码
(4)解耦合,让日志、事务功能等非业务代码与业务代码分离

Q1:如何理解 Aspect oriented programming 字面意思?
Aspect
(1)切面,给你的目标类增加的功能,就是切面,像日志、事务等。
(2)切面的特点:一般都是非业务方法,可以独立使用,(非业务逻辑的)
Orient:面向,对着
Programming:编程


Q2:如何理解面向切面编程?
(1)需要在项目分析功能时,找出切面
(2)合理安排切面执行时间(在目标方法前,还是目标方法后)
(3)合理安排切面执行的位置,在哪个类,哪个方法增加、增强功能
术语:
(1)Aspect:切面,表示增强的功能的代码,完成某一个功能。非业务功能

常见的切面功能有:日志事务统计信息参数信息权限验证

(2)JoinPoint:连接点,连接业务方法和切面的位置。就某个类中的业务方法。
(3)Pointcut:切入点,指多个连接点方法的集合。多个方法
(4)目标对象:给哪个类的方法增加功能,这个类就是目标对象
(5)Advice:通知,表示切面功能执行的时间


Q3:一个切面的三个关键要素
(1)切面的功能代码,切面能干什么
(2)切面的执行位置,使用Pointcut表示且切面执行的位置
(3)切面执行的时间,使用Advice表示时间,在目标类的方法执行之前,还是在目标类的方法执行之后

动态代理

概念:
动态代理:可以在程序执行过程中创建代理对象。通过代理对象执行方法,给目标类的方法增加额外的功能(功能增强)。实现功能的分离,解耦合。

分类:
一、JDK动态代理(例子)

JDK动态代理:使用JDK中的Proxy、Method、InvocationHandler创建代理对象。JDK动态代理要求目标类必须实现接口

在这里插入图片描述

实现步骤:
(1)创建目标类

//SomeService接口
public interface SomeService {
    void doSome();
    void doOther();
}


 /**
 * ServiceUtils:工具类
 * 	doLog()方法,输出当前时间
 * 	doTransaction()方法,业务方法执行完成之后的事务处理
 */
public class SomeServiceImpl implements SomeService {

    @Override
    public void doSome() {
//        ServiceUtils.doLog();
        System.out.println("执行业务方法doSome");
//        ServiceUtils.doTransaction();
    }

    @Override
    public void doOther() {
//        ServiceUtils.doLog();
        System.out.println("执行业务方法doOther");
//        ServiceUtils.doTransaction();

    }
}

(2)创建InvocationHandler接口实现类,在这个类实现给目标方法增加功能

package com.spring.hander;

import com.spring.util.ServiceUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    //目标对象
    private Object target; //SomeServiceImpl

    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    /**
    * ServiceUtils:工具类
    * 	doLog()方法,输出当前时间
    * 	doTransaction()方法,业务方法执行完成之后的事务处理
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过代理对象执行方法时,会调用这个invoke( )
        System.out.println("执行了MyInvocationHandler中的invoke方法");
        Object res = null;
        ServiceUtils.doLog(); 
        //执行目标类的方法,通过Method类invoke方法实现
        res = method.invoke(target, args); //SomeServiceImpl.doOther(),doSome()
       ServiceUtils.doTransaction();
        //目标方法执行结果
        return res;
    }
}

(3)使用JDK中类Proxy,创建代理对象,实现创建对象的能力

public class MyAPP {
    public static void main(String[] args) {
        
        /*使用jdk的Proxy类创建代理对象*/

        //创建目标对象
        SomeService target= new SomeServiceImpl();
        //创建InvocationHandler对象
        InvocationHandler handler = new MyInvocationHandler(target);
        //使用Proxy创建代理对象
        SomeService proxy = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                               target.getClass().getInterfaces(),handler);
        //通过代理执行方法,会调用handler中的invoke方法
        proxy.doSome();
    }
}

二、CGLIB动态代理
CGLIB动态代理:第三方工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象,要求目标类不能是final定义的,方法也不能是final定义的。
1、Java 中使用,参考:CGLIB 动态代理机制
2、spring中,目标类没有实现接口时,spring框架会自动应用CGLIB动态代理。如果目标类实现接口时,使用CGLIB动态代理,可以在spring配置文件中加入如下配置:

<!--如果期望目标类有接口,实现CGLIB动态代理
       <aop:aspectj-autoproxy proxy-target-class="true"/>
        其中:proxy-target-class="true" 告诉spring框架,要使用CGLIB动态代理
-->
 <aop:aspectj-autoproxy proxy-target-class="true"/>

Spring的AOP的实现

1、AOP是一个规范,是一个动态的规范化,一个标准
2、AOP的技术实现框架:
(1)spring:spring在内部实现了AOP规范,能做AOP工作。spring主要在事务处理时使用了AOP。我们在项目开发中很少使用AOP实现,因为spring的AOP比较比较笨重。
(2)aspectj:一个开源的专门做AOP的框架,spring框架中继承了aspectj 框架,通过spring就能使用 aspectj 的功能

1、使用xml的配置文件
2、使用注解,我们在项目中做AOP功能,一般都使用注解,aspectj 有5个注解

AspectJ框架

1、切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强)
在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签

(1)@Before:前置通知,在目标方法之前执行切面功能
package com.aop.ba01;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;


/**
 * @Aspect: 是aspect就框架中的注解
 * 作用:表示当前类是切面类
 * 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 * 位置:在类定义的的上面
 */
@Aspect
public class MyAspect {

    /**
     * 定义方法,方法实现切面功能
     * 方法定义的要求:
     * 1、公共方法 public
     * 2、方法没有返回值
     * 3、方法名称自定义
     * 方法可以有参数,也可以没有参数。
     *      如果有参数,参数不是自定义的。有几个参数类型可以使用。
     */


    /**
     * @Before 前置通知注解
     * 属性:value 是切入点表达式,表示切面的功能执行位置
     * 位置:方法的上面
     * 特点:
     * 1、在目标方法之前先执行的
     * 2、不会改变目标方法的执行结果
     * 3、不会影响目标方法的执行
     */
    /*@Before(value = "execution(public  void com.aop.ba01.SomeService.doSome(String , Integer ))")
    public void myBefore() {
        //就是你切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
    }*/


    /**
     * 指定通知方法中的参数:JoinPoint
     * JoinPoint:业务方法,要加入漆面功能的业务方法
     * 作用是:可以在通知方法中获取执行时的信息,例如方法名称,方法的参数
     * 如果切面功能中需要用到方法的信息,就加入JoinPoint。
     * 这个JoinPoint参数的值由框架赋予,必须是第一个位置的参数
     */
    @Before(value = "execution(public  void com.aop.ba01.SomeService.doSome(String , Integer ))")
    public void myBefore(JoinPoint joinPoint) {
        //获取方法的完整定义
        System.out.println("方法的签名(定义):" + joinPoint.getSignature());
        System.out.println("方法的名称:" + joinPoint.getSignature().getName());
        //获取方法的实参
        Object args[] = joinPoint.getArgs();
        for (Object o : args) {
            System.out.println("参数:" + o);
        }


        //就是你切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
    }
}

(2)@AfterReturning:后置通知,目标方法之后执行,可以获取目标方法执行后的返回值
package com.aop.ba02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;


/**
 * @Aspect: 是aspect就框架中的注解
 * 作用:表示当前类是切面类
 * 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 * 位置:在类定义的的上面
 */
@Aspect
public class MyAspect {

    /**
     * 定义方法,方法实现切面功能
     * 方法定义的要求:
     * 1、公共方法 public
     * 2、方法没有返回值
     * 3、方法名称自定义
     * 4、方法有参数,推荐使用Object,参数名自定义
     */

    /**
     * @param res
     * @AfterReturning: 后置通知
     * 属性:
     * 1、value  切入点表达式
     * 2、returning  自定义变量:表示目标方法的返回值。
     * 自定义变量名称和方法的形参名一样
     * 位置: 在方法定义的上面
     * 特点:
     * 1、在目标方法之后执行
     * 2、能够获取目标方法的返回值,可以根据这个返回制作不同的处理功能
     * 3、可以操作这个返回值
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
            returning = "res")
   public void myAfterReturning(JoinPoint joinPoint, Object res) {
        //获取方法的完整定义
        System.out.println("方法的签名(定义):" + joinPoint.getSignature());
        // Object res:目标方法执行后的返回值,根据返回值做切面功能
        System.out.println("后置通知,在目标方法之后执行的,获取的返回值是:" + res);
        //修改res,修改简单类型返回值,目标方法执行结果没有影响;对引用类型的修改会影响目标方法的返回值
        res = "hello World";//简单类型
    }

}

(3)@Around:环绕通知
package com.aop.ba03;

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

import java.util.Date;


/**
 * @Aspect: 是aspect就框架中的注解
 * 作用:表示当前类是切面类
 * 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 * 位置:在类定义的的上面
 */
@Aspect
public class MyAspect {

    /**
     * 环绕通知方法的定义格式
     * 1、public
     * 2、必须有一个返回值,推荐使用Object
     * 3、方法名称自定义
     * 4、方法有参数,固定的参数 ProceedingJoinPoint
     */

    /**
     * @param proceedingJoinPoint
     * @return
     * @Around 环绕通知
     * 属性:value 切入点表达式
     * 位置:在方法定义上面
     * 特点:
     * 1、他是功能最强的通知
     * 2、在目标方法的前和后都能增强功能
     * 3、能够控制目标方法是否能够被调用执行
     * 4、修改原来的目标方法的执行结果。影响调用结果
     * 
     * 环绕通知:等同于jdk动态代理,InvocationHandler接口
     * 参数: ProceedingJoinPoint 就等同于Method
     * 作用:执行目标方法
     * 返回值:就是目标方法的执行结果,可以被修改
     * 执行目标方法时,相当于执行myAround
     * 环绕通知:经常做事务,在目标方法之前开启,执行目标方法,在目标方法之后提交事务
     * 
     */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //获取第一个参数
        Object[] args = proceedingJoinPoint.getArgs();
        String name = "";
        if (args != null && args.length > 1) {
            Object arg = args[0];
            name = (String) arg;
        }

        //实现环绕通知的功能
        Object result = null;

        System.out.println("环绕通知:在目标方法之前,输出时间:" + new Date());
        //1、目标方法的调用
        //控制是否执行目标方法
        if (name.equals("张三")) {
            result = proceedingJoinPoint.proceed();//method.invoke(); Object result = doFirst();
        }
        System.out.println("环绕通知:在目标方法之后,提交事务");
        //2、在目标方法的前和后加入功能
        //改变目标方法执行结果
        if (result != null) {
            result = "Hello AspectJ AOP";
        }
        //返回环绕通知的执行结果
        return result;
    }
}

(4)@AfterThrowing:异常通知
package com.aop.ba04;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;


/**
 * @Aspect: 是aspect就框架中的注解
 * 作用:表示当前类是切面类
 * 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 * 位置:在类定义的的上面
 */
@Aspect
public class MyAspect {

    /**
     * 异常通知方法的定义格式
     * 1、public
     * 2、没有返回值
     * 3、方法名称自定义
     * 4、方法有一个是Exception,如果还有是JoinPoint
     */

    /**
     * @param ex
     * @AfterThrowing 异常通知
     * 属性:
     * 1、value 切入点表达式
     * 2、throwing 自定义变量。表示目标方法抛出的异常对象。变量名必须和方法参数名一样
     * 特点:
     * 1、在目标方法抛出异常时执行
     * 2、可以做异常监控的程序,监控目标方法执行时是否有异常。如果有异常,可以发送邮件或短信通知。
     * try{
     *     SomeServiceImpl.doSecond(..)
     * }catch(Exception e){
     *     myAfterThrowing(Exception ex)
     * }
     */

    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
            throwing = "ex")
    public void myAfterThrowing(Exception ex) {
        System.out.println("异常通知:方法发生异常时,执行:" + ex.getMessage());
        //发送短信或邮件通知开发人员
    }
}

(5)@After:最终通知
package com.aop.ba05;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;


/**
 * @Aspect: 是aspect就框架中的注解
 * 作用:表示当前类是切面类
 * 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 * 位置:在类定义的的上面
 */
@Aspect
public class MyAspect {

    /**
     * 最终通知方法的定义格式
     * 1、public
     * 2、没有返回值
     * 3、方法名称自定义
     * 4、没有参数,如果还有是JoinPoint
     */


    /**
     * @After 最终通知
     * 属性:value 切入点表达是
     * 位置:在方法的上面
     * 特点:
     * 1、总会执行
     * 2、在目标方法之后执行
     * 
     * 
     * try{
     *     SomeServiceImpl.doThird(..)
     * }catch(Exception e){
     *     
     * }finally{
     *     myAfter()
     * }
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("执行最终通知,总会被执行的代码");
        //一般做资源清除工作
    }
}

(6)@Pointcut:定义和管理切入点
package com.aop.ba06;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;


/**
 * @Aspect: 是aspect就框架中的注解
 * 作用:表示当前类是切面类
 * 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 * 位置:在类定义的的上面
 */
@Aspect
public class MyAspect {


    @After(value = "myPointCut()")
    public void myAfter() {
        System.out.println("执行最终通知,总会被执行的代码");
        //一般做资源清除工作
    }

    @Before(value = "myPointCut()")
    public void myBefore() {
        System.out.println("执行前置通知,目标代码执行之前执行");
        //一般做资源清除工作
    }

    /**
     * @pointCut 定义和管理切入点,如果项目中有多个切入点和表达式是重复的、可复用的。可以使用PointCut
     * 属性:value 切入点表达式
     * 位置:在自定义方法上面
     * 特点:
     * 当使用@PointCut定义在一个方法上面,此时这个方法的名称就是切入点表达式的别名
     * 其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void myPointCut(){
        //无需代码
    }

}

2、表示切面执行的位置(Pointcut),使用的是 aspectj切入点表达式
3、使用aspectj框架实现aop

目的:是给已经存在的一些类和方法,增加额外的功能,前提是不改变原来的类的代码。

使用aspectj实现aop的基本步骤(案例:以@Before为例):

(1)新建maven项目
在这里插入图片描述

(2)加入依赖:spring依赖、aspectj依赖

<!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!--spring-aop-aspectj-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>${spring.version}</version>
    </dependency>

(3)创建目标类:接口及其实现类;要做的是给类中的方法增加功能
a、接口

package com.aop.ba01;
public interface SomeService {
    void doSome(String name, Integer age);
}

b、实现类

package com.aop.ba01;

public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        //给doSome方法增加一个功能,在doSome方法执行之前输出执行时间
        System.out.println("======目标方法doSome()======");
    }
}

(4)创建切面类:普通类

1、在类的上面加入@Aspect注解
2、在类中定义方法,方法就是切面要执行的功能代码;在方法上面加入aspectj中的通知注解,例如:@Before。有需要指定切入点表达式execution().

package com.aop.ba01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;


/**
 * @Aspect: 是aspect就框架中的注解
 * 作用:表示当前类是切面类
 * 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 * 位置:在类定义的的上面
 */
@Aspect
public class MyAspect {

    /**
     * 定义方法,方法实现切面共拿给你
     * 方法定义的要求:
     * 1、公共方法 public
     * 2、方法没有返回值
     * 3、方法名称自定义
     * 方法可以有参数,也可以没有参数。
     *      如果有参数,参数不是自定义的。有几个参数类型可以使用。
     */


    /**
     * @Before 前置通知注解
     * 属性:value 是切入点表达式,表示切面的功能执行位置
     * 位置:方法的上面
     * 特点:
     * 1、在目标方法之前先执行的
     * 2、不会改变目标方法的执行结果
     * 3、不会影响目标方法的执行
     */
    /*@Before(value = "execution(public  void com.aop.ba01.SomeService.doSome(String , Integer ))")
    public void myBefore() {
        //就是你切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
    }*/


    /**
     * 指定通知方法中的参数:JoinPoint
     * JoinPoint:业务方法,要加入漆面功能的业务方法
     * 作用是:可以在通知方法中获取执行时的信息,例如方法名称,方法的参数
     * 如果切面功能中需要用到方法的信息,就加入JoinPoint。
     * 这个JoinPoint参数的值由框架赋予,必须是第一个位置的参数
     */
    @Before(value = "execution(public  void com.aop.ba01.SomeService.doSome(String , Integer ))")
    public void myBefore(JoinPoint joinPoint) {
        //获取方法的完整定义
        System.out.println("方法的签名(定义):" + joinPoint.getSignature());
        System.out.println("方法的名称:" + joinPoint.getSignature().getName());
        //获取方法的实参
        Object args[] = joinPoint.getArgs();
        for (Object o : args) {
            System.out.println("参数:" + o);
        }


        //就是你切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
    }
}

(5)创建spring的配置文件:声明对象,把对象交给容器统一管理。使用注解或者xml配置文件的<bean>标签

1、声明目标对象
2、声明切面类的对象
3、声明 aspectj 框架中的自动代理生成标签。自动代理生成器:用来完成代理对象的自动创建功能的。

<!-- applicationContext.xml(spring的配置文件) -->
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--把对象交给spring容器、由spring容器同意创建、管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.aop.ba07.SomeServiceImpl"/>

    <!--声明切面对象-->
    <bean id="myAspect" class="com.aop.ba07.MyAspect"/>

    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
    创建对励对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象,
    所以目标对象就是被修改后的代理对象.

    aspectj-autoproxy:会把spring容器中所有的目标对象,一次性都声称代理对象。
    -->
	<!--<aop:aspectj-autoproxy/>-->

    <!--如果期望目标类有接口,实现CGLIB动态代理
        <aop:aspectj-autoproxy proxy-target-class="true"/>
        其中:proxy-target-class="true" 告诉spring框架,要使用CGLIB动态代理
    -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

(6)创建测试类,从spring容器中获取目标对象(实际就是代理对象)。通过代理执行方法,实现aop的功能增强

public class AppTest {

    @Test
    public void test01() {
        String config = "classpath:applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象
        SomeService proxy = (SomeService) context.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时,增强了功能
        proxy.doSome("张三",20);
    }
}

(7)效果
在这里插入图片描述

》》》》》至此教程结束《《《《《

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陌守

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

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

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

打赏作者

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

抵扣说明:

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

余额充值