使用Spring框架的优点:简化Java开发

Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。但Spring不仅仅局限于服务器端开发,任何Java应用都能在简单性可测试性松耦合等方面从Spring中获益。
Spring通过以下四种策略降低Java开发的复杂性:

  1. 基于POJO的轻量级和最小侵入性编程
  2. 基于切面和惯例进行声明式编程;
  3. 通过依赖注入和面向接口实现松耦合;
  4. 通过切面和模板减少样板式代码。

一、基于POJO的轻量级和最小侵入性编程

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语句,大量的样板代码,而利用切面,可以将这些操作都放入切面中,只需关注真正的数据操作。