系列笔记:
- SpringBoot实战(1)-Spring 4.x
- SpringBoot实战(2)-Spring MVC 4.x
- SpringBoot实战(3)-Spring Boot
- SpringBoot实战(4)-Spring Boot Web&Data
- SpringBoot实战(5)-Spring Boot 企业级开发
- SpringBoot实战(6)-Spring Boot 开发部署与测试
- SpringBoot实战(7)-Spring Boot 应用监控
- SpringBoot实战(8)-Spring Boot 分布式系统开发
简单记录下《JavaEE开发的颠覆者SpringBoot实战》的学习心得。 先贴下读者学习时的一些代码记录:
本书基于
Spring 4.X
和Spring Boot 1.3.0
展开(最低要求Java1.6,推荐1.8),主打基于注解和 Java 配置的零配置(无xml配置),由于读者学习时是基于Spring Boot 2.1.1 进行的,故文中 Spring的源码是基于Spring 5.1的,Spring Boot 的源码是基于2.1.1的 。
概念
控制反转(Inversion of Control, IoC)不等同于依赖注入(Dependency Injection, DI),实际上它们有着本质上的不同:控制反转是一种思想(设计原则),用来降低代码之间耦合度,而依赖注入是实现控制反转的一种形式。
Spring @Bean
Java配置是Spring 4.x、Spring Boot 推荐的配置方式,可以完全替代想,xml配置。
在 Spring 中是通过@Configuration和@Bean来实现的:
- @Configuration 声明当前类时一个配置类,相当于一个Spring配置的xml文件
- @Bean 注解在方法上,声明当前方法的返回值为一个Bean(Bean的名称可以是方法名)。
在Spring容器中,只要容器中存在某个Bean,就可以在另外一个Bean的声明方法的参数中注入。@Bean 代表将当前方法返回的 POJO 装配到 IoC 容器中,而其属性 name 定义这个 Bean 的名称,如果没有配置它,则将当前方法名称作为 Bean 的名称保存到 Spring IoC 容器中 。
Java 配置方式:使用@Bean的 initMethod 和 destoryMethod(相当于xml配置的init-method和destory-xml)。例如:
@Bean(initMethos="init",destoryMethod="destory")
注解方式:利用JSR-250的 @PostConstruct和@PreDestory(需要引入 jsr250-api jar包)。
分别代表在构造函数执行完之后进行和在Bean销毁之前执行。
条件注解@Conditional
@Confition 可根据满足某一特定条件创建一个特定的Bean(可以根据特定条件来控制Bean的创建行为)。在Spring Boot 中有大量应用。
相关特定条件类可通过实现 Condition 接口的 matches() 方法来定义判断条件,然后通过
@Conditional(XXXCondition.calss)
来使用。
Profile
Profile 为在不同环境下使用不同配置提供了支持。
通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。在开发中使用@Profile注解类或者方法,达到在不同情况下选择实例化不同的Bean。
可通过设定jvm的spring.profiles.active 参数来设置配置环境。
使用时应该先设置活动的Profile,后注册Bean配置;类,不然会报Bean未定义的错误。
Spring 中简单使用示例:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("prod");
ctx.register(XXXConfig.class);
ctc.refresh();
Spring AOP
AOP: 面向切面的编程,相对于OOP面向对象编程。
SpringAOP存在的目的是为了解耦。AOP可以让一组类共享相同的行为,在OOP中只能通过继承类(且为单继承)和实现接口,会使代码的耦合度增强,AOP弥补了OOP的不足。
Spring 支持 AspectJ 的注解式切面编程:
- 1、使用@Aspect 声明是一个切面
- 2、使用@After、@Before、@Around 定义建言(advice),可直接将拦截规则(切点)作为参数。
- 3、其中@After、@Before、@Around 参数的兰姐规则称为切点(PointCut),为了使切点复用,可使用 @PointCut 专门定义拦截规则,然后在@After、@Before、@Around 的参数中调用。
- 4、其中符合条件的每一个被拦截处为连接点(JoinPoint)。
Spring 支持基于注解拦截和基于方法规则拦截两种方式,注解式拦截可以很好的控制要拦截的粒度和获得更丰富的信息,Spring本身在事务处理(@Transcational)和数据缓存(@Cacheable)等上面都是使用此种形式的拦截。
启用Spring AOP 需要引入 spring-aop、aspectjrt、aspectjweaver 包。
可通过 @Aspect 声明一个切面,使用 @Component 让该切面成为Spring容器管理的Bean。
通过 @PointCut 声明切点。
通过 @After、@Before、@Around 等声明一个建言,可以使用@PointCut定义的切点,如:如:@Pointcut("@annotation(com.xxx.aop.Action)")
;也可以直接使用拦截规则作为参数,如:@Before("execution(*com.xxx.aop.XXXService.*(..))")
。
通过 @EnableAspectJAutoProxy 注解开启Spring对AspectJ的支持。
后记
- 如果被代理的目标对象实现了接口,那么Spring会默认使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。
- 如果是被代理类的方法自调用,在自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生 AOP,因为这样Spring就不能把你的代码织入到约定的流程中。
- 需要代理的对象方法不能是private的,因为Spring不管使用的是JDK动态代理还是CGLIB动态代理,一个是针对接口实现的类,一个是通过子类实现。无论是接口还是父类,显然都不能出现 private 方法,否则子类或实现类都不能覆盖到。如果方法为private,那么在代理过程中,根本找不到这个方法,引起代理对象创建出现问题,也就可能会导致有的对象没有注入成功。
元注解
所谓元注解其实就是可以注解到别的注解上的注解,被注解的注解称之为组合注解。
Spring 中有很多组合注解,比如:@Configuration 就是一个组合 @Component 注解,表名这个类其实也是一个Bean。
Java注解之 @Target、@Retention、@Documented
先来看一个Spring中的一个常用注解示例:
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 组合 @Component 注解
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any
*/
// 覆盖 value 参数
String value() default "";
}
@Target({ElementType.TYPE})
ElementType 这个枚举类型的常量提供了一个简单的分类:注释可能出现在Java程序中的语法位置(这些常量与元注释类型(@Target)一起指定写入注释的合法位置在何处)。
源码摘要:
package java.lang.annotation;
// @since 1.5
public enum ElementType {
/** 类, 接口 (包括注释类型), 或 枚举 声明 Class, interface (including annotation type), or enum declaration */
TYPE,
/** 字段声明(包括枚举常量)Field declaration (includes enum constants) */
FIELD,
/** 方法声明 Method declaration */
METHOD,
/** 正式的参数声明 Formal parameter declaration */
PARAMETER,
/** 构造函数声明 Constructor declaration */
CONSTRUCTOR,
/** 局部变量声明 Local variable declaration */
LOCAL_VARIABLE,
/** 注释类型声明 Annotation type declaration */
ANNOTATION_TYPE,
/** 包声明 Package declaration */
PACKAGE,
/**
* 类型参数声明 Type parameter declaration
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 使用的类型 Use of a type
* @since 1.8
*/
TYPE_USE
}
@Retention({RetentionPolicy.Runtime})
RetentionPolicy 这个枚举类型的常量描述保留注释的各种策略,它们与元注释(@Retention)一起指定注释要保留多长时间。
源码摘要:
package java.lang.annotation;
// @since 1.5
public enum RetentionPolicy {
/**
* 注释只在源代码级别保留,编译时被忽略 (Annotations are to be discarded by the compiler)
*/
SOURCE,
/**
* 注释将被编译器在类文件中记录,但在运行时不需要VM保留。这是默认的行为。
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* 注释将被编译器记录在类文件中,在运行时被VM保留,因此可以反读。
* (Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Documented
表示默认情况下,javadoc 和类似工具将记录带有类型的注释。 此类型应用于注释类型的声明,其注释会影响其客户端对带注释元素的使用。 如果使用 Documented 注释类型声明,则其注释将成为带注释元素的公共API的一部分。
Spring 单元测试
Spring 提供了一个 SpringJUnit4ClassRunner 类,他提供了 Spring TestContect Framework 的功能。通过 @ContextConfiguration 来配置 Application Context,通过 @ActiveProfiles 确定活动的profile。
import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
// RunWith是指定使用的单元测试执行类(指定的类需继承org.junit.runners.BlockJUnit4ClassRunner,@since junit 4.4)
// 在junit环境下提供 Spring TestContect Framework 的功能,
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {XXXConfig.class}) // 用来加载配置 ApplicationContext,其中 classes 属性用来加载配置类。
@ActiveProfiles("prod") // 用来声明活动的 profile
// 这个用于指定在测试类执行之前,可以做的一些动作,TransactionalTestExecutionListener.class用于对事务进行管理
@TestExecutionListeners({ TransactionalTestExecutionListener.class })
/**
* 不是必须的,这里是和@TestExecutionListeners中的TransactionalTestExecutionListener.class
* 配合使用,用于保证插入的数据库中的测试数据,在测试完后,事务回滚,将插入的数据给删除掉,保证数
* 据库的干净。如果没有显示的指定@Transactional,那么插入到数据库中的数据就是真实的插入了。
*/
@Transactional
public class XXXTests {
// xxxx
}
Spring 常用概念
IOC
IOC(Inversion Of Controll,控制反转)是一种设计思想,将原本在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC容器是Spring用来实现IOC的载体,IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。
这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂,当需要创建一个对象,只需要配置好配置文件/注解即可,不用考虑对象是如何被创建出来的,大大增加了项目的可维护性且降低了开发难度。
AOP
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量,提高了系统的扩展性。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
Spring AOP / AspectJ AOP 的区别?
Spring AOP属于运行时增强,而AspectJ是编译时增强。
Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。如果切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。
评论区