文章目录
AOP概述
- AOP(Aspect Oriented Programing)是OOP的延续
- 使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理向目标类(被代理类)织入增强代码
- 用于安全检查,缓存,性能监视和事务管理
横向抽取
- 任务需求:save方法内加入新内容,如保存用户前要进行权限校验,有两种方式
- 传统方法:纵向继承体系——编写通用的DAO类,子类继承该类,从而在子类的save方法中都调用父类的校验方法
- 横向抽取机制(代理机制),分为两种
- JDK动态代理对实现某接口的类产生代理类,代理类中增强方法
- cglib第三方代理
AOP术语
- JDK的动态代理能对实现了接口的类产生动态代理(一定要实现了接口)
- 代理类增强被代理类的方法
- Joinpoint——连接点:可以被拦截到的点(方法)
- Pointcut——切入点:真正被拦截到的点(方法)
- 通知/增强(advice):方法层面的,即切面要完成的功能,拦截到连接点做的事情(前,后,环绕,异常,最终通知)
- introduction:引介,类层面的特殊通知,可在运行期动态地添加类的方法和属性
- weaving:将advice应用到target来创建代理类的过程
- Target:被代理对象或目标对象
- Proxy:代理对象,一个类被AOP织入增强后就会产生一个结果代理类
- 切面:切入点和通知的组合
- spring只支持方法层面的增强
- spring:动态代理weaving,而AspectJ(第三方工具)采用编译器织入和类装载期织入
创建项目
- 创建项目:选择maven下的webapp模板
- 新建名字为Java的文件夹——》设置为source root——>创建包
- pom.xml:添加dependency,如引入junit
- import org.junit.Test
AOP底层实现原理(两种)
JDK动态代理
public class MyJdkProxy implements InvocationHandler{
private UserDao userDao;
public MyJdkProxy(UserDao userDao){
this.userDao = userDao;
}
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验...");
return method.invoke(userDao,args);
}
return method.invoke(userDao,args);
}
}
- 定义接口A
- 被代理类去实现接口A
- 代理类动态代理
- 实现接口 InvocationHandler
- 传入代理类对象引用(接口形参),PS:代理类有一成员变量为接口类型
- 手动创建代理类,并返回—— Proxy.newProxyInstance(类加载器,接口,this【 InvocationHandler参数】)
- invoke方法内编写代理的内容
参见反射章节
- 适用于使用接口的类
CGLIB动态代理
- 该技术可以在类层面动态地增强方法
- 底层字节码增强技术,生成一个类来继承目标类,所以即使没有实现接口也行
实例
public class MyCglibProxy implements MethodInterceptor{
private ProductDao productDao;
public MyCglibProxy(ProductDao productDao){
this.productDao = productDao;
}
public Object createProxy(){
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(productDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验===================");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
}
-
创建被代理类(目标类)
-
引入spring开发包(因为spring下包含CGLIB相关的包)
-
创建代理类
- 实现接口MethodInterceptor
- 传入目标类对象引用,也可用Object类型
- 编写创建代理类方法
- 创建核心类:Enhancer enhancer = new Enhancer;
- 设置父类:enhancer.setSuperclass()——为目标类产生子类
- 设置回调enhancer.setCallback(this)
- 生成代理:
- interceptJDK方式的invoke方法
- invokeSuper方法调用父类方法,即原封不动地调用目标类方法(代理了)
代理方式总结
- 本质都是运行期生成代理类对象,不需要特殊编译器
- 不用final来修饰类或类中的方法(标记为final的方法不能被代理——》因为无法被覆盖)
- 提倡面向接口编程,优先对接口创建代理(第一种方式有线)
- spring只能对方法层面增强,不能增强属性
Spring传统AOP(手动代理)
AOP增强类型
- AOP规范由AOP联盟定义,spring最好地实现了该规范
- AOP增强类型即通知类型
- 传统的AOP增强类型分为五类:前,后,环绕,异常抛出通知,introduction通知
- aop包下
AOP切面类型
- Advisor:一般切面,增强所有方法
- PointcutAdvisor:增强特定方法
- 引介切面:增强类的方法
一般切面
- 配置Spring实现动态代理,从而对所有方法进行增强
- 要引入Spring基本包和aop联盟包及spring-aop包
-
接口A
-
创建接口A的实现类,然后创建前置通知接口的实现类
-
创建配置文件(spring.config类型)
-
配置目标类,通知接口的实现类,代理类(Spring提供的)(3种):
- bean
- target(目标类),proxyInterfaces,interceptorNames (value写通知的id)
- ref属性的值为目标类的ID
-
创建测试类 (spring-test)
- 关注强制使用CGLIB
@RunWith(SpringJUnit4ClassRunner.class) //简化测试写法,要引入spring-test的jar包
@ContextConfiguration("classpath:applicationContext.xml") //注解方式注入配置文件
public class SpringDemo3 {
// @Resource(name="studentDao") //目标类对象注入,不会增强
@Resource(name="studentDaoProxy") //要注入增强后的对象(代理的对象)
private StudentDao studentDao; //接口
@Test
public void demo1(){
studentDao.find();
studentDao.save();
studentDao.update();
studentDao.delete();
}
}
- 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目标类=======================-->
<bean id="studentDao" class="com.imooc.aop.demo3.StudentDaoImpl"/>
<!--前置通知类型=====================-->
<bean id="myBeforeAdvice" class="com.imooc.aop.demo3.MyBeforeAdvice"/>
<!--Spring的AOP 产生代理对象-->
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置目标类-->
<property name="target" ref="studentDao"/>
<!--实现的接口-->
<property name="proxyInterfaces" value="com.imooc.aop.demo3.StudentDao"/>
<!--采用拦截的名称-->
<property name="interceptorNames" value="myBeforeAdvice"/>
<property name="optimize" value="true"></property>
</bean>
</beans>
带有切入点的切面实例
- 创建目标类(使用CGLIB方式,则不需要实现接口)
- 创建某通知接口的实现类
- 环绕通知(功能最强),可阻止目标方法的执行 (不加入proceed方法)
public class MyAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕前增强===================");
Object obj = invocation.proceed();
System.out.println("环绕后增强===================");
return obj;
}
}
- 测试类中属性注入一定是注入代理类对象
- 关注配置产生代理的方法
- 使用正则表达式方式
- 配置目标类+配置通知+配置代理类+配置切面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目标类============-->
<bean id="customerDao" class="com.imooc.aop.demo4.CustomerDao"/>
<!--配置通知============== -->
<bean id="myAroundAdvice" class="com.imooc.aop.demo4.MyAroundAdvice"/>
<!--一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!表示正则表达式方式>
<!--pattern中配置正则表达式:.任意字符 *任意次数 -->
<!--<property name="pattern" value=".*save.*"/>-->
<property name="patterns" value=".*save.*,.*delete.*"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<!-- 配置产生代理 -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="customerDao"/>
<property name="proxyTargetClass" value="true"/> //表示使用CGLIB代理
<property name="interceptorNames" value="myAdvisor"/> //一般切面直接写通知名称
</bean>
</beans>
- .* 表示所有方法
- 匹配多个方法(patterns),单个方法(pattern)
Spring传统AOP(自动代理)
基于bean名称
- 该种方式类比一般切面,增强了所有方法
- 自动代理的原因:每个目标类都需要配置代理类对象——》麻烦
- 比较
- 基于类名称自动产生代理,不需要id,底层基于BeanPostProcessor ,在类的产生过程中已经生成代理
- 之前代理方式的原理:先有目标类,对目标类生成代理,而现在在类的产生过程中已经生成代理
- 因为自动产生代理,属性注入,直接注入目标类就行(所有方法都增强了),而不是如手动代理那种注入代理类的id
- value="*Dao":表示以Dao结尾的id所对应的bean类——》对这些类自动产生代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目标类-->
<bean id="studentDao" class="com.imooc.aop.demo5.StudentDaoImpl"/>
<bean id="customerDao" class="com.imooc.aop.demo5.CustomerDao"/>
<!-- 配置增强-->
<bean id="myBeforeAdvice" class="com.imooc.aop.demo5.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.imooc.aop.demo5.MyAroundAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="myBeforeAdvice"/>
</bean>
</beans>
基于切面信息
- DefaultAdvisorAutoProxyCreator
- 使用正则表达式方式来配置切面
- 配置目标类,通知,切面和DefaultAdvisorAutoProxyCreator
- advice表示增强类型
- \转义“.”:正则表达式啊
?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目标类-->
<bean id="studentDao" class="com.imooc.aop.demo6.StudentDaoImpl"/>
<bean id="customerDao" class="com.imooc.aop.demo6.CustomerDao"/>
<!-- 配置增强-->
<bean id="myBeforeAdvice" class="com.imooc.aop.demo6.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.imooc.aop.demo6.MyAroundAdvice"/>
<!--配置切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value="com\.imooc\.aop\.demo6\.CustomerDao\.save"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
</beans>