初识Spring
莫道君行早,更有早行人
文章目录
- 初识Spring
- spring的两大功能
- IoC/DI
- Xml方式
- Java配置类方式
- AOP
- Aop简单介绍
- Aop中的术语
- 举一个容易理解的例子
- 其他的一些内容
- XmlAOP方式
- Java配置类方式
本篇主要是简单的尝试一下Spring的两大功能,来感受一下Spring的强大,后面将进行更加详细的介绍。
spring的两大功能
我们都知道,Spring两大核心功能就是控制反转/依赖注入、面向切面编程。下面介绍一下两大功能。
IoC/DI
Don’t call me , I will call you!
控制反转(Inversion of Control)/依赖注入(Dependency Injection),简称IoC/DI.
控制反转不是一种技术,而是一种设计思想:将原来程序需要什么对象自己创建 转变为 需要什么对象向IoC容器获取,创建对象的工作由原来程序自身控制,反转到了由IoC容器进行创建。把相关的控制权进行了反转,反转给了Spring IoC容器。
DI:Dependency Injection。即依赖注入。对象(组件)之间的依赖关系由IoC容器来进行决定。
比如:
在UserController中需要调用UserService(暂不考虑接口设计)。则在UserController中需要其自身通过new来创建UserService对象。
如下:
UserService:
public class UserService{
private PrintStream printStream;
public UserService(PrintStream printStream){
this.printStream = printStream;
}
public void sayHello(){
printStream.println("sayHello!")
}
}
UserController:
public class UserController{
private UserService userService;
public UserController(){
this.userService = new UserService(System.out);
}
public void sayHi(){
userService.sayHello();
}
}
在Spring中,程序的对象控制权由其自身反转到了Spring容器,也就是不需要应用程序来new对象。既然不需要应用程序自身来创建Bean了,那么程序在运行的过程中,Bean从何而来呢?此时就是DI的show time了。
Spring中的DI正是来实现IoC的一种方式:Spring容器负责维护对象(Bean)之间的依赖关系,并通过DI来向对象中注入其所依赖的对象。
Xml方式
下面使用Spring的方式来设计:
public class UserService{
private PrintStream printStream;
public UserService(PrintStream printStream){
this.printStream = printStream;
}
public void sayHello(){
printStream.println("sayHello!")
}
}
public class UserController{
private UserService userService;
public UserController(UserService userService){
this.userService = userService;
}
public void sayHi(){
userService.sayHello();
}
}
spring.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">
<bean id="userService" class="com.ooyhao.spring.bean.UserService">
<constructor-arg value="#{T(System).out}"/>
</bean>
<bean id="UserController" class="com.ooyhao.spring.bean.UserController">
<constructor-arg ref="userService"/>
</bean>
</beans>
测试类:
public void testXml(){
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("spring.xml");
UserController userController = ctx.getBean(UserController.class);
userController.sayHi();
}
如果需要面向接口设计的话,接口由于无法实例化,所以在编码的时候必须指定具体的实现类,如此一来,导致无法自由动态的切换实现类,耦合度太高。而Spring xml方式的话,实现类松耦合,简化了开发,后期如果需要修改的话,直接修改xml文件,不用修改代码。
Java配置类方式
SpringConfig配置类:
public class SpringConfig{
@Bean
public UserService userService(){
UserService userService = new UserService(System.out);
return userService;
}
@Bean
public UserController userController(){
UserController userController = new UserController(userService());
return userController;
}
}
测试类:
public void testJavaConfig(){
AnnotationConfigApplicationContext ctx
= new AnnotationConfigApplicationContext(SpringConfig.class);
UserController userController = ctx.getBean(UserController.class);
userController.sayHi();
}
::: tip
确实Spring xml文件实现了松耦合,但是实际项目中可以发现,往往xml很少修改。所以,SpringBoot又主张Java配置类的方式,但是Java配置类的绝大部分都是由Spring xml转化过来的。所以,不管是Java配置类方式还是Spring xml文件方式都有必要了解,当然我觉得,由于为了后期更好的学习和使用SpringBoot,可以以Java配置类方式为主。
:::
AOP
Aop:是Aspect oriented Programming的缩写,即面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP(面向切面编程)是OOP(面向对象编程)的一种补充,而不是一种替代品。利用AOP可以对业务逻辑的各个部分进行隔离,从而降低各个模块之间的耦合度,简化维护。常见使用AOP的场景:事务控制,日志管理,权限控制等等。
Aop简单介绍
下面先用几张图熟悉一下AOP是怎么回事。(图片来源于网络)
- 从图中可以看出:不管是 【获取活动相关数据】还是 【根据条件活动奖励发放】都需要先【检测活动有效性】和【检测活动是否需要登录】这两步操作。
- 可以将【检测活动有效性】和【检测活动是否需要登录】两部操作封装到一个方法,然后在两个不同的业务中进行调用,但是这样虽然重用了代码,但是还是将两步不是必须耦合在一起的代码耦合在了一起。
- 而第三幅图则使用了AOP的思想,将【检测活动有效性】和【检测活动是否需要登录】两个操作封装到一个单独的类(切面)。只需要在需要执行的地方,进行切入即可达到前面一样的效果。这样最大程度的降低了模块之间的耦合度。
Aop中的术语
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
举一个容易理解的例子
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了, 我当时也是花了老大劲才梳理清楚的.
下面我以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系.
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?
- Join point : 爪哇的小县城里的百姓: 因为根据定义, Join point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joinpoint. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
- Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joinpoint) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.
- Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.
- Aspect::Aspect 是 pointcut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect.
其他的一些内容
AOP中的Joinpoint
可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中我们可以在上面的这些Joinpoint
上织入我们自定义的Advice
,但是在Spring中却没有实现上面所有的joinpoint
,确切的说,Spring只支持方法执行类型的Joinpoint
。
Advice 的类型
-
before advice
, 在 join point
前被执行的 advice
. 虽然 before advice
是在 join point
前被执行, 但是它并不能够阻止join point
的执行, 除非发生了异常(即我们在 before advice
代码中, 不能人为地决定是否继续执行 join point
中的代码) -
after return advice
, 在一个 join point
正常返回后执行的 advice
-
after throwing advice
, 当一个 join point
抛出异常后执行的 advice
-
after(final) advice
, 无论一个 join point
是正常退出还是发生了异常, 都会被执行的 advice
. -
around advice
, 在 join point
前和 joint point
退出后都执行的 advice
. 这个是最常用的 advice
. -
introduction
,introduction
可以为原有的对象增加新的属性和方法。
在Spring中,通过动态代理和动态字节码技术实现了AOP
,这些内容,我们将在以后进行讲解。
下面分别使用XML和Java配置类实现AOP
XmlAOP方式
目标类:
public class Person {
public String sayHello(){
System.out.println("Person say Hello!");
return "sayHelloMethod";
}
public String sayBye(){
System.out.println("Person say ByeBye!");
return "sayByeMethod";
}
}
切面类:
public class XmlLoggerAspect {
public void before(){
System.out.println("--->before");
}
public void after(){
System.out.println("--->after");
}
public void afterReturning(Object returnVal){
System.out.println("--->afterReturning : " + returnVal);
}
public void afterThrowing(Exception exception){
System.out.println("--->afterTrowing:"+exception.getMessage());
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("--->around before");
Object proceed = joinPoint.proceed();
System.out.println("around result : "+proceed);
System.out.println("--->around after");
return proceed;
}
}
xml配置文件:
<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
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--业务类Target-->
<bean id="person" class="com.ooyhao.spring.aop.Person"/>
<!--切面类-->
<bean id="loggerAspect" class="com.ooyhao.spring.aspect.XmlLoggerAspect"/>
<aop:config>
<aop:aspect ref="loggerAspect">
<!--public String com.ooyhao.spring.aop.Person.sayHello()
* String com.ooyhao.spring.aop.Person.sayHello()
* com.ooyhao.spring.aop.Person.sayHello()
* *.sayHello()
* *.say*()
* *.say*(..)
-->
<aop:pointcut id="pointCut" expression="execution(* *.say*(..))"/>
<aop:before method="before" pointcut-ref="pointCut"/>
<aop:after method="after" pointcut-ref="pointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnVal"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="exception"/>
<aop:around method="around" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
测试:
@Test
public void testXmlAop(){
ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext("spring-aop.xml");
Person bean = context.getBean(Person.class);
bean.sayHello();
System.out.println("=================");
bean.sayBye();
/**
*
* --->before
* --->around before
* Person say Hello!
* around result : sayHelloMethod
* --->around after
* --->afterReturning : sayHelloMethod
* --->after
* =================
* --->before
* --->around before
* Person say ByeBye!
* around result : sayByeMethod
* --->around after
* --->afterReturning : sayByeMethod
* --->after
*/
//sayHello出现 int i = 1/0;时
/**
*
* --->before
* --->around before
* Person say Hello!
* --->afterTrowing:/ by zero
* --->after
*
* java.lang.ArithmeticException: / by zero
* at com.ooyhao.spring.aop.Person.sayHello(Person.java:7)
*
* */
}
由上测试结果可以看出:
- 正常执行的执行过程
before
– > around before
--> target method
--> around after
--> afterReturning
--> after
- 出现异常的执行过程
before
--> around before
--> target method
--> afterTrowing
--> after
Java配置类方式
java配置方式的切面:
@Aspect
@Component
@EnableAspectJAutoProxy
public class ConfigLoggerAspect {
@Pointcut("execution(**.say*()))")
public void pointCut(){}
@Before("pointCut()")
public void before(){
System.out.println("--->before");
}
@After("pointCut()")
public void after(){
System.out.println("--->after");
}
@AfterReturning(value = "pointCut()", returning = "returnVal")
public void afterReturning(Object returnVal){
System.out.println("--->afterReturning : " + returnVal);
}
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrowing(Exception exception){
System.out.println("--->afterTrowing:"+exception.getMessage());
}
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("--->around before");
Object proceed = joinPoint.proceed();
System.out.println("around result : "+proceed);
System.out.println("--->around after");
return proceed;
}
}
-
@Aspect
: 定义为一个切面 -
@Component
:定义为一个Spring
组件 -
@EnableAspectJAutoProxy
:开启Aop
自动代理模式
Java配置类:
@ComponentScan(basePackages = "com.ooyhao.spring")
public class AopConfig {
@Bean
public Person person(){
return new Person();
}
}
-
@ComponentScan(basePackages = "com.ooyhao.spring")
: 将前面的切面进行扫描成组件。
测试类:
@Test
public void testJavaConfigAop(){
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(AopConfig.class);
Person bean = context.getBean(Person.class);
bean.sayHello();
bean.sayBye();
}
:::tip
提示:
前面均使用的是AspectJ表达式,这样可以定位到有一定规律的目标方法,降低程序耦合,但是操作不是特别灵活,个人比较使用注解方式,可以指定到某一个目标方法。
@pointcut("@annotation(com.sample.security.AdminOnly)")
// 匹配注解有AdminOnly注解的方法
:::
源码地址:https://gitee.com/ooyhao/JavaRepo_Public/tree/master/Spring-in-Action