赞
踩
Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。但Spring不仅仅局限于服务器端开发,任何Java应用都能在简单性、可测试性和松耦合等方面从Spring中获益。
Spring通过以下四种策略降低Java开发的复杂性:
Spring不会强迫应用继承它的类或实现它的接口从而导致应用与框架绑死,Spring尽量避免因自身的API而弄乱你的应用代码。相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。
如下面一段示例代码:
public class Person { private String name; public String getName() { return name; } public Person setName(String name) { this.name = name; return this; } public void eat(){ System.out.println(name + "吃饭..."); } }
在main方法中调用work()
方法:
public class Main {
public static void main(String[] args) {
//将Person对象的创建和依赖关系交由Spring管理
ApplicationContext ctx = new AnnotationConfigApplicationContext(AOPConfig.class);
//从Spring容器中获取Person对象
Person Jason = ctx.getBean(Person.class);
Jason.eat();
}
}
执行结果为:
在这段代码中,Person只是一个普通的Java类——POJO,没有任何地方表明它是一个Spring组件,除了main()
方法中初始化Spring容器和获取Person类对象实例的语句,完全看不出Spring的痕迹。
面向切面编程(Aspect-Oriented Programming, AOP)一方面把功能分离出来形成可重用的组件,另一方面使得开发者更专注于核心业务。
如在Person类的对象吃饭前,需要买菜、烧菜、将菜端上桌,吃完后还要收拾碗筷,若在吃饭中途碗被打碎了,还要处理碎碗,那么Person的eat()
方法代码可能是这样:
public void eat(){ System.out.println("妈妈去菜市场买菜..."); System.out.println("妈妈回家煮饭、烧菜..."); System.out.println("妈妈将香喷喷的饭菜端到饭桌..."); System.out.println("======================"); //核心业务代码 System.out.println(name + "吃饭..."); if(Math.random() > 0.5){ //模拟吃饭时碗被打碎 System.out.println("挑拣碎碗..."); System.out.println("打扫地面..."); } System.out.println("======================"); System.out.println("妈妈收拾碗筷..."); }
有经验可能是这样:
public void eat(){ prepare(); System.out.println(name + "吃饭..."); if(Math.random() > 0.5){ //模拟吃饭时碗被打碎 handleSmashedBowl(); } afterEat(); } private void prepare() { System.out.println("妈妈去菜市场买菜..."); System.out.println("妈妈回家煮饭、烧菜..."); System.out.println("妈妈将香喷喷的饭菜端到饭桌..."); System.out.println("======================"); } private void afterEat() { System.out.println("======================"); System.out.println("妈妈收拾碗筷..."); } private void handleSmashedBowl() { System.out.println("挑拣碎碗..."); System.out.println("打扫地面..."); }
而在面向切面编程中是这样:
public void eat(){
System.out.println(name + "吃饭...");
if(Math.random() > 0.5){ //模拟吃饭时碗被打碎
throw new BowlSmashedException();
}
}
可以看到eat()
方法中只保留有核心业务代码,其他的工作则在切面中配置:
@Aspect public class PersonAOP { //@Pointcut中的字符串声明Person类的eat方法为一个切点 @Pointcut("execution(* zb.spring.aop.pojo.Person.eat(..))") public void eat() { } //@Around中的值引用上面的eat()方法切点 @Around("eat()") public void around(ProceedingJoinPoint jp) { //吃饭前准备 prepare(); try { //相当于执行Person类的eat方法 jp.proceed(); } catch (Throwable t) { //处理异常 exceptionHandler(t); } //吃完后收拾 afterEat(); } private void prepare() { System.out.println("妈妈去菜市场买菜..."); System.out.println("妈妈回家煮饭、烧菜..."); System.out.println("妈妈将香喷喷的饭菜端到饭桌..."); System.out.println("======================"); } private void afterEat() { System.out.println("======================"); System.out.println("妈妈收拾碗筷..."); } //处理异常 private void exceptionHandler(Throwable t) { System.out.println(t.getMessage()); System.out.println("挑拣碎碗..."); System.out.println("打扫地面..."); } }
在main()
方法中(有异常时)执行的结果为:
这就是Spring的魔法之一,也就是所谓的面向切面编程(Aspect-Oriented Programming, AOP)
,它可以将功能分离出来,让你从重复、繁琐的样板代码中解脱出来,只需要关注于真正的业务代码。如在实际的应用中,可以使用切面来检测用户登录状态、统一异常处理、处理数据库事务等。
任何有实际意义的应用都会由两个可者更多的类组成,这些类之前进行协作来完成特定的业务逻辑。按照传统的做法,每个对象负责管理与自己协作的对象的引用,这将导致代码的高度耦合和难以测试。而在Spring的理念中,这些依赖都交由Spring管理,程序员只需要关心真正的逻辑即可。
比如一个Person类的对象要吃饭,程序员不需要知道吃饭这个去作需要依赖于哪些类或对象,只要知道Person类可以吃饭就行了。
思考上面的例子,在main方法中调用Jason对象的eat()
方法时,为什么PersonAOP类的对象知道Jason对象的eat()
方法被调用了呢?(注:注解只是用来标注,并没有任何业务逻辑)
以下是将Person对象Jason和Person的切面对象personAOP交由Spring管理的代码:
@Configuration @EnableAspectJAutoProxy @ComponentScan(basePackages = {"zb.spring.aop.aspect"}) public class AOPConfig { /** * Person类对象的切面,定义 */ @Bean public PersonAOP personAOP(){ return new PersonAOP(); } /** * 此处代码意思是创建一个name为Jason的Person * 类对象并将实例放入Spring容器中,具体含义不必深究 */ @Bean public Person person() { return new Person().setName("Jason"); } }
这样就将Person的对象和PersonAOP的对象交由Spring管理,Spring容器通过扫描Person对象的注解和PersonAOP的注解来读取两者之间的依赖关系。这样就完成了将依赖关系交给Spring容器的操作。(可能还有人问即既然注解只是用来标注的,那么AOPConfig中的配置信息又怎么被Spring发现的呢?还记得在main()方法中有个初始化容器的语句吗,new AnnotationConfigApplicationContext(AOPConfig.class)就是读取AOPConfig类中的配置信息并创建容器(应用上下文))
回到之前的提问,PersonAOP类的对象知道Jason对象的eat()方法被调用了呢?,Person类的对象Jason和PersonAOP的对象都被交由Spring容器,在PersonAOP中使用注解execution(* zb.spring.aop.pojo.Person.eat(..))
标注Person类中的eat方法为一个切点,@Around("eat()")
标注给eat()
方法添加环绕通知,Spring容器读出这些信息后就能在容器中的Jason对象的eat()
方法被执行时先来到被@Around()
标注的方法中。如果Jason对象不是交由Spring管理,而是在外部创建并调用eat()
方法,切面是无法对其产生作用的。
想想在之前开发的过程中是怎样进行数据库操作的?先要加载驱动,然后获取连接、定义预处理语句,然后才是对数据进行操作,而在这之后又要关闭资源,关闭资源时先要进行判空操作,然后再进行关闭,在关闭时甚至还要嵌套一个try catch语句,大量的样板代码,而利用切面,可以将这些操作都放入切面中,只需关注真正的数据操作。
下一篇——Spring IoC容器
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。