文章目录
AOP 面向切面
AOP 理论概述
概述:
- AOP(Aspect Oriented Programming) 面向切面编程,作为编程范式指导开发者如何组织程序结构。
- 结合 Spring 理念,无侵入式地对方法进行功能增强。
- 将交叉业务(日志、事务等)与核心业务分离,使开发者更关注业务逻辑。
AOP核心:
- JoinPoint(连接点):在程序执行过程中的某个阶段点,指的接口或实现类中所有的方法。
- PointCut(切入点):需要加强的方法,通过规则来选中切入点。
- Advice(通知):切入点处所要执行的程序代码,即要执行的公共方法;通知的类型有:前置通知、后置通知、异常通知、最终通知、环绕通知。
- Aspect(切面):含有 PointCut 和 Advice 的类,Aspect = PointCut + Advice。
- Target(目标对象):被代理的对象,即需要方法被增强的类。
- Weaving(织入):指把 Advice 用于目标对象,创建代理对象的过程。
- Proxy(代理):一个类被 AOP 织入增强后产生的结果类,即代理类。
XML 实现案例
编写目标类。
//BookDao是Book类service层接口 @Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println(System.currentTimeMillis()); System.out.println("book dao save ..."); } }
编写切面类。
// 负责计时的切面类 public class TimerAspect { public void time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long begin = System.currentTimeMillis(); //执行目标 proceedingJoinPoint.proceed(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } }
编写 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" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--纳入spring bean管理--> <bean id="bookDao" class="com.lxl.dao.BookDao"/> <bean id="timerAspect" class="com.lxl.dao.TimerAspect"/> <!-- aop配置 --> <aop:config> <!-- 切点表达式 --> <aop:pointcut id="tPoint" expression="execution(* com.lxl.dao.BookDao.*(..))"/> <!-- 切面类 --> <aop:aspect ref="timerAspect"> <!-- Aspect = PointCut + Advice --> <aop:around method="time" pointcut-ref="tPoint"/> </aop:aspect> </aop:config> </beans>
Annotation 实现案例
导入坐标。
<!-- spring context依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0-M2</version> </dependency> <!-- spring aop依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.0-M2</version> </dependency> <!-- spring aspects依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.0-M2</version> </dependency>
开启Sring注解包扫描、AOP功能。
@Configuration @ComponentScan("com.lxl") @EnableAspectJAutoProxy //开启注解开发AOP功能 public class SpringConfig { }
定义业务接口与实现类。
//BookDao是Book类service层接口 @Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println(System.currentTimeMillis()); System.out.println("book dao save ..."); } public void update(){ System.out.println("book dao update ..."); } }
定义切面类:通知与切入点,并进行织入。
@Component //切面类必须配置成 Spring 管理的 bean @Aspest //表示当前类为切面类 public class MyAdvice { // 直接使用切入点表达式形式,前置通知指定切入点 @Before("execution(void com.lxl.dao.BookDao.update())") public void method(){ System.out.println(System.currentTimeMillis()); } // 设置可复用的切入点表达式 // 设置切入点,@Pointcut 注解要求配置在方法上方 @Pointcut("execution(void com.lxl.dao.BookDao.update())") private void pt(){} @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
Spring AOP 原理
- Spring 容器启动。
- 读取所有切面配置中的切入点。
- 初始化 bean,判定 bean 对应的类中的方法是否匹配到任意切入点:
- 匹配失败,创建原始对象。
- 匹配成功,创建原始对象(目标对象)的代理对象。
- 获取 bean 执行方法:
- 获取的 bean 是原始对象时,调用方法并执行,完成操作。
- 获取的 bean 是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作。
切入点表达式
概念:切入点表达式是对要进行增强的方法的描述方式,可定义接口或其实现类的方法作为切入点。
切入点表达式规范:
- 格式:
切点函数(访问修饰符 返回类型 类全名.方法名(参数类型) 异常类型)
- 切点函数:描述切入点的行为动作,例如 execution 表示执行到指定切入点。
- 访问修饰符:public,private 等(可省略)。
- 返回值类型。
- 包类名:多级包名使用点连接(可省略)。
- 参数:直接写参数的类型,多个类型用逗号隔开。
- 异常名:方法定义中抛出指定异常(可省略)。
表达式通配符:
- * 单个独立的任意符号,表示任意一个,可独立出现,也可作为前缀或者后缀的匹配符出现。
- … 多个连续的任意符号,表示任意个数(0~n),可以独立出现,常用于简化包名与参数的书写。
- + 专用于匹配某类的子类类型。
execution(public * com.lxl.*.UserService.find*(*))
//匹配com.lxl包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
execution(public User com..UserService.findById(..))
//匹配com包下或任意子包中的UserService类或接口中所有名称为findById的方法
execution(* com.lxl.AccountService+.update(..))
//匹配com.lxl包下的AccountService类或接口的子类
execution(public int com.lxl.service.impl.AccountServiceImpl.save(java.lang.String))
//最精确
execution(int save(java.lang.String))
//省略 修饰符, 包名.类名
execution(* com.*.service.AccountService.update())
//返回值任意, com下的任意一个子包的service包中的AccountService类的update方法
execution(* com..AccountService.update())
//返回值任意, com下的任意子包中的AccountService类的update方法
execution(* com.lxl.service.AccountService.*())
// 0个参数的方法
execution(* com.lxl.service.AccountService.*(*))
// 1个参数的方法
execution(* com.lxl.service.AccountService.*(..))
// 0个或1个或多个参数
execution(* com.lxl.service.AccountService.*e(..))
// e结尾的方法
execution(* com.lxl.service.AccountService.u*(..))
// u开头的方法
execution(* save(..)) || execution(* update(..))
//方法名是save或update的方法
!execution(* save(..))
//除了方法名是save的所有方法
通知类型
通知类型:
- 前置通知:目标方法执行前执行,如果通知中抛出异常,阻止目标方法运行。
- 后置通知:目标方法正常执行完毕并返回结果后执行,如果目标方法中抛出异常,无法执行。
- 最终通知:目标方法执行后执行,无论目标方法中是否出现异常,都将执行通知。
- 异常通知:目标方法抛出异常后执行,如果目标方法没有抛出异常,无法执行。
- 环绕通知:在目标方法执行前后均有对应执行,还可以阻止目标方法的执行。
通知注解:
- @Before 通知方法在目标切入点方法前运行。
- @AfterReturning 通知方法在目标切入点方法正常执行完毕后运行。
- @AfterThrowing 通知方法在目标切入点方法运行抛出异常后执行。
- @After 通知方法在目标切入点方法后运行。
- @Around 在目标切入点方法前后运行,灵活且适配性强(常用)。
环绕通知注意事项:
- 环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对目标方法的调用,进而实现目标方法调用前后同时添加通知
- 通知中如果未使用 ProceedingJoinPoint 对目标方法进行调用将跳过目标方法的执行
- 目标方法的调用可不接收返回值,通知方法设置成 void 即可,如果接收返回值,必须设定为 Object。
- 目标方法的返回值如果是 void 类型,通知方法的返回值类型可以设置成 void ,也可以设置成Object。
通知获取目标方法数据:
- 获取切入点方法的参数:
- JoinPoint:适用于前置、最终、后置、异常通知
- ProceedJointPoint:适用于环绕通知
- 获取切入点方法返回值:
- 后置通知
- 环绕通知
- 获取切入点方法运行异常信息:
- 异常通知
- 环绕通知
@Before("pt()") public void before(JoinPoint jp) { Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); } @AfterReturning(value = "pt()",returning = "rName") public void afterReturning(Object rName) { System.out.println("afterReturning advice ..." + rName); } @AfterThrowing(value = "pt()",throwing = "tName") public void afterThrowing(Throwable tName) { System.out.println("afterThrowing advice ..." + tName); } @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = null; Object[] args = pjp.getArgs(); //获取参数数组 System.out.println(Arrays.toString(args)); try { ret = pjp.proceed(args); //获取返回值 } catch (Throwable t) { //捕获异常信息 t.printStackTrace(); } return ret; }
动态代理模式
JDK动态代理具体实现原理:
- 通过实现 InvocationHandler 接口创建自己的调用处理器。
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理。
- 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型。
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入。
- JDK动态代理是面向接口的代理模式,如果被代理目标没有接口,那么 Spring 也无能为力,Spring 通过 Java 的反射机制生产被代理接口的新的匿名实现类,重写了其中 AOP 的增强方法。
CGLib动态代理:
- CGLib 是一个强大、高性能的 Code 生产类库,可以实现运行期动态扩展 java 类,Spring 在运行期间通过 CGlib 继承要被动态代理的类,重写父类的方法,实现 AOP 面向切面编程。
两者对比:
JDK 动态代理是面向接口的。
CGLib 动态代理是通过字节码底层继承要代理类来实现(如果被代理类被 final 关键字所修饰,那么抱歉会失败)。
CGLib 所创建的动态代理对象在实际运行时候的性能要比 JDK 动态代理高。
CGLib 在创建对象的时候所花费的时间却比 JDK 动态代理要多很多。
Spring事务管理
概述:
- Spring 事务作用是在数据层或业务层保障一系列的数据库操作同成功或同失败。
Spring事务体系:
实现步骤
JdbcConfig配置类中,创建事务管理器放到Spring容器中。
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager ptm = new DataSourceTransactionManager(); ptm.setDataSource(dataSource); return ptm; } //事务管理器要根据实现技术进行选择 MyBatis 框架使用的是 JDBC 事务
在Spring配置类中开启事务管理器。
@EnableTransactionManagement public class SpringConfig { }
业务层接口上添加Spring事务管理
public interface AccountService { @Transactional public void transfer(String out,String in ,Double money); } //Spring 注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合 //可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
@Transactional 属性
属性 | 作用 | 示例 |
---|---|---|
readOnly | 设置是否为只读事务 | readOnly=true 只读事务 |
timeout | 设置事务超时时间 | timeout = -1(永不超时) |
rollbackFor | 设置事务回滚异常(class) | rollbackFor = {NullPointException.class} |
rollbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor = {NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
事务传播行为
事务角色:
- 事务管理员:发起事务方,在Spring中通常指业务层开启事务的方法。
- 事务协调员:加入事务方,在Spring中通常指数据层方法,也可以是业务层方法。
事务传播行为:
- 事务协调员对事务管理员所携带事务的处理态度。即外层方法(事务管理员)中执行了某个其他方法(事务协调员),被执行者与执行者的事务关系。
public interface LogService { //propagation 设置事务属性:传播行为设置为当前操作需要新事务。 //只需设置事务协调员的事务传播行为即可。 @Transactional(propagation = Propagation.REQUIRES_NEW) void log(String out, String in, Double money); }
Spring常用注解
IOC 注解
@Component(任何层)
- @Controller
- @Service
- @Repository(dao)用于实例化对象
@Scope : 设置Spring对象的作用域。
@PostConstruct、@PreDestroy : 用于设置Spring创建对象在对象创建之后和销毁之前要执行的方法。
@Bean: 表在方法上,用于将方法的返回值对象放入容器。
DI 注解
- @Value: 简单属性的依赖注入。
- @Autowired: 对象属性的依赖注入。
- @Qualifier: 要和 @Autowired 联合使用,代表在按照类型匹配的基础上,再按照名称匹配。
- @Resource: 按照类型和属性名称依赖注入 @Resource = @Autowired+@Qualifier。
- @ComponentScan: 组件扫描。
- @Lazy: 延迟加载bean对象,即在使用时才去初始化,可减少Spring的IOC容器启动时的加载时间、解决bean的循环依赖问题。
AOP 注解
- @Before: 前置通知,会在运行原有方法前面执行。
- @AfterReturning: 后置通知,会在运行原有方法后面执行,前提原有方法不发生异常。
- @AfterThrowing: 异常通知,会在运行原有方法发生异常的时候运行。
- @After: 最终通知,会在运行原有方法后运行, 无论原有方法是否发生异常都会运行。
- @Around: 环绕通知,一个环绕就可以实现上面4个位置的增强
- @Aspect: 标识当前类为切面类。
- @Pointcut: 切入点表达式。
事务注解
- @Transactional: 此注解可以标在类上,也可以标在方法上,表示当前类中的方法具有事务管理功能。
其他配置
- @PropertySource: 用于引入其它的properties配置文件。
- @Import: 在一个配置类中导入其它配置类的内容。
- @Configuration: 被此注解标注的类,会被 Spring 认为是配置类。Spring 在启动的时候会自动扫描并加载所有配置类,然后将配置类中 bean 放入容器。
- @ConfigurationProperties: Bean 上添加上这个注解,指定好配置文件的前缀,对应的配置文件数据就会自动填充到 Bean 中。