十分钟速过 Spring

72 阅读6分钟

IOC 容器在 Spring 中的实现

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫 bean。在创建 bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式;

1. BeanFactory

这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

2. ApplicationContext

BeanFactory 的子接口,提供更多高级特性

3. ApplicationContext的主要实现类

image.png

基于 XML 管理 bean

实验一:入门案例

  1. 创建Maven Module

  2. 引入依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
</dependencies>
  1. 创建类 HelloWorld

  2. 创建 Spring 配置文件 image.png

  3. 在 Spring 配置文件中那个配置 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        bean :配置 bean 对象,将对象给 IOC 容器管理
        id:bean 的唯一标识
        class :全类名
    -->
    <bean id="helloWorld" class="com.lyq.spring.pojo.HelloWorld"/>
</beans>
  1. 创建测试类
public class HelloTest {
    @Test
    public void test(){
        // 获取 IOC 容器
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorld helloWorld = ioc.getBean("helloWorld", HelloWorld.class);
        helloWorld.sayHello();
    }
}

获取 bean 的三种方式

  1. 根据 bean 的 id 获取
HelloWorld helloWorld = ioc.getBean("helloWorld");
  1. 根据 bean 的类型获取
HelloWorld helloWorld = ioc.getBean(HelloWorld.class);
  1. 根据 bean 的 id 和类型获取
HelloWorld helloWorld = ioc.getBean("helloWorld", HelloWorld.class);

依赖注入

依赖注入 - setter 注入

<bean id="studentTwo" class="com.lyq.spring.pojo.Student">
    <property name="age" value="12"/>
    <property name="sname" value="小张"/>
    <property name="sid" value="1"/>
    <property name="gender" value="男"/>
</bean>

依赖注入 - 构造器注入

<bean id="studentThree" class="com.lyq.spring.pojo.Student">
    <constructor-arg name="age" value="15"/>
    <constructor-arg name="sname" value="校长"/>
</bean>

依赖注入 - 特殊值处理

<bean id="studentThree" class="com.lyq.spring.pojo.Student">
    <property name="age" value="12"/>
    <property name="sname" value="&lt;小张&gt;"/>
    <property name="gender">
        <null/>
    </property>
</bean>

依赖注入 - 为类类型赋值

<bean id="clazzOne" class="com.lyq.spring.pojo.Clazz">
    <property name="cid" value="05"/>
    <property name="cname" value="吸杂"/>
</bean>
<bean id="studentFive" class="com.lyq.spring.pojo.Student">
    <property name="age" value="26"/>
    <property name="clazz" ref="clazzOne"/>
</bean>

依赖注入 - 给数组类型赋值

<bean id="studentSix" class="com.lyq.spring.pojo.Student">
    <property name="clazz" ref="clazzOne"/>
    <property name="hobby">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
        </array>
    </property>
</bean>

依赖注入 - 给List类型赋值

<bean id="clazzOne" class="com.lyq.spring.pojo.Clazz">
    <property name="cid" value="05"/>
    <property name="students">
        <list>
            <ref bean="studentTwo"/>
            <ref bean="studentThree"/>
        </list>
    </property>
</bean>

依赖注入 - 给 Map 类型赋值

<bean id="studentSix" class="com.lyq.spring.pojo.Student">
    <property name="age" value="26"/>
    <property name="teacherMap">
        <map>
            <entry key="001" value-ref="teacherOne" />
            <entry key="002" value-ref="teacherTwo" />
        </map>
    </property>
</bean>
<bean id="teacherOne" class="com.lyq.spring.pojo.Teacher">
    <property name="tid" value="001"/>
    <property name="tname" value="消化"/>
</bean>

依赖注入 - p 命名空间

<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="studentOne" class="com.lyq.spring.pojo.Student" scope="prototype"/>

<bean id="studentSeven" class="com.lyq.spring.pojo.Student"
      p:score="1"
      p:age="2"
      p:clazz-ref="clazzOne"
></bean>

Spring 管理数据源

Spring 配置文件中引入 jdbc.properties

<?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">
    <!-- 引入 jdbc.properties -->

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

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="username" value="${jdbc.username}"/>
    </bean>
</beans>

Spring 引入 druid 数据源

  1. 加入依赖
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
  1. 编写配置文件
<?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">
    <!-- 引入 jdbc.properties -->

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

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="username" value="${jdbc.username}"/>
    </bean>
</beans>

案例 1:bean 的生命周期

  1. 实例化
  2. 依赖注入
  3. 初始化,需要通过 bean 的init-method 属性指定初始化的方法
  4. IOC 容器关闭时销毁,需要通过 bean 的destroy-method 属性指定销毁的方法

bean 的后置处理器(针对所有 bean)

  1. 创建处理类
public class MyBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 此方法在 bean 的生命周期初始化之前执行
        return null;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 此方法在 bean 的生命周期初始化之后执行
        return null;
    }
}
  1. 写在配置文件中
<bean id="myBeanPostProcessor" class="com.lyq.spring.process.MyBeanPostProcessor"/>

Spring 自动装配

基于 xml 的自动装配 - byType

IOC 容器会自动根据类型在bean中那个自动匹配bean。

注意:

  • 如果通过类型没有找到对应的 bean,此时不装配,使用默认值
  • 如果通过类型找到了多个 bean,会抛出异常,NouniqueBeanDefginitionException 不唯一异常
<bean class="com.lyq.spring.controller.UserController" autowire="byType"/>

基于 xml 的自动装配 - byName

将要赋值的属性的属性名作为 bean 的 id 在 IOC 容器中那个匹配某个 bean,为属性赋值

注意

  • 当类型匹配的 bean 有多个时,此时可以使用 byName 实现自动装配
<bean class="com.lyq.spring.controller.UserController" autowire="byName"/>

基于注解管理 bean

标识组件的常用注解

  • @Component :将类标识为普通组件
  • @Controller:将类标识为控制层组件
  • @Service:将类标识为业务层组件
  • @Repository:将类标识为持久层组件

使用注解功能需要在配置文件中扫描

<?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">
       <!--  扫描组件  -->
    <context:component-scan base-package="com.lyq.spring">
        <!-- 排除 controller 类别的注解 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!-- 包括 controller 类别的注解 -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

AOP

代理模式

二十三种设计模式中的一种,属于结构型模式。它的作用是提供一个代理类,让我们在调用的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用

image.png

静态代理

1. 创建实现类

public class CalculatorImpl implements Calculator {

    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

2. 创建静态代理类

public class CalculatorStaticProxy  implements Calculator{
    private CalculatorImpl target;


    public CalculatorStaticProxy(CalculatorImpl calculator){
        this.target = calculator;
    }

    public int add(int i, int j) {
        System.out.println("日志:方法:add,参数:" + i + " ," + j);
        int result = target.add(i, j);
        System.out.println("日志:方法:add,结果:" + result);
        return result;
    }

    public int sub(int i, int j) {
        System.out.println("日志:方法:sub,参数:" + i + " ," + j);
        int result = target.sub(i, j);
        System.out.println("日志:方法:sub,结果:" + result);
        return result;
    }

        public int div(int i, int j) {
        System.out.println("日志:方法:div,参数:" + i + " ," + j);
        int result = target.div(i, j);
        System.out.println("日志:方法:div,结果:" + result);
        return result;
    }

    public int mul(int i, int j) {
        System.out.println("日志:方法:mul,参数:" + i + " ," + j);
        int result = target.mul(i, j);
        System.out.println("日志:方法:mul,结果:" + result);
        return result;
    }
}

3. 创建测试类

public void t(){
    CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(new CalculatorImpl());
    int add = calculatorStaticProxy.add(1, 2);
    int mul = calculatorStaticProxy.mul(1, 2);
    System.out.println(mul);
    System.out.println(add);
}

动态代理

public class ProxyFactory {
    private Object target;

    public ProxyFactory(Object object){
        this.target = object;
    }
    public Object getProxy(){
        /**
         * ClassLoader loader :指定加载动态生成的代理类的类加载器
         * Class<?>[] interfaces :获取目标对象实现的所有接口的 Class 对象数组
         * InvocationHandler h :设置代理类中抽象方法如何重写
         */
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("实现前" + method.getName() + ", 参数 " + Arrays.toString(args));
                /**
                 * proxy :代表要代理的对象
                 * method :要执行的方法
                 * args :要执行方法的到的参数列表
                 */
                Object result = method.invoke(target, args);
                System.out.println("实现前" + method.getName() + ", 结果 " + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }

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

通知

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法

  • 前置通知 @Before :再被代理的目标方法前面执行
  • 返回通知 @AfterReturning:再被代理的目标方法成功执行后执行(寿终正寝)
  • 异常通知 @AfterThrowing: 再被代理的目标方法异常结束后执行(死于非命)
  • 后置通知 @After:再被代理的目标方法最终结束后执行(盖棺定论)
  • 环绕通知 @Around:使用 try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

image.png

基于注解的 AOP

导入相关依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.22</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.23</version>
    </dependency>
</dependencies>

准备被代理的目标资源

导入 Calculator 接口 以及 CalculatorImpl 实现类

基于注解的 - 前置/后置通知

1. 日志类

@Component
@Aspect
public class LoggerAspect {
    // 设置公共切入点
    @Pointcut("execution(* com.lyq.spring.aop.annotation.CalculatorImpl.*(..))")
    public void pointCut(){
        
    }
    // @Before("execution(public int com.lyq.spring.aop.annotation.CalculatorImpl.add(int, int))")
    // 任意修饰符,任意返回值,在 com.lyq.spring.aop.annotation.CalculatorImpl下面的任意方法,任意参数
    // @Before("execution(* com.lyq.spring.aop.annotation.CalculatorImpl.*(..))")
    @Before("pointCut()")
    public void beforeAdviceMethod(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        // signature.getName() 获取连接点方法名
        Object[] args = joinPoint.getArgs();
        // joinPoint.getArgs() 获取方法的参数
        System.out.println("LoggerAspect, 前置通知 : " + signature.getName());
        System.out.println(Arrays.toString(args));
    }
    @After("pointCut()")
    public void afterAdviceMethod(){
        System.out.println("后置通知");
    }
}

2. 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:context="http://www.springframework.org/schema/context"
       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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
       <!--
              aop 注意事项 :
                切面类和目标类都需要交给 IOC 管理
                必须通过@Aspects注解标识切面类
                再在Spring的配置文件中设置 aop:aspectj-autoproxy 开启注解aop
        -->
        <context:component-scan base-package="com.lyq.spring.aop.annotation"></context:component-scan>

        <!-- 开启基于注解的 AOP -->
        <aop:aspectj-autoproxy />
</beans>

基于注解的 - 异常通知

  • 需要使用注解 @AfterThrow
  • 如果需要获取异常信息,只需要指定@AfterThrowthrowing属性即可
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowMethod(JoinPoint joinPoint, Exception ex){
    System.out.println("异常通知");
}

基于注解的 - 环绕通知

  • 需要使用 @Around注解
  • 环绕通知的方法的返回值一定要和目标对象返回值一致
@Around("pointCut()")
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
    Object result = null;
    try {
        // 环绕通知 - 前置通知的位置
        System.out.println("环绕 - 前置");
        // 目标对象方法的执行
        result = joinPoint.proceed();
        // 环绕通知 - 返回通知的位置
        System.out.println("环绕 - 返回");
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        // 环绕通知 - 异常通知的位置
        System.out.println("环绕 - 异常");
    } finally {
        // 环绕通知 - 后置通知位置
        System.out.println("环绕 - 后置");
    }
    return result;
}

切面优先级

  • 可以通过 @order注解的value属性设置优先级,默认值Integer的最大值
  • @Order注解的value属性值越小,优先级越高

基于 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: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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描组件-->
    <context:component-scan base-package="com.lyq.spring.aop.xml"/>
    <!--开始 aop-->
    <aop:config>
        <!--设置公共切入点表达式-->
        <aop:pointcut id="pointCut" expression="execution(* com.lyq.spring.aop.xml.CalculatorImpl.*(..))"/>
        <!--将 ioc 容器的某个 bean 设置为切面-->
        <aop:aspect ref="loggerAspect">
            <aop:after method="afterAdviceMethod" pointcut-ref="pointCut" />
            <aop:before method="beforeAdviceMethod" pointcut-ref="pointCut" />
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="pointCut" returning="result" />
            <aop:after-throwing method="afterThrowMethod" pointcut-ref="pointCut" throwing="ex" />
            <aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"/>
        </aop:aspect>

        <aop:aspect ref="validateAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="pointCut" />
        </aop:aspect>
    </aop:config>
</beans>