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

  • IOC(控制反转)、DI(依赖注入)

  • AOP(面向切面编程)

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

MVC:Struts2、Spring MVC

ORMapping:Hibernate、Mybatis、Spring Data

如何使用IoC

创建Maven工程,pom.xml添加依赖

<?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>org.example</groupId>
    <artifactId>SpringProject</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.11.RELEASE</version>
        </dependency>
     
    </dependencies>


</project>

创建实体类Student

package cn.soutwind.pojo;

import lombok.Data;

@Data
public class Student {
    private long id;
    private String name;
    private Integer age;
}

通过IOC创建对象,在配置文件中添加需要管理的对象,XML格式的配置文件,文件名可以自定义

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

    <!-- bean的id直接对应工厂拿bean的bean name -->
    <bean id="student" class="cn.soutwind.pojo.Student">
        <property name="age" value="18"></property>
        <property name="id"  value="1"></property>
        <property name="name" value="张三"></property>
    </bean>

</beans>

从IoC中获取对象,通过ID获取

@Test
public void test1(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
    Student student = (Student) applicationContext.getBean("student");
    System.out.println(student);
}

配置文件

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

  • id:对象名

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

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

  • name:成员变量名

  • value:成员变量值(基本数据类型,String可以直接赋值,如果是其他引用类型,不能通过value直接赋值)

  • ref:将IoC中的另外一个bean赋给当前的成员变量(DI)

<bean id="student" class="cn.soutwind.pojo.Student">
    <property name="age" value="18"></property>
    <property name="id"  value="1"></property>
    <property name="name" value="张三"></property>
    <property name="address" ref="address"></property>
</bean>

<bean id="address" class="cn.soutwind.pojo.Address">
    <property name="id" value="1"></property>
    <property name="name" value="长沙"></property>
</bean>

通过有参构造创建bean

  • 在实体类中创建对应的有参构造函数。

  • 配置文件

<bean id="student2" class="cn.soutwind.pojo.Student">
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="id" value="2"></constructor-arg>
    <constructor-arg name="name" value="李四"></constructor-arg>
    <constructor-arg name="address" ref="address"></constructor-arg>
</bean>
<bean id="student3" class="cn.soutwind.pojo.Student">
    <constructor-arg index="2" value="18"></constructor-arg>
    <constructor-arg index="0" value="2"></constructor-arg>
    <constructor-arg index="1" value="李四"></constructor-arg>
    <constructor-arg index="3" ref="address"></constructor-arg>
</bean>

给 bean 注入集合

<bean id="student" class="cn.soutwind.pojo.Student">
    <property name="age" value="18"></property>
    <property name="id"  value="1"></property>
    <property name="name" value="张三"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
</bean>
<bean id="address" class="cn.soutwind.pojo.Address">
    <property name="id" value="1"></property>
    <property name="name" value="长沙"></property>
</bean>

<bean id="address2" class="cn.soutwind.pojo.Address">
    <property name="id" value="2"></property>
    <property name="name" value="岳阳"></property>
</bean>

scope作用域

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

  • singleton:单例,表示通过IoC容器获取的bean是唯一的。

  • prototype:原型,表示通过IoC容器获取到的bean是不同的。

  • request:请求,表示在一次请求内有效。

  • session:会话,表示在一个用户会话内有效。

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

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

singleton模式无论业务代码是否获取IoC容器中的bean,Spring在加载spring-confi.xml时就会创建bean

Spring继承

与Java继承不同,Java继承是类层面的继承,子类可以继承父类的内部结构信息;Spring是对象层面的继承,子对象可以继承父对象的属性值

<bean id="student" class="cn.soutwind.pojo.Student">
    <property name="age" value="18"></property>
    <property name="id"  value="1"></property>
    <property name="name" value="张三"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
</bean>

<bean id="address" class="cn.soutwind.pojo.Address">
    <property name="id" value="1"></property>
    <property name="name" value="长沙"></property>
</bean>

<bean id="address2" class="cn.soutwind.pojo.Address">
    <property name="id" value="2"></property>
    <property name="name" value="岳阳"></property>
</bean>

<bean id="stu" class="cn.soutwind.pojo.Student" parent="student">
    <property name="name" value="李四" ></property>
</bean>

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

Spring的依赖

与继承类似,依赖也是描述bean 和 bean之间的一种关系,配置依赖后,被依赖的bean一定先创建,再创建依赖的bean, A 依赖 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.xsd">

    <!-- bean的id直接对应工厂拿bean的bean name -->
    <bean id="student" class="cn.soutwind.pojo.Student" depends-on="user">

    </bean>

    <bean id="user" class="cn.soutwind.pojo.User">

    </bean>

</beans>

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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean的id直接对应工厂拿bean的bean name -->
    <bean id="student" class="cn.soutwind.pojo.Student" p:id="1" p:name="李四" p:age="18" p:address-ref="address"></bean>

    <bean id="address" class="cn.soutwind.pojo.Address" p:id="18" p:name="邵阳"></bean>

</beans>

Spring的工厂方法

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

  • 静态工厂方法

  • 实例工厂方法

静态工厂方法

package cn.soutwind.pojo;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
    private int id;
    private String name;
}
package cn.soutwind.factory;

import cn.soutwind.pojo.Car;

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

public class StaticCarFactory {
    private static Map<Integer, Car> carMap;
    static {
        carMap = new HashMap<Integer, Car>();
        carMap.put(1,new Car(1,"黑马"));
        carMap.put(2,new Car(2,"白马"));
    }

    public static Car getCar(Integer id){
        return carMap.get(id);
    }
}
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean的id直接对应工厂拿bean的bean name -->
    <bean id="car" class="cn.soutwind.factory.StaticCarFactory" factory-method="getCar">
        <constructor-arg value="1"></constructor-arg>
    </bean>

</beans>

实例工厂方式

package cn.soutwind.factory;

import cn.soutwind.pojo.Car;

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

public class InstanceCarFactory {
    private Map<Integer, Car> carMap;

    public InstanceCarFactory(){
        carMap = new HashMap<Integer, Car>();
        carMap.put(1,new Car(1,"黑马"));
        carMap.put(2,new Car(2,"白马"));
    }

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

}
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean的id直接对应工厂拿bean的bean name -->
    <bean id="car" class="cn.soutwind.factory.StaticCarFactory" factory-method="getCar">
        <constructor-arg value="1"></constructor-arg>
    </bean>

    <bean id="carFactory" class="cn.soutwind.factory.InstanceCarFactory"></bean>

    <bean id="car2" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="2"></constructor-arg>
    </bean>

</beans>

IoC自动装载(Autowire)

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

自动装载有两种方式:

  • byName:通过属性名自动装载

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

byName

<<bean id="car" class="cn.soutwind.pojo.Car">
    <property name="id" value="1"></property>
    <property name="name" value="宝马"></property>
</bean>

<bean id="person" class="cn.soutwind.pojo.Person" autowire="byName">
    <property name="id" value="1"></property>
    <property name="name" value="张三"></property>
</bean>

byType

<bean id="car" class="cn.soutwind.pojo.Car">
    <property name="id" value="1"></property>
    <property name="name" value="宝马"></property>
</bean>

<bean id="person" class="cn.soutwind.pojo.Person" autowire="byType">
    <property name="id" value="1"></property>
    <property name="name" value="张三"></property>

</bean>

使用byType时需要注意,如果同时存在两个及以上的bean对象时,自动装载会抛出异常。

AOP

AOP:Aspect Oriented Programming 面向切面编程

AOP的优点:

  • 降低模块之间的耦合度

  • 使系统更容易扩展

  • 更好的代发复用

  • 非业务代码更加集中,不分散,便于统一管理

  • 业务代码更加简洁纯粹,不掺杂其他代码的影响

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

如何使用

创建Maven工厂,pom.xml添加

<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.3.29</version>
</dependency>

创建一个计算机接口Cal,定义4个方法

package cn.soutwind.untils;

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);

}
package cn.soutwind.untils;

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

public class MyInvocationHandler implements InvocationHandler {
    // 接受委托对象
    private Object object = null;

    // 返回代理对象
    public Object bind(Object object) {
        this.object = object;
        // object.getClass().getInterfaces()获取委托对象的所有接口
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + "参数有" + Arrays.toString(args));
        int result = (int) method.invoke(this.object,args);
        System.out.println(method.getName() + "结果是" + result);
        return result;
    }
}

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

Spring框架中不需要创建InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可

package cn.soutwind.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 cn.soutwind.untils.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 cn.soutwind.untils.CalImpl.*(..))")
    public void after(JoinPoint joinPoint){
        System.out.println("方法执行完毕");
    }

    @AfterReturning(value =  "execution(public int cn.soutwind.untils.CalImpl.*(..))", returning = "res")
    public void afterReturning(JoinPoint joinPoint, Object res){
        String name = joinPoint.getSignature().getName();
        System.out.println(name +"执行完的结果是:"+ res);
    }


    @AfterThrowing(value =  "execution(public int cn.soutwind.untils.CalImpl.*(..))", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "抛出了一个异常:" + exception);
    }

}

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

@Aspect:表示该类是切面类

@Component:将该类的对象注入到IoC容器

具体方法处添加的注解:

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

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

package cn.soutwind.untils;

import org.springframework.stereotype.Component;

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

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

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

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

spring.xml中配置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:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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">
    
        <!--自动扫描-->
        <context:component-scan base-package="cn.soutwind"></context:component-scan>

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

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

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

  • 切面:横切关注点被模块化的抽象对象

  • 通知:切面对象完成的工作者。

  • 目标:被通知的对象,即被横切的对象。

  • 代理:切面、通知、目标混合之后的对象。

  • 连接点:通知要插入业务代码的具体位置。

  • 切点:AOP通过切点定位到连接点。