目录
一、认识Spring框架
二、控制反转和依赖注入(IoC+DI)
三、面向切面编程(AOP)
1、AOP原理
2、AOP六大基本概念
横向关注点
切面
连接点
切点
通知
四、AOP使用核心案例
1、新建Spring项目
2、创建简单业务类
3、创建切面类
4、创建配置类
5、创建测试主类
五、BUG解决方法
1、Bug1:java.util.prefs.WindowsPreferences
2、Bug2:WARN Please initialize the log4j system properly
一直以来,久仰Spring大名,却不知是何物,望文生义,释义“春天”。最近从零开始学习,有了一些最基本的理解,故记录于此。
其实,我很好奇Spring框架名字的由来,特意查了一下:
名字来自于自然界,实际上Spring代表传统J2EE冬天的过去,标志着Java EE开发的新时代,所以认同了这个简单优雅的名字。
Spring框架的强大之处在于对J2EE开发进行了简化,对大部分常用的功能进行了封装。其中的实现原理依赖于两种技术原理:控制反转(Inverse of Control)和面向切面编程(Aspect Oriented Programing)。
Spring框架有各大模块组成,在Spring 5 中,分别有:
- 核心模块Core:依赖注入(DI)、面向切面编程(AOP)、事件处理(events)、资源访问(resources)、数据绑定(data binding)、i18n(国际化)等。
- 测试模块Testing:支持Spring MVC Test、WebTestClient、TestContext Framework、mock objects测试框架。
- 数据访问模块Data Access:事务处理(transaction)、数据访问对象(DAO)、数据库连接(JDBC)、对象关系映射(ORM)和处理XML(Marshalling XML)。
- Spring MVC模块和Spring WebFlux Web Framework模块。
- 集成模块Integration:远程访问(remoting)、消息服务(JMS)、加解密(JCA)、管理扩展(JMX)、邮件管理(mail)等。
- 支持语言Languages:支持使用Kotlin、Groovy等语言进行开发。
学到这里,发现Spring的功能特别全面和强大,“书山有路,学海无涯”再次给了最好的诠释,对于刚学习的小白来说,不知从何入手,所以接下来从最基础部分,也是很重要的技术原理开始学习。
在平时普通的编程项目中,如果在A.java中要使用B.java类,那就必须在A中实例化B类对象,造成了A、B类之间的紧耦合,成为“侵入式开发”。假设随着项目的更新和升级,需要增加更多的功能,业务代码中B类需要进行大量扩展和改动,那这样在A类中也需要改动,很明显这样不利于软件的更新和维护。
因此,Spring的IoC技术解决了这种问题,通过反射技术,实现控制反转。IoC将调用者A和被调用者B分离,类与类直接关系解耦,满足“低耦合”的期望。
如上图,Spring框架的IoC技术实现A类和B类的解耦,A类中不用出现new B()的情况,而是将实例化任务交给Spring来完成,再使用反射机制将实例化对象赋值给A中的b对象。
原先是A主动实例化B类的对象,而现在成为被动方,有Spring框架来实现,实现了反转,所以说“控制反转”。
IoC在此成为了一种设计思想,具体在赋值的实现方式是依赖注入(DI)。在Spring中管理JavaBean的容器就是IoC容器,上面的B类对象有容器创建,容器再对A中的b进行对象值的注入,所以DI使用了反射技术进行了类属性的动态赋值。IoC/DI从编程技术看是将接口和实现方法进行分离,IoC容器管理JavaBean的生命周期和多个JavaBean的注入关系。
一种技术的诞生必是解决一类问题,Spring的AOP技术则是解决软件项目中具有通用性的问题,比如程序日志信息,包括了执行时间、执行者、效率和结果等信息。
如果在源程序中增删日志代码,一是会影响程序运行的效率,二是难以模块化维护软件,显得繁琐。所以,基于动态代理的AOP技术,可以在几乎不修改代码的基础上,对原模块的功能进行扩展,将通用性的日志模块解耦出来,易于模块化管理。
比如,在不改变Servlet代码的基础上加上日志功能;在不改变Spring MVC框架的控制层代码基础上增加数据库事务的功能。
Spring AOP技术是基于代理设计模式的原理,所谓代理,在这里的理解就是,为其他对象提供一种代理以控制对这个对象的访问。有时候,一个对象不适合或者不能直接引用另一个对象,需要代理对象在客户端和目标对象之间起中介的作用。
代理设计模式分为静态代理和动态代理,静态代理由于代理类绑定了固定的接口,不利于扩展,我们主要学习一下动态代理。
在Java中,实现动态代理有4种常见的方式:
后面案例会使用Spring框架实现AOP动态代理。
横向关注点就是通用性的功能。常用的横向关注点有方法执行时间记录、访问权限验证、常规日志、数据库事务处理。
它把通用性代码如日志功能代码和Service业务代码进行分离,达到解耦合的目的。与DI不同的性质,AOP的解耦主要是横向关注点的代码和业务代码之间的分离,而依赖注入是对象间的解耦。
切面是对横向关注点的模块化,每个切面是一个横向关注点功能的实现,它将通用性的代码提取出来放入单独的类中统一处理,方便复用。这个类就是切面类,面向切面编程就是主要对切面编写代码。
连接点是在软件程序中能够插入切面的一个点。连接点的位置可以是调用方法前/后、抛出异常时、返回值后等。
切点则是部分连接点,减少了连接点数量的一个概念,针对切点应用切面,提高软件运行效率。
在Spring AOP中,通知分为5种:
(1)前置通知(Before):方法调用前。
(2)后置通知(After):方法调用后。
(3)环绕通知(Around):方法调用前后。
(4)返回通知(After-returning):方法有返回值。
(5)异常通知(After-throwing):方法出现异常。
切面包含了通知和切点,是两者的结合。更容易地理解为,通知定义了应用切面的时机;切点定义了在哪些连接点上放置切面。
前面大量知识或许还不能理解,“纸上得来终觉浅”,接下来在AOP简单应用的案例中,会更直观看到理论在实践中的应用。
IDE:Intellij IDEA U版本
JDK:jdk1.8.0
Spring:Spring 5.2.3
Aspect:Aspectj1.9
在src文件下创建包Spring_AOP,我的文件结构如下:
使用注解的方式实现前面提到的Advice,注解是Spring中的一个重要应用,可以减少人工配置xml的繁琐工作。
/*
* UserinfoService.java
* Copyright (c) 2021-02-25
* LastModified:2021/2/25 上午10:48
* Author : Charzous
* Blog : https://blog.csdn.net/Charzous
* All right reserved.
*/
package Spring_AOP.aopDemo.service;
import org.springframework.stereotype.Service;
@Service(value = "a")
public class UserinfoService {
public void method1() {
System.out.println("method 1 ");
}
public String method2() {
System.out.println("method 2");
return "我是方法2返回值";
}
public String method3() {
System.out.println("method 3:测试异常");
Integer.parseInt("a");//测试error
return "我是方法3返回值";
}
}
/*
* AspectObject.java
* Copyright (c) 2021-02-25
* LastModified:2021/2/25 上午10:55
* Author : Charzous
* Blog : https://blog.csdn.net/Charzous
* All right reserved.
*/
package Spring_AOP.aopDemo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectObject {
@Around(value = "execution(* Spring_AOP.aopDemo.service.UserinfoService.*(..))")//
//注解实现环绕通知
public Object around(ProceedingJoinPoint point) {
Object returnObject = null;
try {
System.out.println("环绕开始");
returnObject = point.proceed();
System.out.println("环绕结束");
} catch (Throwable e) {
e.printStackTrace();
}
return returnObject;
}
//*:任意返回值;service.UserinfoService.* service包下UserinfoService类中任意方法;.. 任意类型的参数
@Before(value = "execution(* Spring_AOP.aopDemo.service.UserinfoService.*(..))")
public void before() {
System.out.println("Before 方法");
}
@After(value = "execution(* Spring_AOP.aopDemo.service.UserinfoService.*(..))")
public void after() {
System.out.println("After 方法");
}
@AfterReturning(value = "execution(* Spring_AOP.aopDemo.service.UserinfoService.*(..))")
public void afterReturning() {
System.out.println("afterReturning");
}
@AfterThrowing(value = "execution(* Spring_AOP.aopDemo.service.UserinfoService.*(..))")
public void afterThrowing() {
System.out.println("afterThrowing");
}
}
在代码中实现了前置、后置、返回、环绕和异常通知,其中使用到切面表达式进行业务类的匹配。
可以看到,切面表达式是一致的,进一步优化,可以通过全局切点来减少表达式冗余。
/*
* AspectObject.java
* Copyright (c) 2021-02-25
* LastModified:2021/2/25 上午10:55
* Author : Charzous
* Blog : https://blog.csdn.net/Charzous
* All right reserved.
*/
package Spring_AOP.aopDemo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectObject {
//使用全局切点,使表达时全局化,减少冗余
@Pointcut(value = "execution(* Spring_AOP.aopDemo.service.UserinfoService.*(..))")
public void publicPointCut(){
}
@Around(value = "publicPointCut()")//"execution(* Spring_AOP.aopDemo.service.UserinfoService.*(..))"
//注解实现环绕通知
public Object around(ProceedingJoinPoint point) {
Object returnObject = null;
try {
System.out.println("环绕开始");
returnObject = point.proceed();
System.out.println("环绕结束");
} catch (Throwable e) {
e.printStackTrace();
}
return returnObject;
}
//*:任意返回值;service.UserinfoService.* service包下UserinfoService类中任意方法;.. 任意类型的参数
@Before(value = "publicPointCut()")
public void before() {
System.out.println("Before 方法");
}
@After(value = "publicPointCut()")
public void after() {
System.out.println("After 方法");
}
@AfterReturning(value = "publicPointCut()")
public void afterReturning() {
System.out.println("afterReturning");
}
@AfterThrowing(value = "publicPointCut()")
public void afterThrowing() {
System.out.println("afterThrowing");
}
}
使用注解的方式,扫描指定包中的各个类,Spring会完成自动配置。该类同时实现了Spring动态代理,注意需要使用包全名,才能正确配置。
/*
* MyContext.java
* Copyright (c) 2021-02-25
* LastModified:2021/2/20 下午10:28
* Author : Charzous
* Blog : https://blog.csdn.net/Charzous
* All right reserved.
*/
package Spring_AOP.aopDemo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {"Spring_AOP.aopDemo.service","Spring_AOP.aopDemo.aspect"})
public class MyContext {
}
/*
* aopDemo_test.java
* Copyright (c) 2021-02-25
* LastModified:2021/2/20 下午6:58
* Author : Charzous
* Blog : https://blog.csdn.net/Charzous
* All right reserved.
*/
package Spring_AOP.aopDemo;
import Spring_AOP.aopDemo.service.UserinfoService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class aopDemo_test {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(MyContext.class);
UserinfoService service=(UserinfoService) context.getBean(UserinfoService.class);
service.method1();
System.out.println();
System.out.println("get method 2 return value="+service.method2());
System.out.println();
System.out.println("get method 3 return value="+service.method3());
}
}
可以看到,ApplicationContext使用了注解方式来解析上下文,另一种方式是XML文件解析。
method 3测试异常通知,结果运行如下:
我在测试运行程序时候,出现了Warning警告。
java.util.prefs.WindowsPreferences
WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
虽然不影响运行,但还是解决为好。
解决:
打开Windows注册表编辑器,找到下面的目录
HKEY_LOCAL_MACHINE\Software\JavaSoft
然后新建文件夹 Prefs。
出现另一种警告为:
log4j:WARN No appenders could be found for logger (org.springframework.XXX).
log4j:WARN Please initialize the log4j system properly.
这是Mybatis中log4j消息日志框架,因为我创建的另一个包用到Mybatis框架,对这次运行影响不大,输出警告信息。
解决:
只需要在src下新建log4j.properties文件,内容为:
#
# log4j.properties
# Copyright (c) 2021-02-25
# LastModified:2021/2/24 下午9:42
# Author : Charzous
# Blog : https://blog.csdn.net/Charzous
# All right reserved.
#
log4j.rootLogger=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
到此,Spring 5 核心技术AOP简单基础实践全部完成,Spring框架的强大之处在于对J2EE开发进行了简化,对大部分常用的功能进行了封装。其中的实现原理依赖于两种技术原理:控制反转(IoC)和面向切面编程(AOP)。这篇主要认识Spring的基础知识和实现AOP,有了一些最基本的理解,故记录于此。
如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/114044697