介绍+内容回顾
上一篇写了IOC的一些基本的内容(链接),这一篇我们写一些AOP的内容吧,基本知识都在上一篇介绍过了,这里放一张图:
实现原理
AOP实现的基本原理就是反射+动态代理,而动态代理有两种底层实现,一种是JDK自带的动态代理(java.lang.reflect.Proxy; 作用:动态生成代理类和对象。),一种是CGLIB(Code Generation Library,是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架)
Proxy和CGLIB的简单对比
这两种实现的方法还是有所不同的,JDK自带的动态代理简单易用,但是有个缺陷就是只能对实现接口的类生效,简而言之,就是这个类,如果没有实现接口就不能使用JDK的动态代理,CGLIB实现的动态代理就没有这个限制,但是使用CGLIB做动态代理的时候需要导入第三方的jar包,所以使用的时候可以根据自己的情况进行选择;Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB直接对字节码进行操作,在类的执行过程中比较高效。
至于更底层的东西,不是本篇幅的重点,这里就不说了(其实也不太懂,小声BB)。
AOP( Aspect Oriented Programming )面向切面编程
OOP( Object Oriented Programming )面向对象编程
面向切面编程:基于OOP基础之上的新的编程思想
指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式
场景:
计算机运行加减乘除时的记录日志
加日志记录:
1)、直接编写在方法内部;不推荐,效率低,修改维护特别麻烦
日志记录:系统的辅助功能
业务逻辑:(核心功能)
如果写到一起就耦合了
2)、我们希望的是:
业务逻辑:(核心功能)
日志模块:在核心功能运行期间,自己动态的加上
动态代理缺点:
1.写起来十分困难
2.如果目标对象没有实现任何一个接口,就不能使用动态代理,因为jdk默认目标对象必须实现接口
代理对象和被代理对象唯一的关联就是实现了同一个接口
综上,Spring实现了AOP功能;这个功能底层就是动态代理
1.可以利用Spring一句代码都不写的去创建动态代理
实现简单,并且没用强制要求
AOP专业术语
切面类:
横切关注点
通知方法
连接点:每一个方法的每一个位置都是连接点
切入点:我们真正需要执行日志记录的地方
切入点表达式:在众多连接点中选出我们感兴趣的地方
AOP使用步骤
1.导包
spring基础包:
spring-jcl-5.0.0.RELEASE.jar
spring-expression-5.0.0.RELEASE.jar
spring-core-5.0.0.RELEASE.jar
spring-context-5.0.0.RELEASE.jar
spring-beans-5.0.0.RELEASE.jar
spring-aop-5.0.0.RELEASE.jar
spring支持面向切面编程的的包:
spring-aspects-5.0.0.RELEASE.jar 基础版的面向切面编程的包
加强版的面向切面编程包:
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
2.写配置
1)、将目标类和切面类加入到IOC容器中
2)、让spring知道哪个类是切面类 @Aspect这个注解表示这个类是切面类
3)、告诉spring通知方法什么时候、什么地方运行
通知注解
@Before,在目标方法运行之前,前置通知
@After,在目标方法运行结束之前,后置通知
@AfterReturning,在目标方法正常返回之后,返回通知
@AfterThrowing,在目标方法抛出异常之后,异常通知
@Around,环绕 ,环绕通知是Spring中最强大的通知;上边的通知4合1就是环绕通知
环绕通知里面有个参数:ProceedingJoinPoint pjp
环绕通知优于普通通知执行,并且可以吃掉异常,从而让普通通知感受不到
4)、开启基于注解的AOP模式
注意:
有接口,容器中存放的组件是代理对象
无接口,容器中存放的组件是cglib帮我们创建的代理对象
切入点表达式写法:
固定格式:execution(访问权限符 返回值类型 方法签名(参数表))
通配符:
*:
1.匹配一个或多个字符
2.匹配任意一个参数
3.*在.后匹配任意一层路径,*开头能匹配任意层任意路径
4.匹配任意一种返回值
5.不能匹配权限
..:
1.匹配任意数量和人员类型的参数
2.匹配任意多层路径
普通通知方法的执行顺序:
正常执行:@Before->@After->@AfterReturning
异常执行:@Before->@After->@AfterThrowing
环绕执行顺序(影响当前切面):
环绕前置->普通前置->目标方法执行->环绕返回/环绕出现异常->环绕后置->普通后置->普通返回/普通出现异常
拿到目标方法的详细信息:
JoinPoint joinPoint:封装了当前目标方法的详细信息
抽取可重用的切入点表达式:
1.随便声明一个没有实现的返回void的空方法
2.给方法上标注一个@Pointcut注解
5)、开启基于配置的AOP模式
<?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">
<!--基于配置的AOP
1.将目标类和切面类都加入到ioc容器中
2.告诉Spring哪个是切面类
3.在切面类中使用5个通知注解来配置切面中的这些通知方法都何时何地运行
4.开启基于注解的AOP功能
-->
<bean class="com.phj.impl.MyMathCalculator" id="myMathCalculator"/>
<bean class="com.phj.utils.LogUtils" id="logUtils"/>
<bean class="com.phj.utils.ValidateApsect" id="validateApsect"/>
<!--先配先切-->
<aop:config>
<aop:pointcut id="myPoint" expression="execution(public int com.phj.impl.MyMathCalculator.*(int,int))"/>
<!--指定切面-->
<!--在切面类中使用5个通知注解来配置切面中的这些通知方法都何时何地运行-->
<aop:aspect ref="logUtils">
<aop:before method="logStart" pointcut-ref="myPoint"/>
<aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/>
<aop:after method="logEnd" pointcut-ref="myPoint"/>
</aop:aspect>
<aop:aspect ref="validateApsect">
<aop:before method="vaStart" pointcut-ref="myPoint"/>
<aop:after-returning method="vaReturn" pointcut-ref="myPoint" returning="result"/>
<aop:after-throwing method="vaException" pointcut-ref="myPoint" throwing="e"/>
<aop:after method="vaEnd" pointcut-ref="myPoint"/>
</aop:aspect>
</aop:config>
</beans>
以上 小4和小5两种方法可以任选一种或者两种混合使用,根据自己爱好和应用场景吧
3.测试
AOP的应用场景:
1.AOP加日志保存到数据库
2.AOP做权限验证
3.AOP做安全检查
4.AOP做事务控制
4、JDK动态代理的小实现
public class CalculatorProxy {
/**
* 为传入的参数对象创建一个动态代理对象
* 参数是被代理对象
* 如果目标对象没有实现任何一个接口,就不能使用动态代理,因为jdk默认目标对象必须实现接口
* 代理对象和被代理对象唯一的关联就是实现了同一个接口
*/
public static Calculator getProxy(final Calculator calculator){
ClassLoader loader = calculator.getClass().getClassLoader();
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,帮我们的目标对象执行目标方法
InvocationHandler h = new InvocationHandler() {
/**
* Object proxy :代理对象,给jdk使用,任何时候都不要动这个对象
* Method method :当前将要执行的目标对象的方法
* Object[] args :这个方法调用时外界传入的参数值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//利用反射执行目标方法
//返回值时目标方法执行的时候的返回值
Object result = null;
try {
LogUtils.logStart(method,args);
result = method.invoke(calculator, args);
LogUtils.logReturn(method,result);
}catch (Exception e){
LogUtils.logException(method,e);
}finally {
LogUtils.logEnd(method);
}
return result;
}
};
//Proxy为目标对象创建代理对象
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
return (Calculator)proxy;
}
}
简单说一下Spring的声明式事务的两种实现
基于注解
在类上或者方法上标注@Transactional,这个注解里边还有点配置属性
在下一篇,或者下下篇里详细说吧
基于配置
配置文件:
<!--扫描-->
<context:component-scan base-package="com.phj"/>
<!--配置外部数据源配置文件-->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"/>
<property name="driverClass" value="${driverClass}"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--控制数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务控制基于xml,依赖tx名称空间和aop名称空间-->
<!--1.配置事务管理器(切面),让其进行事务控制,一定要导入面向切面的包
2.配置出事务方法
3.告诉spring哪些方法是事务方法(事务切面按照我们的切入点表达式去切入事务方法)
-->
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.phj.service.* .*(..))"/>
<!--事务建议,事务增强 advice-ref指向事务管理器的配置-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!--配置事务管理器
transaction-manager="transactionManager":指定是配置哪个事务管理器
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性-->
<tx:attributes>
<!--指明哪些方法是事务方法-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
总结
算是比较浅显的说了一下AOP的原理和使用场景以及如何使用吧,如果有错误希望大家批评指正,一起学习,一起进步。