Spring笔记(AOP与IOC)

Spring 框架两大核心机制(IoC、AOP)

  • IoC(控制反转)/ DI(依赖注入)
  • AOP(面向切面编程)

Spring 是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。

MVC:Struts2、Spring MVC

ORMapping:Hibernate、MyBatis、Spring Data(这个就是就是和数据库进行交互的东西,把一些比较持久需要保存的东西放到数据库里进行保存)

如何使用 IoC

  • 创建 Maven 工程,pom.xml 添加依赖
    这里引入了spring-context,以及lombok,spring-context就是用来导入spring的,lombok就是用来简化开发的,就不用写一些get,set方法。只需要在类前面加一个@Data就好了。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.southwind</groupId>
    <artifactId>aispringioc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.11.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

在pom.xml 复制编辑好这些代码后,要记得刷新一下。
pom文件刷新

  • 创建实体类 Student
package com.southwind.entity;

import lombok.Data;

@Data
public class Student {
    private long id;
    private String name;
    private int age;
}
  • 传统的开发方式,手动 new Student
Student student = new Student();
student.setId(1L);
student.setName("张三");
student.setAge(22);
System.out.println(student);
  • 通过 IoC 创建对象,在配置文件中添加需要管理的对象,XML 格式的配置文件,文件名可以自定义。(resources文件夹里创建一个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"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
">

    <bean id="stu" class="com.southwind.entity.Student">
        <property name="id" value="1"></property>
        <property name="name" value="张三"></property>
        <property name="age" value="22"></property>
    </bean>

</beans>
  • 从 IoC 中获取对象,通过 id 获取。(在ioc文件夹中新建一个Test类)
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("stu");
System.out.println(student);

结果如下所示,成功仅在spring.xml中写好相关配置,同时在test文件中加载spring.xml,然后再获取到相应的bean,就可以实例化一个对象了。
输出结果

配置文件

  • 通过配置 bean 标签来完成对象的管理。

    • id:对象名。

    • class:对象的模版类(所有交给 IoC 容器来管理的类必须有无参构造函数,因为 Spring 底层是通过反射机制来创建对象,Spring默认调用的就是无参构造)

  • 对象的成员变量通过 property 标签完成赋值。

    • name:成员变量名。
    • value:成员变量值(基本数据类型,String 可以直接赋值,如果是其他引用类型,不能通过 value 赋值)
    • ref:将 IoC 中的另外一个 bean 赋给当前的成员变量(DI:依赖注入)
      需要定义一个新类Address,在原来的student类里,加入一个private Address address;这里演示在id为student的bean里插入一个id为add的bean。
    <bean id="student" class="com.southwind.entity.Student">
        <property name="id" value="1"></property>
        <property name="name" value="张三"></property>
        <property name="age" value="22"></property>
        <property name="address" ref="address"></property>
    </bean>
    
    <bean id="address" class="com.southwind.entity.Address">
        <property name="id" value="1"></property>
        <property name="name" value="科技路"></property>
    </bean>
    

    修改后的Student.class

    package com.southwind.entity;
    
    import lombok.Data;
    
    @Data
    public class Student {
        private long id;
        private String name;
        private int age;
        private Address address;
    }
    
    

    这里是创建的一个新的Address.class。

    package com.southwind.entity;
    
    import lombok.Data;
    
    @Data
    public class Address {
        private int id;
        private String value;
    }
    
    

  

### IoC 底层原理

- 读取配置文件,解析 XML。
- 通过反射机制实例化配置文件中所配置所有的 bean。

```java
package com.southwind.ioc;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ClassPathXmlApplicationContext implements ApplicationContext {
    private Map<String,Object> ioc = new HashMap<String, Object>();
    public ClassPathXmlApplicationContext(String path){
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read("./src/main/resources/"+path);
            Element root = document.getRootElement();
            Iterator<Element> iterator = root.elementIterator();
            while(iterator.hasNext()){
                Element element = iterator.next();
                String id = element.attributeValue("id");
                String className = element.attributeValue("class");
                //通过反射机制创建对象
                Class clazz = Class.forName(className);
                //获取无参构造函数,创建目标对象
                Constructor constructor = clazz.getConstructor();
                Object object = constructor.newInstance();
                //给目标对象赋值
                Iterator<Element> beanIter = element.elementIterator();
                while(beanIter.hasNext()){
                    Element property = beanIter.next();
                    String name = property.attributeValue("name");
                    String valueStr = property.attributeValue("value");
                    String ref = property.attributeValue("ref");
                    if(ref == null){
                        String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1);
                        Field field = clazz.getDeclaredField(name);
                        Method method = clazz.getDeclaredMethod(methodName,field.getType());
                        //根据成员变量的数据类型将 value 进行转换
                        Object value = null;
                        if(field.getType().getName() == "long"){
                            value = Long.parseLong(valueStr);
                        }
                        if(field.getType().getName() == "java.lang.String"){
                            value = valueStr;
                        }
                        if(field.getType().getName() == "int"){
                            value = Integer.parseInt(valueStr);
                        }
                        method.invoke(object,value);
                    }
                    ioc.put(id,object);
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        } catch (NoSuchMethodException e){
            e.printStackTrace();
        } catch (InstantiationException e){
            e.printStackTrace();
        } catch (IllegalAccessException e){
            e.printStackTrace();
        } catch (InvocationTargetException e){
            e.printStackTrace();
        } catch (NoSuchFieldException e){
            e.printStackTrace();
        }
    }

    public Object getBean(String id) {
        return ioc.get(id);
    }
}

通过运行时类获取 bean

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);

注意这里getbean的参数不是xml文件中的bean的id,而是Student.Class,其实也能向清楚,其实是根据class="com.southwind.entity.Student"这个字段找到的,但是
这种方式存在一个问题,配置文件中一个数据类型的对象只能有一个实例,否则会抛出异常,因为没有唯一的 bean。

通过有参构造创建 bean

  • 在实体类中创建对应的有参构造函数(自己写构造函数!)。
  • 配置文件
<bean id="student3" class="com.southwind.entity.Student">
    <constructor-arg name="id" value="3"></constructor-arg>
    <constructor-arg name="name" value="小明"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="address" ref="address"></constructor-arg>
</bean>
<bean id="student3" class="com.southwind.entity.Student">
    <constructor-arg index="0" value="3"></constructor-arg>
    <constructor-arg index="2" value="18"></constructor-arg>
    <constructor-arg index="1" value="小明"></constructor-arg>
    <constructor-arg index="3" ref="address"></constructor-arg>
</bean>

给 bean 注入集合

注入bean的集合

<bean id="student" class="com.southwind.entity.Student">
    <property name="id" value="2"></property>
    <property name="name" value="李四"></property>
    <property name="age" value="33"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
</bean>

<bean id="address" class="com.southwind.entity.Address">
    <property name="id" value="1"></property>
    <property name="name" value="科技路"></property>
</bean>

<bean id="address2" class="com.southwind.entity.Address">
    <property name="id" value="2"></property>
    <property name="name" value="高新区"></property>
</bean>

修改了这里的时候,记得同步修改一下student字段,因为这里用addresses是一个List。

public class Student {
    private long id;
    private String name;
    private int age;
    private List<Address> addresses;
}

scope 作用域

Spring 管理的 bean 是根据 scope 来生成的,表示 bean 的作用域,共4种,默认值是 singleton。

  • singleton:单例,表示通过 IoC 容器获取的 bean 是唯一的。
  • prototype:原型,表示通过 IoC 容器获取的 bean 是不同的。
  • request:请求,表示在一次 HTTP 请求内有效。
  • session:回话,表示在一个用户会话内有效。

request 和 session 只适用于 Web 项目,大多数情况下,使用单例和原型较多。

prototype 模式当业务代码获取 IoC 容器中的 bean 时,Spring 才去调用无参构造创建对应的 bean(懒)。

singleton 模式无论业务代码是否获取 IoC 容器中的 bean,Spring 在加载 spring.xml 时就会创建 bean(勤奋)。
演示部分
单例模式(默认)
在这里插入图片描述

 ApplicationContext applicationContext = new
         ClassPathXmlApplicationContext("spring.xml");
 Student student = (Student) applicationContext.getBean("student2");
 Student student2 = (Student) applicationContext.getBean("student2");
 System.out.println(student==student2);

输出结果为true,表明student和student2是同一个对象。也就验证了singleton单例模式从bean中获取的对象是唯一的。
注意scope="singleton"就好

    <bean id="student2" class="com.southwind.entity.Student" scope="singleton">
        <property name="name" value="李四"></property>
    </bean>

原型模式
修改为prototype,输出为false,student和student2就不是同一个对象了。也就是说prototype原型模式从getBean中获取的对象时不同的。

    <bean id="student2" class="com.southwind.entity.Student" scope="prototype">
        <property name="name" value="李四"></property>
    </bean>

Spring 的继承

与 Java 的继承不同,Java 是类层面的继承,子类可以继承父类的内部结构信息;Spring 是对象层面的继承,子对象可以继承父对象的属性值,父对象的属性一定要被包含于子对象的属性中,也就是说子对象的属性数要比父对象要多(包含关系)。(继承对象的值罢了)

<bean id="student2" class="com.southwind.entity.Student">
    <property name="id" value="1"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="22"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
</bean>

<bean id="address" class="com.southwind.entity.Address">
    <property name="id" value="1"></property>
    <property name="name" value="科技路"></property>
</bean>

<bean id="address2" class="com.southwind.entity.Address">
    <property name="id" value="2"></property>
    <property name="name" value="高新区"></property>
</bean>

<bean id="stu" class="com.southwind.entity.Student" parent="student2">
    <property name="name" value="李四"></property>
</bean>

Spring 的继承关注点在于具体的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象必须包含父对象的所有属性,同时可以在此基础上添加其他的属性。

Spring 的依赖

与继承类似,依赖也是描述 bean 和 bean 之间的一种关系,配置依赖之后,被依赖的 bean 一定先创建,再创建依赖的 bean,A 依赖于 B,先创建 B,再创建 A。
一种比较有意思的用法就是,你把A依赖于B在都是单例模式的前提下的话,Spring会先创建B,然后再创建依赖于B的A,好赞!

<?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-3.2.xsd
                           ">

    <bean id="student" class="com.southwind.entity.Student" depends-on="user"></bean>

    <bean id="user" class="com.southwind.entity.User"></bean>

</beans>

我的演示
么有依赖

    <bean id="student" class="com.southwind.entity.Student"></bean>
    <bean id="User" class="com.southwind.entity.User" ></bean>

创建了Student对象,创建了User对象(先Student后User)
有依赖

    <bean id="student" class="com.southwind.entity.Student" depends-on="User"></bean>
    <bean id="User" class="com.southwind.entity.User" ></bean>

创建了User对象,创建了Student对象(先User后Student),成功!!!

Spring 的 p 命名空间

p 命名空间是对 IoC / DI 的简化操作,使用 p 命名空间可以更加方便的完成 bean 的配置以及 bean 之间的依赖注入。

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

    <bean id="student" class="com.southwind.entity.Student" p:id="1" p:name="张三" p:age="22" p:address-ref="address"></bean>

    <bean id="address" class="com.southwind.entity.Address" p:id="2" p:name="科技路"></bean>

</beans>

Spring 的工厂方法

IoC 通过工厂模式创建 bean 的方式有两种:

  • 静态工厂方法
  • 实例工厂方法

创建一个factory文件夹

静态工厂方法

factory-method="getCar"是关键点所在,其实就是写了一个spring.xml,读取这个配置,这个配置用到了com.southwind.factory.StaticCarFactory这个类(东西都定死了在这里),里面有个静态方法getCar,在bean中需要写的是,用的是哪个静态工厂类(com.southwind.factory.StaticCarFactory),要调用的factory-method是哪个(getCar),以及输入到这个factory-method的参数。由于这个bean是单例模式,也都是静态代码块,所以在业务代码中使用spring读取这个配置文件的时候,先执行了静态代码块,创建Map<id,object对象>,在bean中指定了factory-method的参数,这个对象以及被在Map中拿了出来,放在了ioc容器中。当在业务代码中使用getBean调用这个bean的时候,这个对象就出来了!!!!

package com.southwind.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
    private long id;
    private String name;
}
package com.southwind.factory;

import com.southwind.entity.Car;

import java.util.HashMap;
import java.util.Map;

public class StaticCarFactory {
    private static Map<Long, Car> carMap;
    static{
        carMap = new HashMap<Long, Car>();
        carMap.put(1L,new Car(1L,"宝马"));
        carMap.put(2L,new Car(2L,"奔驰"));
    }

    public static Car getCar(long id){
        return carMap.get(id);
    }
}

静态工厂配置一个bean,因为他不需要实例化工厂,只需要实例化目标的Car,实例化一个对象配置一个bean。

<!-- 配置静态工厂创建 Car -->
<bean id="car" class="com.southwind.factory.StaticCarFactory" factory-method="getCar">
    <constructor-arg value="2"></constructor-arg>
</bean>

java test.java
(还是用getbean ,参数为对应bean的id)

public class Test4 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-factory.xml");
        Car car = (Car) applicationContext.getBean("car");
        System.out.println(car);
    }
}

实例工厂方法

package com.southwind.factory;

import com.southwind.entity.Car;

import java.util.HashMap;
import java.util.Map;

public class InstanceCarFactory {
    private Map<Long, Car> carMap;
    public InstanceCarFactory(){
        carMap = new HashMap<Long, Car>();
        carMap.put(1L,new Car(1L,"宝马"));
        carMap.put(2L,new Car(2L,"奔驰"));
    }

    public Car getCar(long id){
        return carMap.get(id);
    }
}

实例工厂配置两个bean,因为他需要实例化工厂,然后再实例化目标的Car,实例化两个对象配置两个bean。
在配置第二个bean的时候要注意,我们是调用的第一个bean创建的对象的实例方法,所以一定要引用第一个bean的,具体方法是 写一个 factory-bean = 第一个bean的id(应该比较好理解吧,第一这是实例工厂模式,所以得有factory,第二引用的bean,所有得有bean,连起来就是factory-bean),第二就是和静态工厂一样把 factory-method也写上,然后再写上方法参数。因为都是单例模式,所以说在业务代码里加载了spring.xml之后就只要直接获取第二个bean的对象就好了,也不需要单独说先获取第一个bean这样子,

<!-- 配置实例工厂 bean -->
<bean id="carFactory" class="com.southwind.factory.InstanceCarFactory"></bean>

<!-- 赔偿实例工厂创建 Car -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
    <constructor-arg value="1"></constructor-arg>
</bean>

java test.java
(还是用getbean ,参数为对应bean的id)

public class Test4 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-factory.xml");
        Car car = (Car) applicationContext.getBean("car2");
        System.out.println(car);
    }
}

IoC 自动装载(Autowire)

IoC 负责创建对象,DI 负责完成对象的依赖注入,通过配置 property 标签的 ref 属性来完成,同时 Spring 提供了另外一种更加简便的依赖注入方式:自动装载,不需要手动配置 property,IoC 容器会自动选择 bean 完成注入。

自动装载有两种方式:

  • byName:通过属性名自动装载
  • byType:通过属性的数据类型自动装载

byName

<bean id="car" class="com.southwind.entity.Car">
    <property name="id" value="1"></property>
    <property name="name" value="宝马"></property>
</bean>

<bean id="person" class="com.southwind.entity.Person" autowire="byName">
    <property name="id" value="11"></property>
    <property name="name" value="张三"></property>
</bean>

下面是我的测试结果
在这里插入图片描述
在这里插入图片描述
private Car car 告诉ioc,按照car这个变量名去找(byname)。ioc在很多个bean中找到了一个id 为car的,然后就自动把他注入了。
在这里插入图片描述

如果改一下名字,把这个bean的名字改成cars,那么就和Person.java中变量名car不匹配了,那么就无法注入。

在这里插入图片描述
在这里插入图片描述

byType

<bean id="car" class="com.southwind.entity.Car">
    <property name="id" value="2"></property>
    <property name="name" value="奔驰"></property>
</bean>

<bean id="person" class="com.southwind.entity.Person" autowire="byType">
    <property name="id" value="11"></property>
    <property name="name" value="张三"></property>
</bean>

byType 需要注意,如果同时存在两个及以上的符合条件的 bean 时,自动装载会抛出异常。
假定两个bean都是根据同一个类创建的,那这种方式就会报错。(类似于前面业务代码里的getbean,可以引入id,也可以引入类,但引入类报错的情况,就是有两个相同类的bean)

AOP

AOP:Aspect Oriented Programming 面向切面编程。

AOP 的优点:

  • 降低模块之间的耦合度。
  • 使系统更容易扩展。
  • 更好的代码复用。
  • 非业务代码更加集中,不分散,便于统一管理。
  • 业务代码更加简洁存粹,不参杂其他代码的影响。

AOP 是对面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是 AOP。

如何使用?

  • 创建 Maven 工程,pom.xml 添加
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.11.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.11.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.11.RELEASE</version>
    </dependency>
</dependencies>
  • 创建一个计算器接口 Cal,定义4个方法。
package com.southwind.utils;

public interface Cal {
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
  • 创建接口的实现类 CalImpl。
package com.southwind.utils.impl;

import com.southwind.utils.Cal;

public class CalImpl implements Cal {
    public int add(int num1, int num2) {
        System.out.println("add方法的参数是["+num1+","+num2+"]");
        int result = num1+num2;
        System.out.println("add方法的结果是"+result);
        return result;
    }

    public int sub(int num1, int num2) {
        System.out.println("sub方法的参数是["+num1+","+num2+"]");
        int result = num1-num2;
        System.out.println("sub方法的结果是"+result);
        return result;
    }

    public int mul(int num1, int num2) {
        System.out.println("mul方法的参数是["+num1+","+num2+"]");
        int result = num1*num2;
        System.out.println("mul方法的结果是"+result);
        return result;
    }

    public int div(int num1, int num2) {
        System.out.println("div方法的参数是["+num1+","+num2+"]");
        int result = num1/num2;
        System.out.println("div方法的结果是"+result);
        return result;
    }
}

业务代码如下:

public class Test {
    public static void main(String[] args) {
        Cal cal = new CalImpl();
        cal.add(1,1);
        cal.sub(1,1);
        cal.mul(1,1);
        cal.div(1,1);
    }
}

输出结果

在这里插入图片描述

上述代码中,日志信息和业务逻辑的耦合性很高,每一个参数输入和结果输出都要重复的写一些sout,不利于系统的维护,使用 AOP 可以进行优化,如何来实现 AOP?使用动态代理的方式来实现。

给业务代码找一个代理,打印日志信息的工作交个代理来做,这样的话业务代码就只需要关注自身的业务即可。
在这里插入图片描述

InvocationHandler接口是用来创建代理类的,代理类是在程序运行过程中产生的类。

package com.southwind.utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class MyInvocationHandler implements InvocationHandler {
    //接收委托对象(this.object调用)
    private Object object = null;
    //返回代理对象(在帮忙)
    public Object bind(Object object){
        this.object = object;
        //返回的这个代理类具备了委托对象的所有功能,并且还写了invoke方法,当代理类转型为实体类对象调用方法时,将会走invoke的形式执行代码
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过反射将日志代码和业务代码分开
        System.out.println(method.getName()+"方法的参数是"+ Arrays.toString(args));
        //委托对象this.object,method.invoke(调用方法对象,方法参数)
        Object result =  method.invoke(this.object,args);
        System.out.println(method.getName()+"方法的结果是"+result);
        return result;
    }
}

以上是通过动态代理实现 AOP 的过程,比较复杂,不好理解,Spring 框架对 AOP 进行了封装,使用 Spring 框架可以用面向对象的思想来实现 AOP。

Spring 框架中不需要创建 InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可,Spring 框架底层会自动根据切面类以及目标类生成一个代理对象(但我不管!!!)。

LoggerAspect

package com.southwind.aop;

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

import java.util.Arrays;

@Aspect
@Component
public class LoggerAspect {

    @Before(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        //获取参数
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println(name+"方法的参数是:"+ args);
    }

    @After(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))")
    public void after(JoinPoint joinPoint){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法执行完毕");
    }

    @AfterReturning(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法的结果是"+result);
    }

    @AfterThrowing(value = "execution(public int com.southwind.utils.impl.CalImpl.*(..))",throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint,Exception exception){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法抛出异常:"+exception);
    }

}

(在@xx()注解中,value = "execution(public int com.southwind.utils.impl.CalImpl.*(…))"用来定位那一个类里的所有public int的函数,returning用来标记该方法返回的值)

LoggerAspect 类定义处添加的两个注解:

  • @Aspect:表示该类是切面类。(加了就表示这个是切面类)
  • @Component:将该类的对象注入到 IoC 容器。(加了就可以将切面类注入到IOC容器中,目标类后面也要加这个注解,然后IOC再把这两个粘合在一起啊)

具体方法处添加的注解:

@Before:表示方法执行的具体位置和时机。

CalImpl 也需要添加 @Component,交给 IoC 容器来管理。

package com.southwind.utils.impl;

import com.southwind.utils.Cal;
import org.springframework.stereotype.Component;

@Component
public class CalImpl implements Cal {
    public int add(int num1, int num2) {
        int result = num1+num2;
        return result;
    }

    public int sub(int num1, int num2) {
        int result = num1-num2;
        return result;
    }

    public int mul(int num1, int num2) {
        int result = num1*num2;
        return result;
    }

    public int div(int num1, int num2) {
        int result = num1/num2;
        return result;
    }
}

spring.xml 中配置 AOP。
(component-scan是在扫描所有写了component的类,搜索范围通过base-package来限定,然后让aop来干活,op:aspectj-autoproxy,aop自动代理,把两个类粘合起来)
在这里插入图片描述

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

    <!-- 自动扫描 -->
    <context:component-scan base-package="com.southwind"></context:component-scan>

    <!-- 是Aspect注解生效,为目标类自动生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

context:component-scancom.southwind 包中的所有类进行扫描,如果该类同时添加了 @Component,则将该类扫描到 IoC 容器中,即 IoC 管理它的对象。

aop:aspectj-autoproxy 让 Spring 框架结合切面类和目标类自动生成动态代理对象。

  • 切面:横切关注点被模块化的抽象对象。
  • 通知:切面对象完成的工作。
  • 目标:被通知的对象,即被横切的对象。
  • 代理:切面、通知、目标混合之后的对象。
  • 连接点:通知要插入业务代码的具体位置。
  • 切点:AOP 通过切点定位到连接点。

最后业务代码

package com.southwind.test;

import com.southwind.utils.Cal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test2 {
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
        //获取代理对象
        Cal proxy = (Cal) applicationContext.getBean("calImpl");//使用小写的实现类名字就好
        proxy.add(1,1);
        proxy.div(1,0);
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值