系列文章目录
前言
本文主要介绍了Spring IOC和aop的原理及实例详解,文中的示例代码非常详细,需要的朋友可以参考下。
一、什么Spring
Spring框架是一个开放源代码的J2EE应用程序框架,是针对bean的生命周期进行管理的轻量级容器。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。
1.1、Spring核心结构
Spring是?个分层?常清晰并且依赖关系、职责定位?常明确的轻量级框架,主要包括?个?模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块和 Test 模块,如下图所示:
Spring依靠这些基本模块,实现了?个令?愉悦的融合了现有解决?案的零侵?的轻量级框架。
模块 | 名称 | 功能 |
---|
Spring core | 核心容器 | 是Spring框架最核?的部分,负责bean的生命周期管理 | Spring context | 应用上下文 | 拓展了核心容器,提供事件处理、国际化等功能 | Spring AOP | 面向切面编程 | Spring管理的任何对象都支持AOP,AOP可以帮助应?对象解耦 | Data Access | 数据访问与集成 | Spring的JDBC和DAO模块封装了?量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败?引起的问题。 | web | web模块 | 提供了多种构建和其它应?交互的远程调??案。例如:SpringMVC框架,提供了Web应用上下文,对Web开发提供功能上的支持。 | Spring test | 测试模块 | 使得开发者能够很?便的进?测试. |
二、Spring核心思想
众所周知,Spring拥有两大特性:IOC和AOP。IOC,英文全称Inversion of Control,意为控制反转。AOP,英文全称Aspect-Oriented Programming,意为面向切面编程。
下面,我们简要说明下这两大特性。
2.1、IOC
IoC Inversion of Control (控制反转/反转控制),注意它是?个技术思想,不是?个技术实现。
为什么叫做控制反转? 控制:指的是对象创建(实例化、管理)的权利 反转:控制权交给外部环境了(spring框架、IoC容器)
2.1.1、IOC优点
举个例子: 传统调用一个接口:一个请求进来需要创建一个userDao
相比于传统的开发:需要什么new对象,避免了重复创建相同的对象,解决了对象之间的耦合问题。
2.2.2、IOC与DI的区别
DI:Dependancy Injection(依赖注?)
IOC和DI描述的是同?件事情,只不过?度不?样罢了
2.2.3、实战
2.2.3.1、 策略工厂模式
2.2.3.2、ApplicationContent上下文获取Service
2.2.3.3、双数据库
2.2、AOP
AOP,面向切面编程。
在开发中,为了给业务方法中增加日志记录,权限检查,事务控制等功能,此时我们需要在修改业务方法内添加这些零散的功能代码(横切面关注点)。
由于这些功能的关注点不属于业务范围,应该 业务代码中剥离出来.这时候就可以考虑AOP了。 我们可以把这些零散的功能代码放到某个模块中,简称切面。 例如:日志切面就是关注日志的模块,所以说 切面的目的是:功能的增强。
2.2.1、AOP术语
2.2.2、Advice增强
import org.checkerframework.checker.units.qual.A;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class LogUtil {
@Pointcut("execution(* com.lagou.service.impl.*.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,参数是:"+
Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue")
public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog,返回值
是:"+rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowingPrintLog(Throwable e){
System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
}
@After("pointcut()")
public void afterPrintLog(){
System.out.println("最终通知:afterPrintLog");
}
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
System.out.println("前置通知");
Object[] args = pjp.getArgs();
rtValue = pjp.proceed(args);
System.out.println("后置通知");
}catch (Throwable t){
System.out.println("异常通知");
t.printStackTrace();
}finally {
System.out.println("最终通知");
}
return rtValue;
}
}
2.2.3、实现原理
AOP的底层是通过反射创建代理对象的方式实现的。
类型 | 介绍 |
---|
静态代理 | 由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了 | 动态代理 | 在程序运行时,运用反射机制动态创建而成,无需手动编写代码 |
代理对象可以分为静态代理和动态代理,由上可知,AOP是通过动态代理的方式实现的。
java动态代理又分为jdk动态代理和cglib动态代理。
代理方式 | 描述 |
---|
JDK动态代理 | 要求目标对象实现一个接口 | CGLIB动态代理 | 它是在内存中构建构建一个子类对象从而实现对目标对象功能的扩展 |
区别:
- JDK动态代理是自带的,CGLIB需要引入第三方包
- CGLIB动态代理基于继承来实现代理,所有无法对final类、private和static方法实现代理
AOP动态代理默认策略:
- 目标对象实现了接口,则默认使用JDK动态代理
- 目标对象没有实现接口,则默认使用CGLIB动态代理
- 目标对象实现了接口,程序里面依旧可以指定使用CGLIB动态代理
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = tree)
public class AopDemoApplication{
public static void main(String[] args){
SpringApplication.run(AopDemoApplication.class,args);
}
}
2.2.4、扩展:动态代理
2.2.4.1、jdk动态代理
2.2.4.2、cglib动态代理
2.2.3、实战
2.2.3.1、事务注解@Transactions
不恰当的使用此注解,导致不起作用,异常事务无法回滚
-
1.注解@transaction应用在非public修饰的方法上 -
2.注解@Transactional 注解属性 propagation 设置错误,若是错误的配置以下三种propagation,事务将不会发生回滚。 a、 TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 b、 TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。 c、 TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。 -
3.注解@Transactional 注解属性 rollbackFor 设置错误.rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 属性。 -
4.同一个类中方法调用. -
5.异常被 catch捕获了. -
6.数据库引擎不支持事务。(仅InnoDB支持) -
7.被final、static关键字修饰的类或方法。CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰后,无法继承父类与父类的方法。 -
8.多线程环境下,事务的信息都是独立的.
多线程事务bug 举例:
主线程A调用线程B保存Id为1的数据,然后主线程A等待线程B执行完成再通过线程A查询id为1的数据。
这时你会发现在主线程A中无法查询到id为1的数据。因为这两个线程在不同的Spring事务中,本质上会导致它们在Mysql中存在不同的事务中。
Mysql中通过MVCC保证了线程在快照读时只读取小于当前事务号的数据,在线程B显然事务号是大于线程A的,因此查询不到数据。
正确使用事务代码示例:
@RestController
@Slf4j
public class ResolveServiceImpl {
@Autowired
private MemberServiceManage memberServiceManage;
@GetMapping("/resolve2")
public String addUser() {
log.info(">>>流程1");
memberServiceManage.addUserLog();
log.info(">>>流程3");
return "success";
}
}
-----------------------------------------------------------------------------------
@Component
@Slf4j
public class MemberServiceManage {
@Transactions
public String addUserLog() {
try {
Thread.sleep(1000);
} catch (BizException e) {
}
log.info(">>>流程2");
return "success";
}
}
2.2.3.2、异步注解@Async
不恰当的使用此注解,导致不起作用:
- 1.注解@Async的方法不是public方法
- 2.注解@Async的返回值只能为void或Future
- 3.注解@Async方法使用static修饰也会失效
- 4.spring无法扫描到异步类,没加注解@Async或@EnableAsync注解
- 5.调用方与被调用方不能在同一个类
- 6.类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
- 7.在Async方法上标注@Transactional是没用的.但在Async方法调用的方法上标注@Transcational是有效的。
代码示例:
@RestController
@Slf4j
public class ResolveServiceImpl {
@Autowired
private MemberServiceManage memberServiceManage;
@GetMapping("/resolve2")
public String addUser() {
log.info(">>>流程1");
memberServiceManage.addUserLog();
log.info(">>>流程3");
return "success";
}
}
-----------------------------------------------------------------------------------
@Component
@Slf4j
public class MemberServiceManage {
@Async
public String addUserLog() {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
log.info(">>>流程2");
return "success";
}
}
2.2.3.3、 本地缓存
多个地方需要更新本地缓存,为避免代码重复,功能解耦,使用切面更新本地缓存。
定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredCache {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ClearCache {}
模拟缓存
@Component
public class SimpleCache {
private Map<Object,Object> cache=new ConcurrentHashMap<>();
public boolean putObject(Object key,Object value) {
cache.put(key, value);
return true;
}
public Object getObject(Object key) {
return cache.get(key);
}
public void clearObject() {
cache.clear();
}
切面
@Aspect
@Component
public class SysCacheAspect {
@Autowired
private SimpleCache simpleCache;
@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")
public void doCachePointCut() {}
@Around("doCachePointCut()")
public Object around(ProceedingJoinPoint jp)throws Throwable{
Object obj=simpleCache.getObject("deptCache");
if(obj!=null)return obj;
Object result=jp.proceed();
simpleCache.putObject("deptCache", result);
return result;
}
@Pointcut("@annotation(com.cy.pj.common.annotation.ClearCache)")
public void doClearCachePointCut() {}
@AfterReturning("doClearCachePointCut()")
public void doAfterReturning() {
simpleCache.clearObject();
}
}
使用
public interface ResolveService {
@RequiredCache
String writeCache();
}
@Service
@Slf4j
public class Resolve1ServiceImpl implements ResolveService {
public String writeCache() {
log.info(">>>流程1");
log.info(">>>流程3");
return "success";
}
}
@Service
@Slf4j
public class WorkServiceImpl implements ResolveService, WorkService {
public String work() {
log.info(">>>流程1");
this.writeCache();
log.info(">>>流程2");
return "success";
}
public String writeCache() {
log.info(">>>流程3");
log.info(">>>流程4");
return "success";
}
}
由于数据量比较大,多节点处理时,不仅需要分页处理数据,为保证数据的一致性,还需要在切面里增加分布式锁,需要使用@before。
为啥使用切面? 1、更新本地缓存业务逻辑,高内聚。 2、本地缓存业务逻辑,存在并发问题,存在环绕业务逻辑的现象,代码冗余,单中间存在差异。
2.2.3.4、入参去空格拦截器
参考资料: 1、@Async注解的失效之谜 2、@Transactional 注解的失效场景(简述) 3、AOP缓存实现
|