JDk动态代理 --不用导包,jdk提供好了
proxy
条件: 目标类必须得有接口
接口
package com.it.proxy;
/**
* 对生产厂家要求的接口
*/
public interface IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(int money);
/**
* 售后
* @param money
*/
public void afterService(int money);
}
代理类
package com.it.proxy;
/**
* 一个生产者
*/
public class Producer implements IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(int money) {
System.out.println("销售产品,并拿到钱"+money);
}
/**
* 售后
* @param money
*/
public void afterService(int money) {
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
JDK动态代理测试
package com.it.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模拟一个消费者
*
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
*/
public class TestProxy {
public static void main(String[] args) {
//代理对象
final Producer pro=new Producer();
//增强被代理对象
IProducer proProxy= (IProducer) Proxy.newProxyInstance(pro.getClass().getClassLoader(),
pro.getClass().getInterfaces(),
new InvocationHandler() {
//用于提供增强的代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
//提供增强的代码
Object returnValue=null;
//1.获取方法执行的参数
int money=(int)args[0];
//2.判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue= method.invoke(pro, money+1100);
}
return returnValue;
}
}
);
pro.saleProduct(200);
System.out.println("被代理后~~~~");
proProxy.saleProduct(200);
}
}
结果
CGLIB动态代理---第三方 单用它就必须导包
enhance
条件: 只要有一个目标类即可增强
导包
1 Spring-aop.jar(实现了AOP的一套规范)
2 spring-aspects.jar (spring整合aspectj)
---核心包
3 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(第三的--实现了AOP的一套规范)
4 com.springsource.org.aopalliance-1.0.0.jar AOP联盟 (AOP的一套规范(接口))
---依赖包
需要被增强的普通类,无需接口
package com.it.cglib;
/**
* 一个生产者
*/
public class Producer {
/**
* 销售
* @param money
*/
public void saleProduct(int money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(int money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
CGlib测试
package com.it.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
public class TestCglib {
public static void main(String[] args) {
final Producer pro=new Producer();
Producer proCglib = (Producer) Enhancer.create(pro.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue=null;
//1.获取方法执行的参数
int money=(int)args[0];
//2.判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue= method.invoke(pro, money+1100);
}
return returnValue;
}
});
pro.saleProduct(200);
System.out.println("被代理后~~~~");
proCglib.saleProduct(200);
}
}
AOP 面相切面编程,xml编写方式
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。
Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。
Target(目标对象): 代理的目标对象。
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面): 是切入点和通知(引介)的结合
项目结构:
1.导包
1 Spring-aop.jar(实现了AOP的一套规范)
2 spring-aspects.jar (spring整合aspectj)
---核心包
3 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(第三的--实现了AOP的一套规范)
4 com.springsource.org.aopalliance-1.0.0.jar AOP联盟 (AOP的一套规范(接口))
---依赖包
2.引入 约束
编写接口
package com.it.service;
/**
* 账户的业务层实现类
*/
public interface AccountService {
//保存
public void saveAccount();
//删除
public int deleteAccount();
//修改
public void updateAccount(int i);
}
编写实现类(代理对象)
package com.it.serviceImpl;
import com.it.service.AccountService;
/**
* 业务实现类
*
*/
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
//模拟异常
//int i=1/0;
System.out.println("执行了保存");
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
}
模拟编写日志类(切面类)
package com.it.utlis;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
bean.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"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountservice" class="com.it.serviceImpl.AccountServiceImpl"></bean>
<!--spring中基于XML的AOP配置步骤
1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.it.serviceImpl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.it.serviceImpl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.it.serviceImpl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.it.serviceImpl.*.*(..)
-->
<!-- 配置Logger类 -->
<bean id="logger" class="com.it.utlis.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- aop:pointcut 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut expression="execution( void com.it.serviceImpl.*.*())" id="pt"/>
<!--配置切面 -->
<aop:aspect id="loggerAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行 比如开启事务
<aop:before method="beforePrintLog" pointcut-ref="pt"/>
-->
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 比如提交事务
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt"/>
-->
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 比如回滚事务
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt"/>
-->
<!-- 配置最终通知:在切入点方法执行之前执行 比如释放资源
<aop:after method="afterPrintLog" pointcut-ref="pt"/>
-->
<!-- 配置环绕通知 详细的注释请看Logger类中-->
<aop:around method="aroundPringLog" pointcut-ref="pt"></aop:around>
</aop:aspect>
</aop:config>
</beans>
正常运行的结构
有异常的结果
AOP: 面相切面编程思想
大白话: 将一些共性的内容进行抽取,在需要用到的地方,以动态代理的方式进行插入
在不修改源码的基础上,还能对源码进行前后的增强
底层的技术: 动态代理
spring就是把动态代理进行层层封装 诞生出了aop思想
AOP的应用:
权限拦截
日志的输出
性能的检测
事务管理
.....
aop思想的底层技术: 动态代理
基于注解的 AOP 配置
1.导包:
拷贝上面的即可
2.引入约束
<?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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
编写bean.xml
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.it"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
模拟编写日志类
在通知类上使用@Aspect 注解声明为切面
在增强的方法上使用注解配置通知
package com.it.utlis;
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;
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
//<aop:pointcut expression="execution( void com.it.serviceImpl.*.*())" id="pt"/>
@Pointcut(value="execution( void com.it.serviceImpl.*.*())")
private void pt() {};
/**
* 前置通知
*/
@Before("pt()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
@AfterReturning("pt()")
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
@AfterThrowing("pt()")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
@After("pt()")
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
// @Around("pt()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
接口
package com.it.service;
/**
* 账户的业务层实现类
*/
public interface AccountService {
//保存
public void saveAccount();
//删除
public int deleteAccount();
//修改
public void updateAccount(int i);
}
实现类
package com.it.serviceImpl;
import org.springframework.stereotype.Service;
import com.it.service.AccountService;
/**
* 业务实现类
*
*/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
// //模拟异常
// int i=1/0;
System.out.println("执行了保存");
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
}
测试类
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.it.service.AccountService;
public class AopTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//使用JDK动态代理的方法,使用接口来实现代理,需要使用被代理者来增强
//使用如果使用CGlib动态代理的方法,只需要使用代理者来增强
//AccountServiceImpl accountService= (AccountServiceImpl) context.getBean("accountservice");
AccountService accountService= (AccountService) context.getBean("accountService");
accountService.saveAccount();
}
}
结果和上面的一样
环绕通知配置图:通知的类型
注意:
使用不使用环绕通知和使用环绕通知代码的结果不一样
不使用环绕通知:
使用环绕通知