Spring框架
课程目标
- 理解Spring框架的两大核心
- 掌握Spring框架三种依赖注入的实现方式
- 掌握Spring框架AOP的配置及使用
课程重点
- Spring框架的三种依赖注入的实现方式
- Spring框架AOP的配置及使用
1.Spring框架
1.1.Spring框架简介
Spring是一个基于java的轻量级的、一站式框架。 虽然Spring是一个轻量级框架,但并不表示它的功能少。实际上,spring是一个庞然大物,包罗万象。 时至今日,Spring已经成为java世界中事实上的标准。
Spring之父:Rod Johnson(罗德.约翰逊) 他是悉尼大学音乐学博士,而计算机仅仅是学士学位。 由于Rod对JAVAEE笨重、臃肿的现状深恶痛绝,以至于他将他在JAVAEE实战中的经历称为噩梦般的经历。他决定改变这种现状,于是就有了Spring。


1.2.Spring体系架构

Spring 总共大约有 20 个模块,由 1300 多个不同的文件构成。而这些组件被分别整合在6 个模块中:
- 核心容器(Core Container)
- AOP(Aspect Oriented Programming)
- 设备支持(Instrmentation)
- 数据访问及集成(Data Access/Integeration)
- Web报文发送(Messaging)
- Test测试
1.3.Spring两大核心
DI:依赖注入(Dependency Injection) AOP:面向切面编程(Aspect Oriented Programming)
2.DI(依赖注入)
依赖注入(Dependency Injection)是一种设计模式,也是Spring框架的核心概念之一。其作用是去除组件之间的依赖关系,实现解耦合。 也就是说:所谓依赖注入,是指工程中需要的组件无须自己创建,而是依赖于外部环境注入。
Spring实现容器管理依赖注入有三种方式:
- xml配置文件方式(了解)
2.注解方式(官方推荐方式)
3.JavaConfig方式。
2.1.使用xml配置文件实现
下面使用 Spring 来重构dao层组件与service层组件。 也就是说:由Spring创建dao层组件和service层组件,并使用Spring将dao层组件注入给service层组件。
2.1.1. 修改pom.xml添加Spring依赖
<dependencies>
<!-- 此依赖会关联引用Spring中的所有基础jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.15</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
2.1.2.创建日志配置文件
在 resources 文件夹中创建log4j.properties配置文件
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
log4j.rootLogger=debug,stdout
2.1.3.创建dao接口与实现类
public interface UserDao {
void insert();
}
public interface UserDaoImpl implements UserDao {
public void insert(){
System.out.println("插入一行User数据");
}
}
2.1.4.创建service接口与实现类
public interface UserService {
public void addUser();
}
import com.neuedu.spring.di.dao.UserDao;
public class UserServiceImpl implements UserService{
private UserDao dao;
public void setDao(UserDao dao) {
this.dao= dao;
}
@Override
public void addUser() {
System.out.println("添加User业务");
dao.insert();
}
}
2.1.5.创建Spring配置文件
在resources目录下创建spring.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="userDao" class="com.neuedu.spring.di.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.neuedu.spring.di.service.UserServiceImpl">
<property name="dao" ref="userDao"/>
</bean>
</beans>
- Spring框架相当于一个容器。此容器中负责创建对象,并实现对象与对象之间的装配。
- java中每一个类都是一个bean。所以上面的bean标签,就是在容器中创建一个java对象。
- bean标签中的class属性,就是类名; id属性,就是对象名。
- property标签,是给bean的属性注入其它对象。name属性,就是对象属性名; ref属性,就是给属性注入的对象。(如果想要注入基本数据类型,那么使用value属性)
- 给bean的属性注入其它对象,默认使用 get/set 方法注入。也可以使用其它方式注入:构造方法注入、接口注入等详细参看。
2.1.6. 测试
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neuedu.spring.di.service.UserService;
public class XmlTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new ClassPathXmlApplicationContext("spring.xml");
}
@Test
public void test() {
UserService service = (UserService)context.getBean("userService");
service.addUser();
}
}
2.2.使用注解实现
注解(Annotation),也叫元数据。它是一种代码级别的说明,是jdk1.5之后引入的一个特性。
注解的作用:
- 编写文档:通过代码里标识的元数据生成文档。
- 代码分析:通过代码里标识的元数据对代码进行分析。
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查。
2.2.1.修改dao实现类
@Component
public interface UserDaoImpl implements UserDao {
public void insert(){
System.out.println("插入一行User数据");
}
}
@Component:此类的对象创建由Spring容器负责。 @Component("xxxx"):创建此类的对象,取一个对象名,并放入到Spring容器中。
2.2.2.修改Service实现类
@Component
public class UserServiceImpl implements UserService{
@Autowired //自动装配
private UserDao dao;
//注意:dao属性自动注入,所以就可以不用get/set方法了
public void setDao(UserDao dao) {
this.dao= dao;
}
@Override
public void addUser() {
System.out.println("添加User业务");
dao.insert();
}
}
@Autowired:默认按照类型在Spring容器寻找对象,并注入到属性中。 所以此时要注意:UserDao接口的实现类只能有一个。
@Resource注解 @Resource 是Java标准规范(JSR-250 JakartaEE)中定义的注解,用于进行依赖注入。
@Autowired和@Resource的区别:
- @Autowired默认按类型装配,@Resource默认是按名字装配 。
- @Autowire如果想使用名称装配可以结合@Qualifier注解进行使用 。
- 使用Spring框架时,建议使用@Autowired
2.2.3. 测试
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new AnnotationConfigApplicationContext("com.neuedu.spring");
}
@Test
public void test() {
UserService service = context.getBean(UserService.class);
service.addUser();
}
}
2.2.4.相关注解说明
2.2.4.1.组件级注解
除了@Component这个泛指组件的注解外,Spring还提供了与@Component功能相同的三个语义化注解。
- @Service 业务层组件
- @Controller 控制层组件
- @Repository 数据层组件
修改上面代码,使用@Repository 和 @Service 替换 dao 与 service 组件上的注解。
2.2.4.2.Bean作用范围注解
@Scope注解:设置Bean的作用域。值如下:

2.3.使用JavaConfig实现
JavaConfig,是在 Spring 3.0 开始从一个独立的项目并入到 Spring 中的。javaConfig是一个用于完成 Bean配置的 java 类。
一个类中只要标注了@Configuration注解,这个类就可以为spring容器提供Bean定义的信息了,或者说这个类就成为一个spring容器了。
类中的每个标注了@Bean的方法都相当于提供了一个Bean的定义信息。
2.3.1.创建Config类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class AppConfig {
@Bean
public List<String> list() {
return new ArrayList<>();
}
}
2.3.2.测试
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.*;
public class JavaConfigTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new AnnotationConfigApplicationContext("com.neuedu.spring");
}
@Test
public void test() {
List<String> list = context.getBean(List.class);
System.out.print(list.hashCode());
}
}
2.4.IOC与DI
IOC:控制反转(Inversion of Control):它是一种控制权的转移。即组件与组件之间的依赖由主动变为被动。也就是说:应用程序本身不再负责组件的创建、维护等,而是将控制权移交出去。
DI:依赖注入(Dependency Injection):依赖其他容器(比如spring)来创建和维护所需要的组件,并将其注入到应用程序中。
IOC只是将组件控制权移交出去,但并没有说明组件如何获取。而DI明确说明:组件依赖Spring容器获取。 所以可以这样说:DI是IOC思想的一种具体实现。
3.AOP(面向切面)
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
即当需要扩展功能时,传统方式采用纵向继承方式实现。但这种方式有很多缺点。 比如:父类方法名称改变时,子类也要修改。给多个方法扩展功能时,子类也需要修改。 因此,spring的AOP,实际上是采用横向抽取机制,取代传统的纵向继承体系。
实现AOP示意图:
1.先将方面代码抽取出来

2.运行时将业务代码和方面代码编织在一起运行

3.1.使用注解方式实现AOP
3.1.1.抽取方面代码封装通知对象
在实际开发中,除了业务逻辑这个主要功能之外,还需要处理许多辅助功能。 比如:日志、异常处理、事务、输入验证、安全等等,我们将这些代码称为:方面代码。而方面代码,就是我们要抽取出来的。
下面抽取日志方面代码:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Component
@EnableAspectJAutoProxy // 关键注解:启用AspectJ自动代理
@Aspect //@Aspect定义此类为方面代码。
public class MyAspect {
//通知方法 切入点
@Before("execution(* com.neuedu.spring.di.service.*.*(..))")
public void beforeMethod(JoinPoint joinpoint){
System.out.println(joinpoint.getSignature().getName() + "-开始执行");
}
}
- @Aspect注解:定义此类为方面代码。
- @Before注解:定义一个前置通知。即在目标方法执行前切入此注解标注的方法。
- execution() 是一个Aspect表达式,语法为:execution(返回值类型 包名.类名.方法名 (参数) 异常)
例如:execution(* com.neuedu.spring.di.service..(..))
- 第一个 *:所有的返回值类型
- 第二个 *:所有的类
- 第三个 *:所有的方法
- 第四个 .. :所有的参数
3.1.2.测试
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.neuedu.spring.di.service.UserService;
public class AopTest
{
ApplicationContext context = null;
@Before
public void before() throws Exception {
context = new AnnotationConfigApplicationContext("com.neuedu.spring");
}
@Test
public void test() {
UserService service = (UserService)context.getBean(UserService.class);
service.addUser();
}
}
3.2.五种通知类型
方面代码一般也称为通知:定义一个“切面”要实现的功能。通知有五种:
- 前置通知:在某连接点(JoinPoint 就是要织入的业务方法)之前执行的通知。
- 后置通知:当某连接点退出时执行的通知(不论是正常结束还是发生异常)。
- 返回通知:(最终通知)在这里可以得到业务方法的返回值。但在发生异常时无法得到返回值。
- 环绕通知:包围一个连接点的通知,也就是在业务方法执行前和执行后执行的通知。
- 异常通知:在业务方法发生异常时执行的通知。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Component
@EnableAspectJAutoProxy // 关键注解:启用AspectJ自动代理
@Aspect //@Aspect定义此类为方面代码。
public class MyAspect {
//通知方法 切入点
@Pointcut("execution(* com.neuedu.spring.di.service.*.*(..))")
private void anyMethod(){}
@Before("anyMethod()")
public void beforeMethod(JoinPoint joinpoint){
System.out.println( "前置通知" );
}
/* @After("anyMethod()")
public void afterMethod(JoinPoint joinpoint){
System.out.println( "后置通知" );
}
@AfterReturning(pointcut="anyMethod()",returning="result")
public void afterReturnning(JoinPoint joinpoint,Object result){
System.out.println( "返回通知" );
}
@AfterThrowing(pointcut="anyMethod()",throwing="ex")
public void afterThrowing(JoinPoint joinpoint,Exception ex){
System.out.println( "抛出通知" );
}
@Around("anyMethod()")
public Object aroundMethod(ProceedingJoinPoint pjp) {
Object obj = null;
try{
System.out.println("环绕通知日志-1" );
obj = pjp.proceed();
System.out.println("环绕通知日志-2" );
}catch(Throwable e){
e.printStackTrace();
}
return obj;
}*/
}
注意:有了环绕通知,异常通知也将失去作用
3.3.SpringAop内部实现
3.3.1.动态代理模式
动态代理是一种常用的设计模式,广泛应用于框架中,Spring框架的AOP特性就是应用动态代理实现的。

Spring框架采用两种形式动态代理: