Spring in action 4 剑指Spring - (一)初识Spring
初识Spring
spring的两大功能
Spring两大核心功能就是控制反转/依赖注入IoC/DI、面向切面编程Aop。下面介绍一下两大功能
IoC/DI
控制反转(Inversion of Control)/依赖注入(Dependency Injection),简称IoC/DI.
控制反转不是一种技术,而是一种设计思想:将原来程序需要什么对象自己创建 转变为 需要什么对象向IoC容器获取,创建对象的工作由原来程序自身控制,反转到了由IoC容器进行创建。把相关的控制权进行了反转,反转给了Spring IoC容器。
DI:Dependency Injection。即依赖注入。对象(组件)之间的依赖关系由IoC容器来进行决定。
public class App {
private PrintStream num = null;
public App(PrintStream num){
this.num = num;
}
public void hello() {
System.out.println( "Hello World!" + num);
}
}
public class AppUser {
private App app;
public AppUser(App app){
this.app = app;
}
public Integer TestApp(){
app.hello();
return 1;
}
}
Spring中,程序的对象控制权由其自身反转到了Spring容器,也就是不需要应用程序来new对象。既然不需要应用程序自身来创建Bean了,那么程序在运行的过程中,Bean从何而来呢?
Spring中的DI正是来实现IoC的一种方式:Spring容器负责维护对象(Bean)之间的依赖关系,并通过DI来向对象中注入其所依赖的对象。
xml文件:
<bean id="appTest" class="chendongdong.spring.test.bean.App">
<constructor-arg value="#{T(System).out}"></constructor-arg>
</bean>
<bean id="appUser" class="chendongdong.spring.test.bean.AppUser">
<constructor-arg ref="appTest"></constructor-arg>
</bean>
测试类:
@Test
public void shouldAnswerWithTrue() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
AppUser app = (AppUser) context.getBean(AppUser.class);
app.TestApp();
}
Aop
Aop:是Aspect oriented Programming的缩写,即面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP(面向切面编程)是OOP(面向对象编程)的一种补充,而不是一种替代品。利用AOP可以对业务逻辑的各个部分进行隔离,从而降低各个模块之间的耦合度,简化维护。常见使用AOP的场景:事务控制,日志管理,权限控制等等。
第三幅图使用了AOP的思想,将【检测活动有效性】和【检测活动是否需要登录】两个操作封装到一个单独的类(切面)。只需要在需要执行的地方,进行切入即可达到前面一样的效果。这样最大程度的降低了模块之间的耦合度。
Aop相关术语
-
1.通知(Advice):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
-
2.连接点(JoinPoint): 连接点就是在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。这个点可以是一个方法、一个属性、构造函数、类静态初始化块,甚至一条语句。 而对于 Spring 2.0 AOP 来说,连接点只能是方法。每一个方法都可以看成为一个连接点。
-
3.切入点(Pointcut):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
-
4.切面(Aspect):切面是通知和切入点的结合,通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
-
5.引入(introduction): 引入是指给一个现有类添加方法或字段属性,引入还可以在不改变现有类代码的情况下,让现有的 Java 类实现新的接口 (以及一个对应的实现 )。相对于 Advice 可以动态改变程序的功能或流程来说,引介 (Introduction) 则用来改变一个类的静态结构 。
-
6.目标(target):是要被通知(Advice)的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
-
7.代理(proxy):实现整套aop机制的,都是通过代理。
-
8.织入(weaving):把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
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可以为原有的对象增加新的属性和方法。
Aop(xml)方式实现
public class AppUser {
private App app;
public AppUser(App app){
this.app = app;
}
public Integer TestApp(){
app.hello();
return 1;
}
}
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;
}
}
<bean id="appUser" class="chendongdong.spring.test.bean.AppUser">
<constructor-arg ref="appTest"></constructor-arg>
</bean>
<bean id="logger" class="chendongdong.spring.test.bean.XmlLoggerAspect"></bean>
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut id="pointCut" expression="execution(* chendongdong.spring.test.bean.AppUser.TestApp())"/>
<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:before method="before" pointcut-ref="pointCut"/>
<aop:around method="around" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
AppUser app = (AppUser) context.getBean(AppUser.class);
app.TestApp();
}
}
结果:
--->before
--->around before
Hello aaa@qq.com
--->after
--->afterReturning : 1
around result : 1
--->around after
-----------------------------------------------------------
//在调用方法中加入异常
public Integer TestApp(){
app.hello();
int i = 1 / 0;
return 1;
}
--->before
--->around before
Hello aaa@qq.com
--->after
--->afterTrowing:/ by zero
提示
前面均使用的是AspectJ表达式,这样可以定位到有一定规律的目标方法,降低程序耦合,但是操作不是特别灵活,个人比较使用注解方式,可以指定到某一个目标方法。
@pointcut("@annotation(com.sample.security.AdminOnly)")
匹配注解有AdminOnly注解的方法