概述
Spring通过以下策略降低Java开发复杂性
- 基于POJO的轻量级和最小侵入性编程;
- 侵入式让用户代码产生对框架的依赖,这些代码不能在框架外使用,不利于代码的复用。但侵入式可以使用户跟框架更好的结合,更容易更充分的利用框架提供的功能。
- 非侵入式的代码则没有过多的依赖,可以很方便的迁移到其他地方。但是与用户代码互动的方式可能就比较复杂。
- 通过依赖注入(DI)和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过面向切面(AOP)和模板减少样板式代码。
传统开发
var student = new Student();
student.setName("比尔");
student.setAge(15);
student.setId(27);
简化Java开发
依赖注入
使用DI,代码会变得异常简单且易理解和测试。在传统的开发中,需要调用对象时,通常由调用者来创建被调用者的实例,即对象是由调用者主动new出来的;在spring中,则交给IoC容器new,这个控制指的就是对象的new过程。
自动注入
@Component
public class CDPlayer implements MediaPlayer{
private CompactDisc compactDisc;
@Autowired // 依赖注入
@BlankCD
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
@Autowired. // 依赖注入
@BlankCD
public CDPlayer(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
public void play() { compactDisc.play();}
}
装配(wiring)
创建应用组建之间协作的行为称为装配。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="knight" class="com.bill.knights.BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="com.bill.knights.SlayDragonQuest">
<constructor-arg ref="fakePrintStream"/>
</bean>
<!--PrintStream的实例。-->
<bean id="fakePrintStream" class="com.bill.knights.FakePrintStream" />
<bean id="minstrel" class="com.bill.knights.Minstrel">
<constructor-arg value="#{T(System).out}"/>
</bean>
</beans>
-
优点:
-
把类与类之间松解偶;修改方便;容易扩展
容易和其他系统进行数据交互
对象之间的关系一目了然
缺点:
-
配置冗长,需要额外维护;影响开发效率
类型不安全,校验不出来,出错不好排查
注解简单概括:写起来比较简单、方便,看起来也简洁,但是修改麻烦
Xml配置概括:写起来比较灵活、修改方便,但是写和维护麻烦
Java文件装配(注解)
/**
* 当你使用注解配置时,该类用来装配组件。
*/
@Configuration
public class KnightConfig {
@Bean
public Knight knight() { return new BraveKnight(quest());}
@Bean
public Quest quest() { return new SlayDragonQuest(System.out);}
}
-
优点:
-
简化配置
使用起来直观且容易,提升开发的效率
类型安全,容易检测出问题
缺点:
-
修改起来比xml麻烦
如果不项目不了解,可能给开发和维护带来麻烦
应用切面(AOP)
先来看一下一个低内聚的代码:
public class BraveKnight implements Knight {
private Quest quest;
// 关联 服务类,这是一个关注点对象
private Minstrel minstrel;
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
// 需要初始化一个关注点组件
this.minstrel = minstrel;
}
public voi embarkOnQuest() throws QuestException {
// 过于低内聚!
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
- 面向切面编程被定义为 促使软件系统实现关注点分离 的一项技术;
- 例如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系统服务通常被称为 横切关注点 ,因为它们会跨越系统的多个组件;
- 关注点的调用往往散部到各个模块中,但这些关注点不是模块的核心业务。降低了代码的内聚性,增加了耦合度;
- AOP能够使关注点模块化,以声明的方式将它们应用到它们需要影响的组件中去,从而使被影响的组件有更高的内聚性;
- 借助AOP,使用各种功能层去包裹核心业务层。
使用XML声明一个切面
<aop:config>
<aop:aspect ref="minstrel">
<!--将表达式里的执行语句命名为id,令其为切入点-->
<aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
<aop:before pointcut-ref="embark" method="singBeforeQuest"/>
<aop:after pointcut-ref="embark" method="singAfterQuest"/>
</aop:aspect>
</aop:config>
使用注解声明切面
开启代理类
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackageClasses = Performance.class)
public class ConcertConfig {
// @Bean
// public AudienceAround audience() {
// return new AudienceAround();
// }
}
通过切面,在原有方法上引入新的功能
/**
* 重写Audience,这次使用Around建言,切点表达式开头的*表示
* 任意返回类型,然后是类路径,括号里是参数类型。
* @author Bill Ludwig; 2020/4/23 10:29
*/
@Aspect
@Component
public class AudienceAround {
/**
* 一个切面方法。该注解能够在一个@AspectJ切面内定义可重用的切点
*/
@Pointcut("execution(* com.bill.concert.Performance.perform(..))")
public void action() {}
/**
* 一个通知/建言方法。通过Around把通知集中在一个代码块内
* @param jp 通过该对象调用被通知的方法
* @return 应该是返回调用被通知方法后返回的结果
*/
@Around("action()")
public Object watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!");
} catch (Throwable e) {
System.out.println("Demanding a refund!");
}
return jp;
}
}
通过切面,给对象引入新的功能
/**
* 切面类。该注解的value属性指定了哪种类型的bean要引入该接口;
* defaultImpl属性指定了为引入功能提供实现的类;而注解标注
* 的静态属性指明了要引入的接口
* @author Bill Ludwig; 2020/4/23 17:26
*/
@Aspect
@Component
public class EncoreIntroducer {
@DeclareParents(value = "com.bill.concert.Performance+",
defaultImpl = DefaultEncoreable.class)
public static Encoreable encoreable;
}
/**
* 一个为引入功能提供实现的类。即实现了Encoreable接口
* @author Bill Ludwig; 2020/4/23 17:50
*/
public class DefaultEncoreable implements Encoreable {
@Override
public void performEncore() {
System.out.println("Performing an encore!");
}
}
/**
* 测试AOP引入
* @author Bill Ludwig; 2020/4/23 20:38
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ConcertConfig.class})
public class ConcertShowTest {
@Autowired
ConcertShow concertShow;
/**
* 测试。concertShow没有实现Encoreable接口
* 但却可以使用Encoreable的方法。
*/
@Test
public void playShow() {
concertShow.perform();
((Encoreable) concertShow).performEncore();
}
}