Spring 是目前各个企业必备的技术,也是各个框架技术需要整合的组件。Spring 生态技术目前已经是互联网开发中必不可少的一个技术栈。
相信读者能通过本文的学习,打下一定的 Spring 技能基础,为后面 Spring 生态的扩展技术做充足的准备。
本文大致包含了以下几方面的讲解:
Spring 多种开发方式:XML 方式、注解方式、Schema 方式、P 命名空间方式等;
Spring 核心基石:控制反转 IoC/依赖注入 DI ;
Spring 切面编程:AOP。
Spring 起源于 2003 年,Spring 技术栈两大基石控制反转 IOC(依赖注入 DI)和面向方面编程 AOP。 开发 spring 应用必须使用的 jar 包: Spring-aop.jar 开发 Spring AOP 特性; Spring-beans.jar 处理 bean; Spring-context.jar 处理上下文; Spring-core.jar Spring 核心包; Spring-expression.jar 表达式 EL/jtsl; 以及三方提供的日志 jar 包,Commons-logging.jar。 1、Spring 核心基石:控制反转 IOC/依赖注入 DI ; Spring IOC 发展史:IOC 控制反转也可以称之为 DI 依赖注入。IOC 将创建对象、属性值的方式进行了反转,从通过 new setXxx()方式反转为从 Spring IOC 容器通过 getBean()方式;依赖注入 DI,将属性值注入给属性,将属性注入给 bean,将 bean 注入给 IOC 容器。 控制反转 IOC 和依赖注入 DI 要获取对象都可以直接从 Spring IOC 容器中获取,而不需要自己操作。 控制反转 IOC 分为两步,第一步先给 Spring IOC 容器存放对象并赋值,第二步从 Spring IOC 容器中获取对象。 控制反转 IOC 容器赋值,如果简单数据类型(8 个基本数据类型+String 类型),用 value 赋值;如果是对象类型用 ref=”需要引用的 id 值”赋值,因此实现了对象与对象之间的依赖关系。 依赖注入的三种方式: 1)set 方式的依赖注入,赋值默认使用的是 set 方法。依赖注入底层是通过反射实现的,如下例所示:
<bean id="teacher" class="entity.Teacher">
<property name="name" value="liwen"></property>
<property name="age" value="35"></property>
</bean>
<bean id="course" class="entity.Course" autowire="byName">
<property name="cName" value="java"></property>
<property name="teacher" ref="teacher"></property>
</bean>
2)构造器注入,通过构造方法赋值,如下例所示:
需要注意如果的顺序与构造方法参数的顺序不一致,则需通过 type、name、index 指定。 3)P 命名空间注入,如下例所示:xmlns:p=http://www.springframework.org/schema/p 简单类型:p:属性名=“属性值”; 引用类型(除了 String 外):p:属性名-ref=“引用的 ID”。
注入各种集合类型:包括 list、map、set、array、properties; set、list、array 各自都有自己的标签、、,如下例所示:
<bean id="collectionDemo" class="entity.AllCollection">
<property name="list">
<list>
<value>足球</value>
<value>篮球</value>
</list>
</property>
<property name="array">
<list>
<value>足球 1</value>
<value>篮球 1</value>
</list>
</property>
<property name="set">
<set>
<value>足球 2</value>
<value>篮球 2</value>
</set>
</property>
<property name="map">
<map>
<entry>
<key>
<value>foot3</value>
</key>
<value>足球 3</value>
</entry>
<entry>
<key>
<value>basket3</value>
</key>
<value>篮球 3</value>
</entry>
</map>
</property>
<property name="props">
<props>
<prop key="foot4">足球 4</prop>
<prop key="basket4">篮球 4</prop>
</props>
</property>
</bean>
通过构造器方式注入,在 IOC 容器定义 bean 的前提,该 bean 的类必须提供了无参构造。赋值:
自动装配(只适用引用类型 ref):约定优于配置: (byName 本质是 byID) byName:课程 Course 类中有一个 ref 属性 teacher(属性名),并且该 IOC 容器恰好有一个 bean 的 ID 值等于该 Course 类的属性名 teacher,如下例所示:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" default-autowire="byName">
自动装配虽然可以减少代码量,但是会降低程序可读性,使用时需要谨慎。 使用注解定义 bean:通过注解的形式,将 bean 以及属性值放入 IOC 容器中。
<context:component-scan base-package=“org.dao”></context:component-scan>
Spring 在启动的时候会根据扫描的 base-package="org.dao"在该包中扫描所有类,查找这些类是否有注解@Component(“studentDao”),如果有就将该类加入 IOC 容器。 dao 层注解用@Repository; service 层注解用@Service; 控制器层注解用@Controller。
2、SpringAOP 切面编程 AOP 相关的一些名词:切面(Aspect):一个横切功能的模块化,这个功能可能会横切多个对象(业务)。例如:aMethod()方法就是一个“切面”,它横切到多个业务之中。 切入点(Pointcut):可以插入“横切逻辑(如 aMethod())”的方法。例如,“调用 add()”就是一个切点。 通知(Advice): 前置通知(Before Advice),在切入点 add()方法执行之前,插入通知。 后置通知(After Returning Advice),在切入点 add()方法执行完毕之后,插入通知。 异常通知(After Throwing Advice),当切入点 add()方法抛出异常时,插入通知。 最终通知(After Finally Advice),当切入点 add()方法执行完毕时,插入通知。(不论是正常返回还是异常退出)。 环绕通知(Around Advice),可以贯穿切入点 add()方法执行的整个过程。 把一个普通的类变成一个具有特定功能的类,一种方法继承,第二种方法实现接口,第三种方法加注解,第四种方法通过配置。 把类变成通知,通过实现接口的方式: xml 方式的通知类型: 1)前置通知 需要实现的接口:org.springframework.aop.MethodBeforeAdvice 接口中的方法:before() 执行时机:目标方法执行前 2)后置通知 需要实现的接口:org.springframework.aop.AfterReturningAdvice 接口中的方法:afterReturning() 执行时机:目标方法执行后 3)异常通知 需要实现的接口:org.springframework.aop.ThrowsAdvice 接口中的方法:无 执行时机:目标方法发生异常时 4)环绕通知 需要实现的接口:org.aopalliance.intercept.MethodInterceptor 接口中的方法:invoke() 执行时机:拦截对目标方法调用,即调用目标方法的整个过程
execution 表达式和前置通知: 需要导入 jar 包:aopalliance-1.0.jar、aspectjweaver.jar 表达式 expression 常见事例: 1)public boolean addStudent(org.entity.Student) 所有返回类型为 boolean,参数类型为 org.entity.Student 的 addStudent()的方法 2)public boolean org.service.StudentService.addStudent(org.entity.Student) org.service.StudentService 类或接口中的 addStudent()的方法,并且返回类型为参数类型为 boolean,参数类型为 org.entity.Student 3)public * addStudent(org.entity.Student) ""代表任意返回类型 4)public void (org.entity.Student) ""代表任意方法名 5)public void addStudent(…) "…"代表任意参数列表 6) org.service.StudentService..(…) org.service.StudentService包中,包含的所有方法(不包含子包中的方法) 7)* org.service.StudentService….(…) org.service.StudentService包中,包含的所有方法(包含子包中的方法) 后置通知、异常通知: 1)后置通知类实现接口
package org.aop;
import org.springframework.aop.AfterReturningAdvice;
public class LogAfter implements AfterReturningAdvice{
@Override
public void afterReturning(java.lang.Object arg0, java.lang.reflect.Method arg1, java.lang.Object[] arg2, java.lang.Object arg3) throws java.lang.Throwable{
System.out.println("后置通知");
}
}
2)业务类中实现业务方法
package org.service.impl;
import org.dao.StudentDao;
import org.dao.impl.StudentDaoImpl;
import org.service.StudentService;
import entity.Student;
public class StudentServiceImpl implements StudentService{
StudentDao studentDao=new StudentDaoImpl();
public StudentDao getStudentDao() {
return studentDao;
}
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
public void addStudent(Student student) {
studentDao.addStudent(student);
}
}
3)配置
<!-- 将业务类 addStudent()方法所在类纳入 SpingIOC 容器 -->
<bean id="studentService" class="org.service.StudentServiceImpl">
<property name="studentDao" ref="studentDao"></property>
</bean>
<!-- 将后置通知类纳入 SpingIOC 容器-->
<bean id="logAfter" class="org.aop.LogAfter"></bean>
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(public void org.service.impl.StudentServiceImpl.addStudent(entity.Student))" id="pointcut2"/>
<!-- advisor 相当于连接切入点和切面的线 -->
<aop:advisor advice-ref="logAfter" pointcut-ref="pointcut2"/>
</aop:config>
4)异常通知 根据异常通知接口的定义可以发现,异常通知的实现类,必须编写以下方法:public void afterThrowing([Method, args, target], ThrowableSubclass) 5) 环绕通知 在目标方法的前后、异常发生时、最终等各个地方都可以进行的通知,最强大的一个通知;可以获取目标方法的全部控制权(目标方法是否执行、执行之前、执行之后、参数、返回值等) 在使用环绕通知时,目标方法的一切信息都可 invocation 参数获取到,环绕通知底层通过拦截器实现。
基于注解形式的 AOP 实现:
<!-- 开启注解对 AOP 的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 类包 org.aop 放入扫描器 -->
<context:component-scan base-package="org.aop"></context:component-scan>
package org.aop;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("logAspectAnnotation") //将该类纳入 SpringIOC 容器
@Aspect //声明该类是一个通知
public class LogAspectAnnotation {
//定义切点
@Before("execution(public * addStudent(..))")
public void myBefore(){
System.out.println("注解形式前置通知");
}
@AfterReturning("execution(public * addStudent(..))")
public void myAfter(){
System.out.println("注解形式后置通知");
}
}
扫描器会将指定包中的@Component、@Service、@Respository、@Controller 修饰的类产生的对象,增加到 IOC 容器中。 @Aspect 不需要加入扫描器,只需要开启即可,如下例所示:
aop:aspectj-autoproxy</aop:aspectj-autoproxy>
通过注解形式实现的 AOP,如果想获取目标对象的参数,则需要使用对象:JoinPoint 注解形式返回值: 1)声明返回值的参数名
@AfterReturning(pointcut=“execution(public * addStudent(…))”,returning=“returningValue”)
public void myAfter(JoinPoint jp,Object returningValue){
System.out.println(“注解形式后置通知。目标对象:”+jp.getTarget()+",方法名:"+jp.getSignature().getName()+",参数列表:"+Arrays.toString(jp.getArgs())+",返回值:"+returningValue);
}
基于 Schema 形式的 AOP 实现: 1)类似于实现接口的方式 2)编写一个普通类
public class LogBeforce
将该类通过配置转为一个通知,如果要获取目标对象信息:JoinPoint 适用于注解和 Schema。
package org.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class LogSchema {
//JoinPoint 适用于注解和 Schema
public void afterReturning(JoinPoint jp,Object returnValue) throws java.lang.Throwable{
System.out.println("Schema 后置通知");
}
public void before(){
System.out.println("Schema 前置通知");
}
public void myException(JoinPoint jp,NullPointerException e){
System.out.println("Schema 异常通知");
}
//环绕通知返回目标方法的返回值
public Object myAround(ProceedingJoinPoint jp){
Object result=null;
//方法执行之前 前置通知
System.out.println("Schema 方法执行之前 环绕前置通知");
try {
//方法执行时
result=jp.proceed();
//方法执行后 后置通知
System.out.println("Schema 方法执行后 环绕后置通知");
}catch(Throwable e) {
//发生异常时 异常通知
System.out.println("Schema 发生异常时 环绕异常通知");
}
return result;
}
}