概念
- aop (Aspect oriented programming) 面向切面编程,是oop(面向对象的一种补充)。
- 它采取的是横向抽取机制,将重复的代码提取出来,在程序编译或者执行的时候,将提取出来的代码应用到需要执行的地方。
为什莫产生
- 代码编写的时候,通常进行事务处理,日志记录等操作,在oop中可以通过组合和继承的方式来完成事务处理,日志操作等相关的行为(父类写一个方法,子类中重写这个方法)。但是如果实现了一个功能(如记录日志),在很多的子类中都会使用这个方法,但是如果这个方法出错或者需要修改,那末将需要修改所有的方法,这样会增加工作量,还会加大代码出错率。
AOP的框架
- Spring Aop
- Spring Aop 使用的是纯java实现,不需要进行编译构成和类加载器,在运行期间通过代理的方式向目标类植入增强的代码。
- AsepctJ
- 一个基于java的语言的AOp框架,在Spring2.0之后,spring加入了对AspectJ的支持,
AOP的术语
- Aspect (切面):通常指的是横向插入系统功能(事务处理,日志记录)的类
- Joinpoint(连接点):对对象的调用,也就是方法的调用或者是异常的抛出,
- Pointcut(切入点):通常在程序中指的类名或者方法名,
- Advice(通知/增强处理):在特定的切入点执行的增强处理,
- Target Object(目标对象):所有被通知的对象,
- proxy(代理):将通知应用到目标对象之后,被动态的创建对象。
- Weaving(织入):将切面代码插入到目标对象之后,从而生成代理对象的过程。
Spring Aop两种方式
JDK动态代理
目标类必须实现一个或者多个接口
- 过程
- 创建接口UserDao
- 创建接口的实现类UserDaoImpl
- 创建切面类,包含各种通知(方法)
- 创建代理类 implements InvocationHandler, 类中会重写方法invoke(),将通知方法写进去,声明目标类的接口,创建代理方法,return Proxy.newProxyInstance(类加载器,目标类的所有的接口,invoke())
UserDao
package com.yzb.chapter03.JDK;
public interface UserDao {
public void addUser();
public void deleteUser();
}
UserDaoImpl
package com.yzb.chapter03.JDK;
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
MyAspect
package com.yzb.chapter03.JDK;
/*
* 切面类 可能存在多个通知,Advice
* */
public class MyAspect {
public void check_Permissions(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("模拟记录日志");
}
}
JdkProxy
package com.yzb.chapter03.JDK;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
* JDK代理类
* */
public class JdkProxy implements InvocationHandler {
/*
* 生命目标类接口
* */
private UserDao userDao;
/*
* 创建代理方法
* */
public Object createProxy(UserDao userDao){
this.userDao = userDao;
// 类加载器
ClassLoader classLoader = JdkProxy.class.getClassLoader();
//被代理对象的所有接口
Class<?>[] interfaces = userDao.getClass().getInterfaces();
//使用代理类,进行增强,返回的是代理后的对象
return Proxy.newProxyInstance(classLoader,interfaces,this);
}
/*
* 所有动态代理的方法,都交给invoke()方法去处理
* proxy被代理后的对象
* method 将要执行的方法信息(反射)
* args 执行方法是需要的参数
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//声明切面
MyAspect myAspect = new MyAspect();
myAspect.check_Permissions();
Object invoke = method.invoke(userDao, args);
myAspect.log();
return invoke;
}
}
Test1
package com.yzb.chapter03.JDK;
public class Test1 {
public static void main(String[] args) {
//创建代理的对象
JdkProxy jdkProxy = new JdkProxy();
//创建目标对象
UserDao userDao = new UserDaoImpl();
//从代理对象中获取增强后的目标对象
UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}
CGlib动态代理
- 采用底层的字节码工作,对指定的目标类生成一个子类,并对子类进行增强。
- 过程
- 创建UserDao类
- 创建MyAspect类(包含各种通知)
- 创建MyCglib类, implement MethodInterceptor 接口,编写代理方法,核心类Enhancer,(通过new Enhancer创建一个动态类对象)(通过这个对象设置它的父类)(通过这个对象设置回调函数)(生成代理对象),在重写的方法中(前增强,目标类的方法执行,后增强)。
- 测试类Test2
UserDao
package com.yzb.chapter03.CGLIB;
public class UserDao {
public void addUser() {
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}
MyAspect
package com.yzb.chapter03.CGLIB;
/*
* 切面类 可能存在多个通知,Advice
* */
public class MyAspect {
public void check_Permissions(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("模拟记录日志");
}
}
MyCglib
package com.yzb.chapter03.CGLIB;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyCglib implements MethodInterceptor {
//代理方法
public Object createProxy(Object target){
//创建一个动态类对象,它是Cglib的核心类
Enhancer enhancer = new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
//proxy Cglib根据指定父类生成代理对象
//method 拦截方法
//args 拦截方法的参数数组
//methodProxy 方法的代理对象,用于执行父类的方法
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//声明切面类
MyAspect myAspect = new MyAspect();
//前增强
myAspect.check_Permissions();
//目标方法的执行
Object invoke = methodProxy.invoke(proxy, args);
//后增强
myAspect.log();
return invoke;
}
}
Test2
package com.yzb.chapter03.CGLIB;
public class Test2 {
public static void main(String[] args) {
//创建目标对象
UserDao userDao = new UserDao();
//创建代理对象
MyCglib myCglib = new MyCglib();
//获取增强后的目标对象
UserDao userDao1 = (UserDao) myCglib.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}
代理类的Aop实现
可以使用ProxyFactoryBean,它是实现FactoryBean,FactoryBean是实例化一个Bean,而ProxyFactoryBean是为其他的Bean创建代理实例。
- target:代理目标对象
- proxyInterfaces: 代理要实现的接口,如果是多个接口,在赋值的时候使用
- porxyTargetClass: 是否是类代理而不是接口代理,true时,使用CGlib代理
- interceptorNames:需要织入的Advice
- singleton:返回的代理是否时单例
- optimize(最优化,充分使用):设置为true的时候,强制使用CGlib
UserDao
package com.yzb.chapter03.proxyBeanFactory;
public interface UserDao {
public void addUser();
public void deleteUser();
}
UserDaoImpl
package com.yzb.chapter03.proxyBeanFactory;
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
MyAspect
package com.yzb.chapter03.proxyBeanFactory;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
check_Permissions();
Object proceed = methodInvocation.proceed();
log();
return proceed;
}
public void check_Permissions(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("模拟记录日志");
}
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!--目标类-->
<bean id="userDao" class="com.yzb.chapter03.proxyBeanFactory.UserDaoImpl"></bean>
<!--切面类-->
<bean id="myAspect" class="com.yzb.chapter03.proxyBeanFactory.MyAspect"></bean>
<!--使用Spring代理工厂定义一个名称为userDaoProxy的代理对象-->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--指定代理实现的接口-->
<property name="proxyInterfaces" value="com.yzb.chapter03.proxyBeanFactory.UserDao"/>
<!--指定目标对象-->
<property name="target" ref="userDao"/>
<!--指定切面,植入环绕通知-->
<property name="interceptorNames" value="myAspect"/>
<!--指定代理的方式,true的时候使用CGlib,false的时候使用jdk动态代理-->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
ProxyFactoryBeanTest
package com.yzb.chapter03.proxyBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ProxyFactoryBeanTest {
public static void main(String[] args) {
String path = "com/yzb/chapter03/proxyBeanFactory/applicationContext.xml";
ApplicationContext ap= new ClassPathXmlApplicationContext(path);
UserDao userDao = (UserDao) ap.getBean("userDaoProxy");
userDao.addUser();
userDao.deleteUser();
}
}
AspectJ
xml声明式的AspectJ
- 所有的切面,通知,切入点都定义在
配置切面
- 将一个定义好的SpringBean 转换成一个切面Bean,
- 元素的两个属性
- id:切面的为一表示名称
- ref:应用普通的SpringBean
配置切入点
- 两个元素
- id:作为切入点的唯一表示名称
- expression:用于指定切入点的关联的切入点表达式
execution(* com.itheima.jdk.*.*(....))
第一个*表示目标方法的返回值的类型
第二个*表示该包下的所有的类
第三个*表示的所有类下的方法名
括号俩面的点表示目标方法中的参数
配置通知
- pointcut : 该属性用于指定一个切点表达式,Spring将匹配该表达式的连接点时织入该通知,
- pointcut-ref :该属性用于指定一个已经存在的切入点名称
- method: 该属性指定一个方法名,指定将切面Bean中的该方法进行增强处理
- throwing:只在中有效,用于指定一个形参名,通过该形参抛出目标方法的异常。
- returning:在中有效,指定一个形参,通过该形参访问目标方法的返回值。
代码
UserDao
package com.yzb.chapter03.aspect;
public interface UserDao {
public void addUser();
public void deleteUser();
}
UserDaoImpl
package com.yzb.chapter03.aspect;
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
MyAspect
package com.yzb.chapter03.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/*
* 切面类,在这个类中编写通知
* */
public class MyAspect {
/*
* 前置通知
* */
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查。。。");
System.out.println("目标类是:" + joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
}
/*
* 后置通知
* */
public void myAfterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("后置通知,模拟记录日志");
System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
}
/*
* 环绕通知
* ProceedJoinPoint是Joinpoint子接口,表示可以执行目标方法
* 必须是Object类型的返回值
* 必须接收一个参数,类型为ProceedJoinPoint
* 必须throws Throwable
* */
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始
System.out.println("环绕开始,执行目标方法之前,模拟开启事务");
//执行目标方法
Object proceed = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束,执行目标方法之后,模拟关闭事务。。。");
return proceed;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知,模拟结束后的释放资源");
}
}
applicaitonContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--配置目标类-->
<bean id="userDao" class="com.yzb.chapter03.aspect.UserDaoImpl"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.yzb.chapter03.aspect.MyAspect"></bean>
<!--aop编程-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--配置切入点,通知增强那些方法-->
<aop:pointcut id="myPointCut" expression="execution(* com.yzb.chapter03.aspect.*.*(..))"></aop:pointcut>
<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!--后置通知,方法返回之后执行,获得放回值
returning属性:设置后置通知的第二个参数的名称,类型是Object
-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!--异常通知
程序没有异常,则不执行增强
throwing:用于设置第二个参数的名称,类型为Throwable
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!--最终通知,一定执行-->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
TestXmlAspectJ
package com.yzb.chapter03.aspect;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXmlAspectJ {
public static void main(String[] args) {
String path = "com/yzb/chapter03/aspect/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}
}
AspectJ的注解式声明
- 注解解决了在配置文件中书写大量的配置文件的信息。
- @Aspect: 定义一个切面
- @Pointcut:定义一个切入点;用于定义一个切入点表达式,在使用的时候,需要定义一个包含名字和参数的方法签名表示切入点的名称,这个方法签名返回值式Void,且方法体为空。
- @Before:前置通知,通常需要指定一个point/value的值
- @AfterReturning:后置通知,通常需要指定point/value的值,和一个returning,这个可以存放一个参数用来访问执行方法后的返回值,
- @Around:环绕通知,通常需要指定一个point/value
- @AfterThrowing:异常通知,通常需要指定point/vlaue,和Throwable e的值,e.getMessage()得到错误信息。
- @After:最终通知,通常需要指定point/value;
- @DeclareParents:用于定义引介通知,
代码
UserDao
package com.yzb.chapter03.aspectJ;
import org.springframework.stereotype.Component;
public interface UserDao {
public void addUser();
public void deleteUser();
}
UserDaoImpl
package com.yzb.chapter03.aspectJ;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
MyAspect
package com.yzb.chapter03.aspectJ;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/*
* 切面类,在这个类中编写通知
* */
@Aspect
@Component
public class MyAspect {
//定义切入点
@Pointcut("execution(* com.yzb.chapter03.aspectJ .*.*(..))")
public void myPointCut(){
}
/*
* 前置通知
* */
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:模拟执行权限检查。。。");
System.out.println("目标类是:" + joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
}
/*
* 后置通知
* */
@AfterReturning( value = "myPointCut()",returning ="returnVal" )
public void myAfterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("后置通知,模拟记录日志");
System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
}
/*
* 环绕通知
* ProceedJoinPoint是Joinpoint子接口,表示可以执行目标方法
* 必须是Object类型的返回值
* 必须接收一个参数,类型为ProceedJoinPoint
* 必须throws Throwable
* */
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始
System.out.println("环绕开始,执行目标方法之前,模拟开启事务");
//执行目标方法
Object proceed = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束,执行目标方法之后,模拟关闭事务。。。");
return proceed;
}
//异常通知
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
//最终通知
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知,模拟结束后的释放资源");
}
}
ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--指定需要扫描的包,式注解生效-->
<context:component-scan base-package="com.yzb.chapter03.aspectJ"/>
<!--启动基于aspectJ的声明的支持-->
<aop:aspectj-autoproxy/>
</beans>
Test5
package com.yzb.chapter03.aspectJ;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test5 {
public static void main(String[] args) {
String path = "com/yzb/chapter03/aspectJ/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}
}