Spring 是一个轻量级框架,主要解决了创建对象和管理对象的相关问题.Spring会在创建对象之后,完成必要的属性赋值,并且还持有所创建对象的引用,由于大量的对象引用,所以,被称为Spring容器.
IOC就是Spring的核心功能之一,它是帮助程序员创建对象,称之为反转,IOC组件注入的时候自动匹配规则是先按类型再按ID.
1. 首先按照注入参数类型查找相应类型的Bean组件
2. 如果在Spring容器中能够匹配上唯一类型的Bean组件,则进行注入成功
3. 如果按照类型匹配到两个Bean组件,则在查找组件ID和变量名是否匹配,如果匹配则注入成功
4. 如果组件类型和组件ID都不能很好匹配则报错误.
Spring 创建对象的做法
Spring 创建对象有两种,第一种是通过配置类的@Bean方法,第二种是组件扫描
关于@Bean注解,在任何配置类中,自定义返回对象的方法,并在方法上添加@Bean注解,则Spring会自动调用此方法,并且获取方法返回的对象,将对象保存在Spring容器中
@Configuration
public class BeanFactory {
@Bean
public LocalDateTime localDateTime() {
return LocalDateTime.now();
}
// 如果使用这种做法,则AlbumController不必使用组件扫描的做法
@Bean
public AlbumController albumController() {
return new AlbumController();
}
// 如果使用这种做法,则AlbumServiceImpl不必使用组件扫描的做法
@Bean
public AlbumServiceImpl albumServiceImpl() {
return new AlbumServiceImpl();
}
}
关于组件扫描,需要通过@ComponentScan注解来扫描根包,则Spring框架会在此根包下查找组件,并创建这些组件的对象.
组件在Spring中添加了@Compnent及其衍生注解的都是常用组件
常见组件的注解有:
@Component --- 通用组件注解
@Controller --- 添加在控制器类上
@Service --- 添加在业务逻辑类型
@Repository --- 添加在处理数据访问(直接与数据源交互)的类上
@Configuration --- 添加在配置类上
以上注解除了@Configuration以外,其他的功能,用法,执行效果方面在框架中完全相同,只是语义不同.
Spring框架在处理@Configuration注解时,会使用CGLib的代理模式来创建对象,并且,被Spring实际使用的是代理对象。
在实际实践中如果需要创建非自定义类,的对象,必须使用@Bean方法,如果需要自定义对象,建议使用组件注解,更简单一些.
Spring管理的对象作用域
Spring 管理的对象默认是单例的,则在整个程序的运行过程中,随时可以获取或访问Spring容器中的'单例'对象
Spring并没有实际使用单例模式
单例:单一实例(单一对象),即: 在任意时间,某个类的对象最多只有一个
如果需要Spring管理某个对象采取'非单例模式'可以通过@Scope("prototype")注解来实现!
如果是通过@Bean方法创建对象,则@Scope("prototype")注解添加在@Bean方法上,如果是通过组件扫描创建对象,则@Scope("prototype")注解添加在组件类上。
如果没有Spring 则需要自己创建单例模式,分为饿汉式和懒汉式
// 饿汉式
// Spring 默认饿汉式
// 饿汉式启动时就需要创建对象
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
// 懒汉式
// 懒汉式启动时也不创建,需要调用的时候才会给你创建
// 这种情况不建议,比如线程繁忙的时候刚好需要这时候才去创建就会造成压力
public class Singleton {
private static final Object lock = new Object();
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Spring管理的单例对象,默认情况下预加载的!可以通过@Lazy注解配置为懒加载的!
如果是通过@Bean方法创建对象,则@Lazy朱朱姐添加在@Bean方法上,如果通过组件扫描创建对象,则@Lazy注解添加在组件上.
自动装配机制
Spring的自动装配机制 表现为:当Spring管理类的属性需要被自动赋值,或Spring调用的方法参数性需要值是,可以在属性上添加@Autowired注解.
关于方法被调用主要表现为:构造方法,配置类中的@Bean方法等
调用构造方法
如果类中存在无参构造方法,Spring会自动调用无参构造方法
如果类中仅有一个构造方法,Spring会自动尝试调用,且如果构造方法有参数,Spring会自动尝试从容器中找到合适的值调用此构造方法
如果类中有多个构造方法,且都是有参数的,Spring不会自动调用任何构造方法,且会报错.
@Aytowired的自动装配注解机制
如果Spring调用特定构造方法,应该在那一个构造方法上添加 @Autowired注解
关于属性上使用@Autowired是提示Field injection is not recommended,其意思是“字段注入是不推荐的”
因为开发工具认为你有可能在某些情况下创建当前类的对象,例如自行编写实例化对象,由于自行创建对象,Spring框架在此过程中是不干预的,则类的属性接口将不会有Spring注入值.如果此时你也没有为这个属性赋值,则这个属性就是null,如果还执行类中的方法,就可能导致NPE异常.
这种情况可能发生在单元测试中。开发工具建议使用构造方法注入,即使用带参数的构造方法,且通过构造方法为属性赋值,并且类中只有这1个构造方法,在这种情况下,即使自行创建对象,由于唯一的构造方法是带参数的,所以,创建对象时也会为此参数赋值,不会出现属性没有值的情况,所以,通过构造方法为属性注入值的做法被认为是安全的,是建议使用的做法!
但是,在开发实践,通常并不会使用构造方法注入属性的值,因为,属性的增、减都需要调整构造方法,并且,如果类中需要注入值的属性较多,也会导致构造方法的参数较多,不是推荐的!
关于适合的值: Spirng框架会查找容器中匹配类型的对象数量
0个无法装配
1个自动装配成功
超过一个:尝试按照名称来匹配.如果均不匹配,则在加载Spring是直接报错,NoUniqueBeanDefinitonException,按照名称匹配时,要求被装配的变量名与Bean Name保持一致
关于Bean Name
每个Spring Bean都有一个Bean Name,如果是通过@Bean方法创建的对象,则Bean Name就是方法名,或通过@Bean注解参数来指定名称,
如果是通过组件扫描的做法来创建的对象,则Bean Name默认是将类名首字母改为小写的名称(例如,类名为AlbumServiceImpl,则Bean Name为albumServiceImpl)(此规则只适用于类名中第1个字母大写、第2个字母小写的情况,如果不符合此情况,则Bean Name就是类名),Bean Name也可以通过@Component等注解的参数进行配置,或者,你还可以在需要装配的属性上使用@Qualifier注解来指定装配哪个Bean Name对应的Spring Bean。
另外,在处理属性的自动装配上,还可以使用@Resource注解取代@Autowired注解,@Resource是先根据名称尝试装配,再根据类型装配的机制!
Spring管理对象的声明周期
使用组件类时,
初始化方法上添加@PostConstruct
销毁方法上添加@PreDestroy
使用@Bean方法时
初始化方法配置@Bean注解的initMethod
销毁方法配置@Bean注解destroyMethod
Spring AOP
AOP是面向切面编程,实现了横切关注的相关问题,通常是许多不同的数据处理流中都可以解决的问题.
可能处理过程中的处理请求不同,但是需要执行的一些高度相似或完全相同的代码,在某些特定的执行时间点,可以通过一些特殊的组件来完成.
例如: java EE中的Filter.Spring MVC的Interceptor,Mybatis的Interceptor,但是这些特殊的组件只能在特定时间点执行.
例如: Filter 是在服务器接收到请求的第一时间就已经执行Spring MVC的Interceprot是在Controller的前后执行,Mybatis的Intercepror是在处理SQL语句是执行,如果需要在其他执行时间节点处理相关的任务,这些组件都是不可用的!
使用AOP通常解决一下类型的问题: 安全.事务管理.日志等等
AOP技术本身并不是Spring特有的技术.只有Spring很好的支持了AOP
创建用于统计业务耗时切面类,这种切面类本身是一个组件类,并且需要添加@Aspect注解,则在跟包下创建
@Slf4j
@Aspect
@Component
public class TimerAspect {
// 在AOP中,有多种Advice(通知)
// @Around:包裹,可以实现在连接点之前和之后均自定义代码
// @Before:在连接点之前执行
// @After:在连接点之后执行,无论是正常返回还是抛出异常都会执行
// @AfterReturning:在连接点返回之后执行
// @AfterThrowing:在连接点抛出异常之后执行
// 仅当使用@Around时,方法才可以自行处理ProceedingJoinPointer
// 各Advice的执行大概是:
// @Around
// try {
// @Before
// 连接点方法
// @AfterReturning
// } catch (Throwable e) {
// @AfterThrowing
// } finally {
// @After
// }
// @Around
// ---------------------------------------------------
// 关于ProceedingJoinPoint
// 必须调用proceed()方法,表示执行表达式匹配到的方法
// 调用proceed()方法必须获取返回值,且作为当前方法的返回值,表示返回表达式匹配的方法的返回值
// 调用proceed()方法时的异常必须抛出,不可以使用try...catch进行捕获并处理
// ---------------------------------------------------
// 关于execution表达式:用于匹配在何时执行AOP相关代码
// 表达式中的星号:匹配任意内容,只能匹配1次
// 表达式中的2个连续的小数点:匹配任意内容,可以匹配0~n次,只能用于包名和参数列表部分
// 表达式中的包是根包,会自动包含其子孙包中的匹配项
@Around("execution(* cn.tedu.csmall.product.service.*.*(..))")
// ↑ 无论方法的返回值类型是什么
// ↑ 无论是哪个类
// ↑ 无论是哪个方法
// ↑ 2个小数点表示任何参数列表
public Object timer(ProceedingJoinPoint pjp) throws Throwable {
log.debug("执行了TimerAspect中的方法……");
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 执行连接点方法,获取返回结果
long end = System.currentTimeMillis();
log.debug("【{}】类型的对象调用了【{}】方法,方法的参数值为【{}】",
pjp.getTarget().getClass().getName(),
pjp.getSignature().getName(),
pjp.getArgs());
log.debug("执行耗时:{}毫秒", end - start);
return result; // 返回调用pjp.proceed()时的结果
}
切面是无侵入性的,在不修改其他类的代码情况下,就可以作用于整个数据处理过程!