Spring 学习

jefxff 153,756 2020-05-13

Spring

  • Spring 是现在最火的框架, 必须学

  • Spring项目有: Spring data, Spring Boot, Spring Cloud, Spring Framework, Spring Social


IOC: 控制反转 (DI:也叫依赖注入)

搭建 Spring 环境

  • 下载Springa的jar包4最新的4.3.9

  • 开发Spring项目最少需要5个jar包

    • spring-aop.jar : 开发AOP特性需要的jar

    • spring-beans.jar : 处理bean需要的jar

    • spring-context.jar : 处理Spring上下文需要的jar

    • spring-core.jar : Spring的核心jar

    • spring-expression.jar : Spring表达式

    • commons-logging-1.1.1.jar : 第三方日志(apache)

  • 编写第一个Student的javaBean

  • 编写配置文件

<!-- FileMame: 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放在了一个称之为spring ioc的容器的地方 -->
    <!--这里配置Student的bean,  id:唯一标识符 class:指定类型 -->
    <bean id="student" class="xyz.xmcs.entity.Student">
        <!-- property: 该class所代表的类的属性
             name: 属性名
             value: 属性值
         -->
        <property name="stuAge" value="24" />
        <property name="stuName" value="ls" />
        <property name="stuNo"  value="2" />
    </bean>
</beans>
  • 测试
package xyz.xmcs.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.xmcs.entity.Student;

public class TestStudent {
    public static void main(String[] args) {
        // spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 执行从spring容器中获取一个id为student的对象
        Student student = (Student) context.getBean("student");
        System.out.println(student);
    }
}

Spring IOC 发展史

  • new/setter: 通过new一个对象给对象赋值; 缺点, 每次使用的时候都需要通过new来创建新对象, 很零散, 维护麻烦

  • 简单工厂模式: 通过简单工厂, 可以将创建的new集中起来, 方便维护

  • SpringIOC : 控制反转(反转的是: 获取对象的方式(从创造到拿)) 也叫做 DI(依赖注入)

    • 控制反转: 将创建对象, 属性值的方式进行反转, 从 new, setter() 翻转为了从springIOC容器getBean()

    • 依赖注入: 将属性值注给了属性, 将属性注给了bean, 将bean注入给了IOC容器

    • IOC/DI: 无论需要什么对象, 都可以直接去SpringIOC容器中获取(前提是将需要的先注入容器), 而不需要自己操作(new/setter()); 所以IOC容器分为两步:

      • 先给SpringIOC容器存放对象并赋值

      • 直接通过getBean() 获取想要的对象


DI: 依赖注入

  • Bean赋值: IOC 中配置Bean时, 简单类型 8+String 通过value赋值; 而对象类型通过 ref 来赋值即: ref="需要引用的id值", 实现了对象与对象之间的依赖关系
	<bean id="course" class="xyz.xmcs.entity.Course">
        <property name="courseName" value="java"/>
        <property name="courseHour" value="200"/>
        <!-- 对象类型通过ref指定配置过的Bean -->
        <property name="teacher" ref="teacher"/>
    </bean>

依赖注入的三种方法

  • set注入:

    • Bean通过property标签来赋值时默认使用的是setter()方法; 依赖注入的底层是通过反射调用无参构造实现的

    • setter 注入示例

	<bean id="teacher" class="xyz.xmcs.entity.Teacher">
        <!-- 通过 setter 赋值 -->
        <property name="name" value="zs"/>
        <property name="age" value="24"/>
    </bean>
    <bean id="course" class="xyz.xmcs.entity.Course">
        <!-- 通过 setter 赋值 对象类型通过ref指定配置过的Bean -->
        <property name="courseName" value="java"/>
        <property name="courseHour" value="200"/>
        <property name="teacher" ref="teacher"/> 
    </bean>
  • 构造方法注入:

    • 通过构造方法注入(即在Bean中通过 constructoe-arg 标签来指定, 并且加上name属性, 减少电脑误会)

      • 因为无论是 String还是byte/short/int/long都是通过 value="值" 来赋值的, 所以在value后面加上 name="属性名" 来进行区分
    • 注意: 如果 constructoe-arg 标签的顺序和构造器的参数顺序不一致, 则需要通过 constructoe-arg标签的属性 index(参数位置), name(参数名), type(参数类型) 来其中一个来指定顺序

    • 构造器注入的示例

    <bean id="teacher" class="xyz.xmcs.entity.Teacher">
        <!-- 通过构造方法赋值 -->
        <constructor-arg value="24" index="0" name="age" type="int"/>
        <constructor-arg value="ls" index="1" name="name" type="java.lang.String"/>
    </bean>
    <bean id="course" class="xyz.xmcs.entity.Course">
        <!-- 通过构造方法赋值 -->
        <constructor-arg value="C"/>
        <constructor-arg value="100"/>
        <constructor-arg ref="teacher"/>
    </bean>
  • P命令空间注入

    • 第一步: 在头部引入 p 命名空间: xmlns:p="http://www.springframework.org/schema/p"

    • 第二步: 在 Bean 标签内部, 通过 p:属性 或 p:属性-fer 来注入简单类型或引用类型的

    • p注入的示例

	<bean id="teacher" class="xyz.xmcs.entity.Teacher" p:age="25" p:name="ww">
    </bean>
    <bean id="course" class="xyz.xmcs.entity.Course"
          p:courseName="Hadoop" p:courseHour="200" p:teacher-ref="teacher">
    </bean>

各种集合类型的注入

  • 注入: list, set, map, array, properties
public class AllCollectionType<ToString> {
    private List<String> listElement;
    private String[] arrayElement;
    private Set<String> setElement;
    private Map<String, String> mapElement;
    private Properties propsElement;
    // 省略 setter, getter, Constructor, toString
}
	<!-- 通过setter方法注入: 需要记住 -->
    <bean id="collectionDemo" class="xyz.xmcs.entity.AllCollectionType">
        <!-- 通过 setter 来赋值 -->
        <property name="listElement" >
            <list>
                <value>足球list</value>
                <value>篮球list</value>
                <value>台球list</value>
            </list>
        </property>
        <property name="arrayElement">
            <array>
                <value>足球array</value>
                <value>篮球array</value>
                <value>台球array</value>
            </array>
        </property>
        <property name="setElement">
            <set>
                <value>足球set</value>
                <value>篮球set</value>
                <value>台球set</value>
            </set>
        </property>
        <property name="mapElement">
            <map>
                <entry key="foot" value="足球map"/>
                <entry key="basket" value="篮球map"/>
                <entry key="pingpan" value="足球map"/>
            </map>
        </property>
        <property name="propsElement" >
            <props>
                <prop key="foot">足球prop</prop>
                <prop key="basket">篮球prop</prop>
                <prop key="pingpan">足球prop</prop>
            </props>
        </property>
    </bean>

value 与<\value> 注入方式的区别

  • 子元素<\value>写在bean标签体中且不加引用, 而 value 作为属性值写在 bean标签中必须加引号

  • 子元素<\value>有 type属性, 可以通过type属性指定数据类型; 而value没有

  • 子元素<\value> 可通过 <![CDATA[]]>或使用XML预定义的实体引用来使用特殊字符, 而 value 只能通过XML预定义的实体引用来使用特殊字符

  • 实体引用:

    • (实体引用)< --- (表示的符号) <

    • (实体引用)& --- (表示的符号) &

  • 赋空值: null ""

    • 赋 null 时, 不写<\value> 直接在 property 标签中写: <\null/>

    • 赋空字符串时: <\value></value> 或者 value=""


自动装配(只适用于ref类型)

  • 在IOC头部中, 通过 default-autowire="byName", 可一次性将该IOC所有bean设置为"byName"形式的自动装配

  • byName

    • 需要在bean中加一个 autoWrite="byName | byType | constructor"

    • byName 就是 ById, 就是通过 bean 的id值来装配的

    • 如果当前Beans中一个bean的id值等于当前bean中所代表的类的属性名时就自动将bean注入到当前bean的属性里面

  • byType

    • 其他bean的类型(class)是否与给bean类的ref属性一致, 两个类型一致就自动装配

    • 注意: byType 有一个条件就是当前IOC容器中只能有一个bean满足条件

  • constructor

    • 其他bean的类型(class)是否与给bean类的构造方法的参数是否一致; 本质就是ByType的衍生

使用注解定义Bean

  • 第一步:通过注解(@Component("XxxXxx"))的形式, 将bean以及相应的属性值放入IOC容器中
	/**
	 *  @Component("studentDao") 就相当于 <bean id="studentDao" class="xyz.xmcs.dao.StudentDaoImpl"></bean>
	 */
	@Component("studentDao")
	public class StudentDaoImpl {
	    public void addStudent(){
	        System.out.println("增加学生...");
	    }
	}
  • 第二步:在IOC配置文件中引入 xmlns:context="http://www.springframework.org/schema/context"

  • 第三步: 配置扫描器; Spring在启动的时候会根据base-package在该包中扫描所有类, 查找这些类是否有注解@Component("XxxXxx"); 如果有则将该类加入IOC容器中

	<!-- 配置扫描器 -->
    <context:component-scan base-package="xyz.xmcs.dao">

    </context:component-scan>
  • @component注解细化

    • dao层注解: @Repository

    • service层注解: @Service

    • 控制器层注解: @Controller

使用注解实现事务(声明式事务)

  • Soring的jar包, 以及 aopalliance-1.0.jar

  • 配置, 注意需要在Spring配置文件的头部添加 URL

    <?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:tx="http://www.springframework.org/schema/tx"
       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/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd"
       default-autowire="byName">

    <!-- 配置数据库相关 依赖-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="127.0.0.1:mysql:mybatis" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="maxActive" value="10"/>
        <property name="maxIdle" value="6"/>
    </bean>

    <!-- 配置事务管理器txManager  依赖-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 增加对事务的支持 核心-->
    <tx:annotation-driven transaction-manager="txManager"/>
  • 编写代码逻辑, 在需要成为事务的方法前加上注解 @Transactional
    @Transactional(readOnly=false, propagation = Propagation.REQUIRED)
    @Override
    public void addStudent(Student student) {
        // if(判断学生是否存在)
        studentDao.addStudent(student);
    }

AOP之实现通知---实现接口

  • 要将一个普通的Java类变成不普通的具有特殊功能的类, 一般有四种方式, 继承类或实现接口, 添加注解, 配置

  • XML 的方式实现通知(实现接口)

    • 前置通知: 实现 org.springframework.aop.MethodBeforeAdvice接口, 接口方法 before(), 在目标方法执行前执行

    • 后置通知: 实现 org.springframework.aop.AfterReturningAdvice接口, 接口方法 afterReturning(), 在目标方法执行后执行

    • 异常通知: 实现 org.springframework.aop.ThrowsAdvice 接口, 在目标方法发生异常时执行

    • 环绕通知: org.aopalliance.intercept.MethodInterceptor 接口, 接口方法 invoke(), 拦截对目标方法调用, 即调用目标方法的整个过程

实现通知的流程

  • 引入需要的jar包, aopliance.jar, aspectjweaver.jar

  • 异常通知接口空实现, 并没有方法, 但是要实现异常通知就需要在实现类里面定义下列重载方法中的一个

    • public void afterThrowing(Exception ex)

    • public void afterThrowing(RemoteException)

    • public void afterThrowing(Method method, Object[] args, Object target, Exception ex)

    • public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)

  • 环绕通知: 在目标方法的前后, 异常发生时, 最终等各个地方都可以进行通知, 是最强大的一个通知; 可以获取目标方法的全部控制权(目标方法是否执行, 执行之前, 执行之后, 参数, 返回值等)

    • 在方法中通过 invocation 对象可以拿到(Method method, Object[] args, Object target, ServletException ex )这一堆参数
  • 编写类实现通知的接口

    // 通过实现接口, 将普通的类变成前置通知
    public class LogBefore implements MethodBeforeAdvice {

        // 前置通知的具体内容
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("前置通知...");
        }
    }

    // ------------------------ 后置通知 ---------------------
    public class LogAfter implements AfterReturningAdvice {
        /**
         * Callback after a given method successfully returned.
         *
         * @param returnValue the value returned by the method, if any
         * @param method      method being invoked
         * @param args        arguments to the method
         * @param target      target of the method invocation. May be {@code null}.
         * @throws Throwable if this object wishes to abort the call.
         *                   Any exception thrown will be returned to the caller if it's
         *                   allowed by the method signature. Otherwise the exception
         *                   will be wrapped as a runtime exception.
         */
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("这是后置通知, 目标对象(谁调用的该方法): " + target + ", 调用的方法名: " + method + ", 方法的参数个数: " + args.length + ", 方法的返回值: " + returnValue);
        }
    }

    //  ------------------------ 异常通知 ---------------------
    public class LogException implements ThrowsAdvice {

        public void afterThrowing(Method method, Object[] args, Object target, Throwable ex){
            System.out.println("异常通知: 方法名: " + method.getName() + ", 参数长度: " + args.length +
                    ", 目标对象: " + target + ", 异常: " + ex.getMessage());

        }
    }

    //  ------------------------ 环绕通知 ---------------------    
    public class LogAround implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            Object proceed = null;

            // 理解: 若给addStudent(Student)配置环绕通知, 获取方法的执行权后, 通过 invocation.proceed(); 就可以执行 addStudent()方法;
            //       所以在 invocation.proceed(); 之前就相当于前置通知; 在 invocation.proceed(); 之后就相当于后置通知; 在 catch(){} 中写的
            //       就相当于后置通知
            try{
                System.out.println("用环绕通知模拟的[前置通知]: 方法名: " + invocation.getMethod().getName() + ", 参数长度: " + invocation.getArguments().length +
                        ", 目标对象: " + invocation.getThis() + ", 异常: " + proceed);
                proceed = invocation.proceed();
                System.out.println("用环绕通知模拟的[后置通知]: 方法名: " + invocation.getMethod().getName() + ", 参数长度: " + invocation.getArguments().length +
                        ", 目标对象: " + invocation.getThis() + ", 异常: " + proceed);
            }catch (Exception e){
                System.out.println("用环绕通知模拟的[异常通知]: 方法名: " + invocation.getMethod().getName() + ", 参数长度: " + invocation.getArguments().length +
                        ", 目标对象: " + invocation.getThis() + ", 异常: " + proceed);
            }
            return proceed;
        }
    }
  • 配置, 就是将通知的两端进行链接, 实现通知
<!-- 下面所有通知的另一端 -->
    <bean id="studentService" class="xyz.xmcs.service.Impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>

  <!-- "前置通知" 类 -->
    <!-- 链接线的一方 -->
    <bean id="logBefore" class="xyz.xmcs.aop.LogBefore"></bean>
    <!-- 配置前置通知 -->
    <!-- 将方法所在类(addStudent)和通知进行关联 -->
    <aop:config>
        <!--面 配置切入点(在哪里执行通知) -->
        <!-- 链接线的另一方 -->
        <aop:pointcut expression="execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))
            or execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.deleteStudent(int))" id="pointcut"/>

        <!--线 advisor:相当于链接切入点和切面的线 -->
        <!-- 链接线 -->
        <aop:advisor advice-ref="logBefore" pointcut-ref="pointcut" />
    </aop:config>

<!-- ================================================================================================================ -->

    <!-- 后置通知 -->
    <bean id="logAfter" class="xyz.xmcs.aop.LogAfter"></bean>
    <!-- 配置后置通知 -->
    <aop:config>
        <!-- ======================== 配置后置通知的另一方 ============================== -->
        <!-- 业务量的具体方法 -->
        <aop:pointcut id="pointcutAfter" expression="execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))
            or execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.deleteStudent(int))"/>
        <!-- 链接线的另一端 通知方法 -->
        <aop:advisor advice-ref="logAfter" pointcut-ref="pointcutAfter"/>
    </aop:config>

<!-- ================================================================================================================ -->

    <!-- 异常通知 -->
    <bean id="logException" class="xyz.xmcs.aop.LogException"></bean>
    <!-- 配置异常通知 -->
    <aop:config>
        <!-- ======================== 配置后置通知的另一方 ============================== -->
        <!-- 业务量的具体方法 -->
        <aop:pointcut id="pointcutException" expression="execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))
            or execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.deleteStudent(int))"/>
        <!-- 链接线的另一端 通知方法 -->
        <aop:advisor advice-ref="logException" pointcut-ref="pointcutException"/>
    </aop:config>

    <!-- ================================================================================================================ -->

    <!-- 环绕通知 -->
    <bean id="logAround" class="xyz.xmcs.aop.LogAround"></bean>
    <aop:config>
        <aop:pointcut id="pointcutAround" expression="execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))"/>
        <aop:advisor advice-ref="logAround" pointcut-ref="pointcutAround"/>
    </aop:config>

AOP之实现通知---实现注解

  • 引入需要的jar包

  • 编写业务类, 通知, 并纳入IOC容器

    • 通过 @Aspect 注解将一个普通类变成一个通知类

    • 在方法上通过添加 @Before() @AfterReturning(), @AfterThrowing(), @Around, @After 注解来将一个方法变成前置, 后置, 异常, 环绕, 最终通知

    • 方法上注解的参数就是之前在aop:config中aop:pointcut的expression的值, 通知的面

    • 在方法中定义形参 JoinPoint 类型, 通过该对象可以拿到(Method method, Object[] args, Object target, ServletException ex) 这一堆对象

      • 其中 后置方法的返回值问题: 如果定义返回值, 则在 @AfterReturning()注解中通过k=v的形式指定切入点和返回值

      • 如:@AfterReturning(pointcut = "execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))",returning = "returningValue")

    • 注意环绕通知 @Around 修饰的方法的参数是 ProceedingJoinPoint, 它是 JoinPoint 的字接口, 通过 ProceedingJoinPoint 的对象就可以拿到 上述那些参数

    • 异常通知可以指定捕获某种类型的异常, 即在@AfterThrowing修饰的方法的第二个参数指定异常类型, 注意, 当参数超过两个时, 就需要在注解中通过k=v的形式来指定

  • 配置

    • 开启注解对AOP的支持(默认不支持): <\aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    • 开启扫描, 用于将通过注解配置的bean放入IOC容器中(扫描其会将指定包中的 @Component, @Service, @Respository, @Controller 修饰的类产生的对象增加到IOC容器中)

  • 示例

    // @Component 注解就相当于 Spring 配置文件中的bean标签
    @Component("logAnnotation") // 相当于 <bean id="logAnnotation" class="前类名"></bean>
    @Aspect
    public class LogAspectAnnotation {

        // 前置通知
        @Before("execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))") // 属性: 定义切点
        public void myBefore(JoinPoint joinPoint){
            System.out.println("========[注解形式] 的前置通知========");
            System.out.println("用用 ==注解== 模拟的[前置通知]: 方法名: " + joinPoint.getSignature().getName() + ", 参数: " + Arrays.toString(joinPoint.getArgs()) +
                    ", 目标对象: " + joinPoint.getTarget());
        }

        // 后置通知
        @AfterReturning(pointcut = "execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))",
                returning = "returningValue")
        public void myAfter(JoinPoint joinPoint, Object returningValue){
            System.out.println("========[注解形式] 的后置通知========");
            System.out.println("用 ==注解== 模拟的[后置通知]: 方法名: " + joinPoint.getSignature().getName() + ", 参数: " + Arrays.toString(joinPoint.getArgs()) +
                    ", 目标对象: " + joinPoint.getTarget() + ", 返回值:" + returningValue);
        }

        // 异常通知
        // 如果只想捕获特定的异常, 则可以通过第二个参数实现: e
        @AfterThrowing(pointcut = "execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))", throwing = "e")
        public void myException(JoinPoint jp, NullPointerException e){
            System.out.println("========[注解形式] 的异常通知======== e: " + e);
        }

        // 环绕通知
        @Around("execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))")
        public void myAround(ProceedingJoinPoint jp){
            // 方法执行之前
            System.out.println("方法执行之前: ====> 前置通知");
            try{

            // 方法执行时
            jp.proceed();
            // 方法执行之后
                System.out.println("方法执行之后: ====> 后置通知");
            } catch (Throwable e){
                // 方法异常时
                System.out.println("方法发生异常时: ====> 异常通知");

            } finally {
                // 方法最终通知
                System.out.println("最终通知");
            }
        }

        @After("execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))")
        public void MyAfter(){
            System.out.println("=======最终通知======= ");
        }
    }
    <!--  配置扫描, 用来扫描以注解配置的bean  -->
    <context:component-scan base-package="xyz.xmcs.*"></context:component-scan>

    <!--  开启AOP对注解的支持  -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

AOP之实现通知---基于Schema配置

  • 编写通知类, 前置, 后置, 异常通知方法的参数可以说 JoinPoint, 环绕通知的参数是ProceedingJoinPoint

  • 基于Schema 编写的通知的配置实现可上述都类似, 首先将通知以及相关类放入IOC容器, 再通过 aop:config->aop:pointcut指定切入点, 再通过aop:spect标签以及子标签将通知方法和相关类进行连接

    public class LogSchema {

        public void afterReturning(JoinPoint jp, Object returningValue ) throws Throwable {
            System.out.println("这是 ==基于Schema== 的后置通知, 目标对象(谁调用的该方法): " + jp.getTarget() + ", 调用的方法名: " +
                    jp.getSignature().getName() + ", 方法的参数个数: " + jp.getArgs().length + ", 方法的返回值: " + returningValue);
        }

        public void before(JoinPoint jp){
            System.out.println("这是 ==基于Schema== 的前置通知, 目标对象(谁调用的该方法): " + jp.getTarget() + ", 调用的方法名: " +
                    jp.getSignature().getName() + ", 方法的参数个数: " + jp.getArgs().length);
        }

        public void whenException(JoinPoint jp, NullPointerException e){
            System.out.println("这是 $$$$==基于Schema==$$$$ 的异常通知, 目标对象(谁调用的该方法): " + jp.getTarget() + ", 调用的方法名: " +
                    jp.getSignature().getName() + ", 方法的参数个数: " + jp.getArgs().length + ", 异常方法为: " + e);
        }

        public Object around(ProceedingJoinPoint jp){
            Object result = null;
            // 方法执行之前
            System.out.println("Schema######################方法执行之前: ====> 前置通知");
            try{
                // 方法执行时
                result = jp.proceed();
                // 方法执行之后
                System.out.println("Schema######################方法执行之后: ====> 后置通知");
            } catch (Throwable e){
                // 方法异常时
                System.out.println("Schema######################方法发生异常时: ====> 异常通知");
            }
            finally {
                // 方法最终通知
                System.out.println("Schema######################最终通知");
            }
            return  result;
        }
    }
  • 配置
    <!-- 基于Schema的通知配置 -->
    <bean id="logSchema" class="xyz.xmcs.aop.LogSchema">
    </bean>
    <aop:config>
        <aop:pointcut id="pcSchema" expression="execution(public void xyz.xmcs.service.Impl.StudentServiceImpl.addStudent(xyz.xmcs.entity.Student))"/>
        <aop:aspect ref="logSchema">
            <!-- schema实现前置通知 -->
            <aop:before method="before" pointcut-ref="pcSchema"/>
            <!-- schema实现后置通知 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pcSchema" returning="returningValue"/>
            <!-- schema实现异常通知 -->
            <aop:after-throwing method="whenException" pointcut-ref="pcSchema" throwing="e"/>
            <!-- schema实现环绕通知 -->
            <aop:around method="around" pointcut-ref="pcSchema" arg-names=""/>
        </aop:aspect>
    </aop:config>

Spring 开发Web项目

  • SpringIOC容器初始化;

    • 将IOC容器所有的bean实例化成对象

    • 将各个bean依赖的属性注入进去

  • 普通的Java程序中, 通过执行 new ClassPathXmlApplicationContext("applicationContext.xml"); 这句代码就会初始化IOC容器

  • Web 项目如何初始化IOC容器: 思路: 如tomcat, 在启动时, 可以通过配置监听器来监听tomcat是否启动, 在启动时可以类似的执行上面的IOC容器的代码, 在将返回值放在域中即可; 这种思路Spring-web.jar已经提供了实现, 我们所要做的就是引入需要的jar包, 并且配置起来即可

    • web项目启动时, 会自动加载web.xml, 因此需要在web.xml中加载监听器(IOC容器初始化)
  • web项目启动时, 自动实例化IOC容器的配置(有默认配置就是将applicationContext.xml(必须是这个名字) 配置文件放在WEB-INFO/下)

    <!-- web.xml -->
    <!-- 这种配置前提是将 applicationContext.xml 配置文件放在了src路径下;
         如果将 applicationContext.xml 配置文件放在 WEB-INFO/下的话, 就不要需要配置, 就是默认配置
     -->
    <context-param>
        <!-- 监听器的父类ContextLoader中有一个属性contextConfigLocation, 该属性值保存着容器配置文件applicationContext.xml位置 -->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <!-- 配置 spring-web.jar 提供的监听器, 此监听器可以在服务器启动时初始化IOC容器
             初始化IOC容器(applicationContext.xml), 还必须告诉监听器此容器的位置
        -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

拆分Spring配置文件

  • 普通 java 项目涉及不到拆分, 因为需要那个加载那个就可以了

  • web 项目拆分配置文件(拆分原则: 按照三层/功能结构来拆分)

  • web 项目合并配置文件

    • 一种就是在web.xml中context-param标签下的 param-value 中增加每一个 applicationContext.xml 文件

    • 另一种就是使用通配符的形式加载(就选择这种配置形式)

    • 第三种就是在web.xml中只配置主的ApplicationContext.xml, 而将别的配置文件引入到主配置文件中

    <!-- Spring配置文件的合并 -->
    <context-param>
        <!-- 监听器的父类ContextLoader中有一个属性contextConfigLocation, 该属性值保存着容器配置文件applicationContext.xml位置 -->
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml,
            classpath:applicationContext-*.xml
            <!--  
            classpath:applicationContext-Dao.xml,
            classpath:applicationContext-Controller.xml,
            classpath:applicationContext-Service.xml     -->
        </param-value>
    </context-param>
    <!-- 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">
        <!-- 引入拆分的配置文件, 要执行此配置必须只在web.xml中引入applicationContext.xml文件 -->
        <import resource="applicationContext-Service.xml"/>
        <import resource="applicationContext-Controller.xml"/>
        <import resource="applicationContext-Dao.xml"/>
    </beans>

# JavaWeb