目录
第一节 AOP
1.1 AOP概述
- AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理来实现程序功能的统一维护的一种技术。
- AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
- 经典应用:事务管理、性能监视、安全检查、缓存 、日志等
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
- AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
1.2 AOP实现原理
- aop底层采用代理机制进行实现。
- 接口 + 实现类 : 可以采用JDK的Proxy来生成代理对象。
- 有接口的实现类或没有接口的类:都可以采用cglib字节码增强来实现代理 。
1.3 AOP术语
- target:目标类,需要被代理的类。例如:UserService
- Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
- PointCut 切入点:已经被增强的连接点。例如:addUser()
- advice 通知/增强,增强代码。例如:after、before
- Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程
- proxy 代理类
- Aspect(切面):是切入点pointcut和通知advice的结合
一个线是一个特殊的面。
一个切入点和一个通知,组成一个特殊的面。
1.4 手动代理
JDK动态代理
- 代码编写格式,必须要使用接口+实现类实现Proxy
- 目标类UserService接口编写
public interface UserService {
//切面编程
public void addUser();
public void updateUser();
public void deleteUser();
public int deleteUser(int id);
}
- 目标类UserService实现类编写
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("添加用户...");
}
@Override
public void updateUser() {
System.out.println("更新用户...");
}
@Override
public void deleteUser() {
System.out.println("删除用户...");
}
@Override
public int deleteUser(int id) {
System.out.println("通过id删除用户...");
return 1;
}
}
- 切面类MyAspact编写
//切面类:增强代码与切入点的结合
public class MyAspect {
public void before(){
System.out.println("开启事务...");
}
public void after(){
System.out.println("提交事务...");
}
}
- 工厂类MyBeanFactory编写
public class MyBeanFactory {
public static UserService createUserService(){
//1.创建目标对象target(目标类)(内部类访问外面要加final,jdk1.7这里会加final)
final UserService userService = new UserServiceImpl();
//2.声明切面类对象(切面类)
final MyAspect myAspect = new MyAspect();
//3.把切面类2个方法应用到目标类
//3.1创建JDK代理(代理类)
/*newProxyInstance(
ClassLoader loader, 类加载器,写当前类
Class<?>[] interfaces, 接口,代表接口的方法会被拦截(代理类所要实现的接口)
InvocationHandler h) 处理类,一般写匿名类
*/
//将代理类对象强转成我们需要的UserService对象
UserService serviceProxy = (UserService) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//开启事务(增强代码)
myAspect.before();
//放行方法
Object retObj = method.invoke(userService,args);
//这个方法的返回值是业务方法的返回值
//System.out.println("拦截的返回值:"+retObj);
//提交事务(增强代码)
myAspect.after();
return retObj;
}
});
//原来这里返回的是userService,因为现在的所有事都由代理去做
//所以要返回service的代理
return serviceProxy;
}
}
- 测试
@Test
public void test1(){
//自己实现AOP编程思想,使用JDK代理来实现
UserService userService = MyBeanFactory.createUserService();
userService.deleteUser(2);
userService.updateUser();
userService.addUser();
//代理对象可以转换成我们需要的类型,代理对象有实现类的功能
}
- 执行效果(只要调用方法,就会被拦截加强)
- 使用代理,里面使用的就是代理对象(Proxy),代理对象还有实现类的功能
- 不使用代理,里面就没有代理对象
cglib增强字节码
- 有接口的实现类或没有接口的类:都可以采用cglib字节码增强来实现代理
- 采用字节码增强框架cglib(实现AOP编程),在运行时创建目标类的子类,从而对目标类进行增强。
- jar包:
- 核心:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar
- 依赖:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar
- spring-core…jar 已经整合以上两个内容
- 无接口的StudentService类编写
public class StudentService {
public void add() {
System.out.println("添加学生...");
}
public void update() {
System.out.println("更新学生...");
}
public void delete() {
System.out.println("删除学生...");
}
}
- 切面类MyAspact的编写同上
- 工厂类MyBeanFactory编写
public class MyBeanFactory {
//cglib实现代理
public static StudentService createStudentService(){
//1.创建目标对象target(目标类)
final StudentService studentService = new StudentService();
//2.声明切面类对象(切面类)
final MyAspect myAspect = new MyAspect();
//3.创建增强对象(cglib核心类)
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(studentService.getClass());
//设置回调【就是设置拦截】
enhancer.setCallback(new MethodInterceptor() {
@Override //代理对象
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//开启事务(增强代码)
myAspect.before();
//放行方法
//Object retObj = method.invoke(studentService, args);
//解耦写法(proxy表示是运行时创建StudentService的子类,从而对目标类进行增强)
Object retObj = methodProxy.invokeSuper(proxy,args);
//System.out.println("拦截...");
//提交事务(增强代码)
myAspect.after();
return retObj;
}
});
//创建代理对象(强转成我们需要的StudentService对象)
StudentService serviceProxy = (StudentService) enhancer.create();
return serviceProxy;
}
}
- 测试与效果(只要调用目标类StudentService的方法,就会被拦截加强)
- 使用增强cglib代理
1.5 AOP联盟通知类型
- AOP联盟为通知Advice定义了org.aopalliance.aop.Advice
- Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
-
前置通知 org.springframework.aop.MethodBeforeAdvice
在目标方法执行前实施增强 -
后置通知 org.springframework.aop.AfterReturningAdvice
在目标方法执行后实施增强 -
环绕通知org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后实施增强 -
异常抛出通知 org.springframework.aop.ThrowsAdvice
在方法抛出异常后实施增强 -
引介通知 org.springframework.aop.IntroductionInterceptor
在目标类中添加一些新的方法和属性
-
- 环绕通知,必须手动执行目标方法
try{
//前置通知
//执行目标方法
//后置通知
} catch(){
//抛出异常通知
}
1.6 Spring编写代理半自动
- 使用spring创建代理对象,从spring容器中手动的获取代理对象。
第一步:导入相关Jar包
- 【核心4+1日志、AOP联盟(规范)、spring-aop(实现)】
联盟包就是定义了一些接口,spring-aop是联盟包的实现类
第二步:目标类编写
- 目标类UserService接口编写同上面JDK动态代理
- 目标类UserService实现类编写同上面JDK动态代理
第三步:切面类编写
- 注意这里的MethodInterceptor不是cglib下的,而是aopalliance的
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
//切面类:增强代码与切入点的结合
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//拦截方法
System.out.println("开启事务...");
//放行
Object retObj = mi.proceed();
System.out.println("提交事务...");
return retObj;
}
}
第四步:spring配置
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--配置目标类UserService-->
<bean class="com.it.service.UserServiceImpl" id="userService"></bean>
<!--配置切面类对象-->
<bean class="com.it.service.MyAspect" id="myAspect"></bean>
<!--配置代理对象:使用工厂bean创建代理
底层机制:
默认情况下Spring的AOP生成的代理是JDK的Proxy实现的
如果没有接口,采用cglib字节码增强
如果声明optimize = true,无论是否有接口,都采用cglib
-->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="serviceProxy">
<!--接口
查看源码发现interfaces是一个list集合,这里只是一个接口就写value即可,如果是多个接口就写list-->
<property name="interfaces" value="com.it.service.UserService"></property>
<!--目标对象-->
<property name="target" ref="userService"></property>
<!--切面类(这里很特殊不能使用ref引用,要用value,否则报错)
interceptorNames:通知切面类,它是一个数组-->
<property name="interceptorNames" value="myAspect"></property>
<!--配置使用cglib代理-->
<!--<property name="optimize" value="true"></property>-->
</bean>
</beans>
第五步:测试与效果
-
只要调用目标类UserService的方法,就会被拦截加强
-
默认情况下Spring的AOP生成的代理是JDK的Proxy实现的
-
可以配置使用cglib代理
1.7 Spring AOP全自动编程
第一步:导入jar包
- spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE
第二步:目标类、切面类的编写同上面的半自动
第三步:Spring的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:p="http://www.springframework.org/schema/p"
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">
<!--配置UserService-->
<bean class="com.it.service.UserServiceImpl" id="userService"></bean>
<!--配置切面类-->
<bean class="com.it.aspect.MyAspect" id="myAspect"></bean>
<!--全自动AOP配置
1.在bean中配置aop约束
2.配置aop:config内容,把切入点和通知结合
proxy-target-class="true":表示使用cglib实现代理,不写就是默认的JDKProxy代理
AOP:常用于事务配置与日志记录
-->
<aop:config proxy-target-class="true">
<!--aop:pointcut:切入点,从目标对象获取具体方法
把每个service方法前后都开启事务和提交事务
expression表达式:* 表示任意的意思
切入点表达式:
execution( * com.it.service. * . * (..)):表示com.it.service包下的所有类、所有方法都是切入点
任意返回值 包名 任意类名 任意方法名 任意参数
-->
<aop:pointcut id="myPointcut" expression="execution(* com.it.service.*.*(..))"/>
<!--通知 关联 切入点-->
<!--特殊切入面
advice-ref:通知
pointcut-ref:切入点引用
-->
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"></aop:advisor>
</aop:config>
</beans>
第四步:测试与效果
- 写了proxy-target-class=“true”,表示使用cglib实现代理
- 不写就是默认的JDK实现Proxy