目录
3.1 JDK动态代理(通过Proxy类的newProxyInstance方法来实现)
一、AOP的概念和作用
AOP:面向切面编程(也称面向方面编程,是面向对象编程(OOP)的一种补充)。
AOP作用:aop采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或者运行时,再将这些提取出来的代码应用到需要执行的地方。AOP是OOP的延伸和补充,不是OOP的替代品。AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。
AOP思想中,类和切面的关系:
二、AOP术语
- Aspect(切面):切面通常是指封装的用于横向插入系统功能(如事务、日志等)的类
- JoinPoint(连接点):在程序执行过程中的某个阶段点。AOP中指方法的调用。
- PointCut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点。
- Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法。
- Target Object(目标对象):被增强的对象。如果AOP采用的是动态的AOP实现,那么该对象就是一个被代理对象。
- Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
- Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
三、动态代理
3.1 JDK动态代理(通过Proxy类的newProxyInstance方法来实现)
JDK动态代理是通过java.lang.reflect.Proxy类来实现的,通过调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。——使用动态代理的对象必须实现一个或多个接口。
(一)创建UserDao接口及其实现类UserDaoImpl
本案例会将实现类UserDaoImpl作为目标类,对其中的方法进行增强处理。
//创建UserDao接口,并添加相应方法
package com.haust.dao;
public interface UserDao {
public void add();
public void delete(int id);
public void update(int id);
public void query();
}
//创建UserDao接口的实现类UserDaoImpl,并实现相应方法
package com.haust.dao.impl;
import org.springframework.stereotype.Repository;
import com.haust.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("添加用户...");
}
@Override
public void delete(int id) {
// TODO Auto-generated method stub
System.out.println("删除用户..."+id);
}
@Override
public void update(int id) {
// TODO Auto-generated method stub
System.out.println("修改用户..."+id );
}
@Override
public void query() {
// TODO Auto-generated method stub
System.out.println("查询用户...");
}
}
(二)创建切面类
//创建切面类,在该类中定义一个模拟权限检查的方法和一个模拟日志记录的方法,这两个方法就表示切面中的通知
package com.haust.advice;
//切面类,可以存在多个通知Advice(即增强的方法)
public class MyAdvise {
public void check_permission(){
System.out.println("模拟权限控制...");
}
public void log(){
System.out.println("模拟日志记录...");
}
}
(三)创建代理类
package com.haust.utils;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import com.haust.advice.MyAdvise;
import com.haust.dao.UserDao;
public class JdkProxy implements InvocationHandler {
//声明目标类接口
private UserDao userDao ;
//创建代理方法
public Object createProxy(UserDao userDao){
this.userDao = userDao;
//第一个参数是类加载器,通过字节码对象(userDao.getClass())获得类加载器
ClassLoader loader = userDao.getClass().getClassLoader();
//第二个参数是目标类的接口中的所有方法,通过目标类的字节码对象(userDao.getClass())获得所有方法的接口
Class[] interfaces = userDao.getClass().getInterfaces();
return Proxy.newProxyInstance(loader, interfaces,this);
}
/*
* 所有动态代理类的方法调用,都会由invoke()方法去处理
* Object proxy:表示代理对象
* Method method:目标对象的方法对象
* Object[] args:目标对象方法的参数
*/
@Override
public Object invoke(Object proxy, Method methods, Object[] args)throws Throwable {
//声明切面类
MyAdvise myadvise = new MyAdvise();
//前增强
myadvise.check_permission();
//在目标类上调用方法,并传入参数
Object retValue = methods.invoke(userDao, args);
//后增强
myadvise.log();
return retValue;
}
}
(四)测试类
package com.haust.test;
import org.junit.Test;
import com.haust.advice.MyAdvise;
import com.haust.dao.UserDao;
import com.haust.dao.impl.UserDaoImpl;
import com.haust.utils.JdkProxy;
public class TestReflect {
@Test
public void jdkProxyTest(){
//创建代理对象
JdkProxy jdkProxy = new JdkProxy();
//创建目标对象
UserDao userDao = new UserDaoImpl();
//从代理对象中获取增强后的目标对象
UserDao userProxy = (UserDao) jdkProxy.createProxy(userDao);
//执行方法
userProxy.add();
userProxy.delete(10);
}
}
(五)结果显示
3.2 CGLIB代理
CGLIB是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类
生成一个子类,并对子类进行增强。如果想代理没有实现接口的类,就可以使用CGLIB代理。(在spring的核心包中已经集成了CGLIB所需要的包,所以开发中不需要另外导入jar包)
通过一个案例来实现Spring中的CGLIB动态代理。
(一)创建一个目标类UserDao,不需要实现任何接口
package com.haust.dao.impl;
//目标类
public class UserDao {
public void add() {
System.out.println("添加用户...");
}
public void delete(int id) {
System.out.println("删除用户..."+id);
}
}
(二)创建代理类CglibProxy
package com.haust.utils;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.haust.advice.MyAdvise;
public class CglibProxy implements MethodInterceptor {
//代理方法
public Object createProxy(Object terget){
//创建一个动态代理类
Enhancer enhancer = new Enhancer();
//确定需要增强的目标类,设置其父类
enhancer.setSuperclass(terget.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/*
*Objcet proxy :根据指定父类生成的代理对象
*Method method : 拦截的方法
*Object[] args : 拦截方法的参数数组
*methodProxy : 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy, Method methods, Object[] args,
MethodProxy methodproxy ) throws Throwable {
//创建切面类对象
MyAdvise myadvise = new MyAdvise();
//前增强
myadvise.check_permission();
//目标方法执行
Object obj = methodproxy.invokeSuper(proxy, args);
//后增强
myadvise.log();
return obj;
}
}
(三)测试类
import com.haust.utils.CglibProxy;
import com.haust.dao.UserDao;
import org.junit.Test;
public class TestReflect {
@Test
public void CjlibProxyTest(){
//创建代理对象
CglibProxy cglibproxy = new CglibProxy();
//创建目标对象
UserDao userDao =new UserDao();
//获取增强后目标对象
UserDao userProxy = (UserDao) cglibproxy.createProxy(userDao);
//执行方法
userProxy.add();
userproxy.delete(10);
}
}
(四)结果显示
四、AspectJ开发
AspectJ是基于Java语言的AOP框架,新版本的Spring框架,建议使用AspectJ来开发AOP
所需要的jar包:
4.1 基于XML的声明式Aspectj
基于XML的声明式AspectJ是指通过XML文件来定义切面,切入点,通知。
常用的配置代码如下:
<!-- 定义切面Bean -->
<bean id="myAspect" class="com.haust.aspect.MyAspect"></bean>
<aop:config>
<!-- 1.配置切面 -->
<aop:aspect id="aspect" ref="myAspect">
<!--2. 配置切入点 -->
<aop:pointcut expression="execution(* com.haust.dao.*.*(..))" id="pointCut"/>
<!--3. 配置通知-->
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="pointCut"/>
<!-- 后置通知 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="pointCut" returning="returnVal"/>
<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="pointCut"/>
<!-- 异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="pointCut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
<!--2. 配置切入点 -->
<aop:pointcut expression="execution(* com.haust.dao.*.*(..))" id="pointCut"/>该切入点表达式的意思是匹配com.haust.dao包中任意类的任意方法的执行。其中execution()是表达式的主体,第一个*表示的是返回类型,代表返回所有类型;com.haust.dao表示的是需要拦截的包名,后面第二个*表示的是类名,代表所有的类;第三个*代表的是方法名,使用*代表所有的方法;(...)表示方法中的参数,“...”代表任意参数。需要注意的是,第一个*和包名之间有一个空格。
通过一个案例来实现基于XML的AspectJ开发
(一)编写一个切面类MyAspect.java
package com.haust.aspect.xml;
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()); //joinPoint.getTarget 获取被代理对象
System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知,模拟记录日志....");
System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
/*
* 环绕通知
* ProceedingJoinPoint是JoinPoint子接口,表示可以执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接受一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务....");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务....");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知"+"出错了"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知:模拟方法结束后的释放资源....");
}
}
/*JoinPoint对象
* JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以
* 获取到封装了该方法信息的JoinPoint对象
* 方法名:
* Signature getSignature():获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的class等信息
* Object[] getArgs():获取传入目标方法的参数对象
* Object[] getTarget():获取被代理的对象
* Object getThis():获取代理对象
*
* ProceedingJoinPoint对象:
* ProcecedingJoinPoint对象是JoinPoint对象的子接口,该对象只用在@Around的切面方法中
* 该子接口添加了两个方法 :
* Object proceed() throws Throwable //执行目标方法
* Object proceed(Object[] varl) throws Throwable//传入的新的参数去执行目标方法
*/
(二)编写配置文件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:context="http://www.springframework.org/schema/context"
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
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">
<!-- 目标类 -->
<bean id="userDao" class="com.haust.dao.impl.UserDaoImpl"></bean>
<!-- 切面 -->
<bean id="myAspect" class="com.haust.aspect.xml.MyAspect"></bean>
<!-- aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.haust.dao.*.*(..))" id="myPointCut"/>
<!-- 连接切面和切入点 -->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
(三)编写一个测试TestXmlAspectj.java
package com.haust.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.haust.dao.UserDao;
public class TestXmlAspectj {
public static void main(String[] args) {
ApplicationContext applicationcontext = new ClassPathXmlApplicationContext("applicationContext.xml");
//1.从Spring容器中获得内容
UserDao userdao = (UserDao) applicationcontext.getBean("userDao");
//2.执行方法
userdao.delete(10);
}
}
(四)结果显示
4.2 基于注解的声明式AspectJ
基于代理类的AOP实现相比,基于XML的声明式AspectJ要简单的多,但是基于XML的方式在Spring文件中要配置大量的代码信息,为了解决这一问题,就有了基于注解的声明式AspectJ。
AspectJ的注解及其描述:
用一个案例来实现基于注解的声明式AspectJ:
(一)编写切面类/MyAspectAnnocation.java
package com.haust.aspect.annocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspectAnnocation {
//定义切入点表达式
@Pointcut("execution(* com.haust.dao.*.*(..))")
//使用一个返回值为void,方法体为空的方法来命名切入点
private void myPointCut(){}
@Before("myPointCut()")
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知,模拟执行权限检查....");
System.out.println("目标类是"+joinPoint.getTarget());
System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
@AfterReturning("myPointCut()")
//后置通知
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知,模拟记录日志....");
System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
/*
* 环绕通知
* ProceedingJoinPoint是JoinPoint子接口,表示可以执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接受一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
*/
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务....");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务....");
return obj;
}
@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("最终通知:模拟方法结束后的释放资源....");
}
}
(二)编写配置文件aspectAnnocation.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:context="http://www.springframework.org/schema/context"
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
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">
<!-- xmlns:aop="http://www.springframework.org/schema/aop"
使用aop命名空间,在配置文件中打开相应的注解处理器 -->
<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan base-package="com.haust"></context:component-scan>
<!-- 启动基于注解声明式AspectJ支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
(三)编写测试类TestAnnocationAspectj.java
package com.haust.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.haust.dao.UserDao;
public class TestAnnocationAspectj {
public static void main(String[] args) {
ApplicationContext applicationcontext = new ClassPathXmlApplicationContext("aspectAnnocation.xml");
UserDao userdao = (UserDao) applicationcontext.getBean("userDao");
userdao.add();
}
}
(四)结果显示
注意:如果在同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知额执行顺序是未知的,目标方法之后的后置通知和环绕通知的执行顺序也是未知的。