1.Cglib动态代理
cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理!
什么是cglib?
CGLIB是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
//没有接口的类
package com.feng.cglib;
public class ProductService {
public void save() {
System.out.println("ProductService--save");
}
public int find() {
System.out.println("ProductService--find");
return 99;
}
}
//cglib动态代理工厂:用来生成cglib代理对象
package com.feng.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
//MethodInterceptor方法拦截器
public class CglibProxyFactory implements MethodInterceptor{
//声明一个代理对象引用
private Object target;
//注入代理对象
CglibProxyFactory (Object target) {
this.target=target;
}
//获取代理对象
public Object getProxyObject() {
//1.代理对象生成器(工厂思想)
Enhancer enhancer=new Enhancer();
//类加载器
enhancer.setClassLoader(target.getClass().getClassLoader());
//2.在增强器上设置两个属性
//设置要生成代理对象的目标对象:生成的目标对象类型的子类型
enhancer.setSuperclass(target.getClass());
//设置回调方法
enhancer.setCallback(this);
//3.创建获取对象
return enhancer.create();
}
//回调方法(代理对象的方法)
//参数1:代理对象
//参数2:目标对象的方法对象
//参数3:目标对象的方法的参数的值
//参数4:代理对象的方法对象
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// TODO Auto-generated method stub
//如果是保存的方法,执行记录日志操作
if(method.getName().equals("save")) {
writeLog();
}
//目标对象原来的方法执行
Object object=method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象
return object;
}
//写日志的增强功能
//Advice通知、增强
//记录日志
protected static void writeLog() {
// TODO Auto-generated method stub
System.out.println("增强代码:写日志了。。。");
}
}
//测试类
//cdlib动态代理:可以基于类(无需实现接口)生成代理对象
package com.feng.cglib;
import org.junit.Test;
public class SpringTest {
@Test
public void testCglibProxy() {
//target目标
ProductService target=new ProductService();
//weave织入,生成proxy代理对象
//代理工厂对象,注入目标
CglibProxyFactory cglibProxyFactory=new CglibProxyFactory(target);
//获取proxy:思考:对象的类型
//代理对象,其实是目标对象类型的子类型
ProductService proxy=(ProductService) cglibProxyFactory.getProxyObject();
//调用代理对象的方法
proxy.save();
proxy.find();
}
}
2.代理知识小结
区别:
jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口类型的子对象。
Cglib代理:基于类的代理,不需要基于接口,会生成目标对象类型的子对象。
- spring在运行期,生成动态代理对象,不需要特殊的编译器;
- spring有两种方式:
- 若目标对象实现了若干接口,spring使用jdk的java.lang.reflect.Proxy类代理。(默认)
- 若目标对象没有实现任何接口,spring使用Cglib库生成目标对象的子类。
- 使用该方法时需注意:
- 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,所以spring默认使用jdk代理。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
- 标记final的方法不能够被通知,spring是为目标产生子类,任何需要被通知的方法都被复写,将通知织入,final方法是不允许重写的。
- spring只支持方法连接点,不提供属性接入点,spirng的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到结果。
3.AspectJ切面编程(xml方式)
开发方法:
- 确定目标对象(bean)
- 编写通知,对目标对象增强(advice)
- 配置切入点(pointcut)、切面(aspect)
AspectJ提供advice类型
普通的pojo即可。(不需要实现接口)
AspectJ提供不同的通知类型:
- Before前置通知,相当于BeforeAdvice
- AfterReturning后置通知,相当于AfterReturningAdvice
- Around环绕通知,相当于MethodInterceor
- AfterThrowing抛出通知,相当于ThrowAdvice
- After最终final通知,不管是否异常,该通知都会执行
- DeclareParent引介通知,相当于IntroductionInterceptor
相比传统SpringAOP通知类型多了After最终通知(类似finally)
实现步骤:
- 确定目标对象,即确定bean对象
- advice通知(编写)
- 配置切面(包括切入点),让切入点关联通知
package com.feng.advice;
//表示代理的目标接口
public interface ICustomerService {
//保存
public void save();
//查询
public Integer find();
}
package com.feng.advice;
//实现类
public class CustomerServiceImpl implements ICustomerService{
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("CustomerServiceImpl--save");
}
@Override
public Integer find() {
System.out.println("CustomerServiceImpl--find");
return 99;
// TODO Auto-generated method stub
}
}
//没有接口的类
package com.feng.advice;
public class ProductService {
public void save() {
System.out.println("ProductService--save");
}
public Integer find() {
System.out.println("ProductService--find");
return 99;
}
}
//aspect的advice通知增强类,无需实现任何接口
package com.feng.advice;
public class MyAspect {
//普通方法
//前置通知
public void firstbefore() {
System.out.println("第一个前置通知执行了。。。");
}
//后置通知
public void firstafter() {
System.out.println("第一个后置通知执行了。。。");
}
//final通知
public void firstround() {
System.out.println("第一个final通知执行了。。。");
}
}
//测试类
package com.feng.advice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//Springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test() {
//基于接口
customerService.save();
customerService.find();
//基于类
productService.save();
productService.find();
}
}
//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"
xmlns:aop="http://www.springframework.org/schema/aop"
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-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService"
class="com.feng.advice.CustomerServiceImpl"></bean>
<bean id="productService"
class="com.feng.advice.ProductService"></bean>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="com.feng.advice.MyAspect"></bean>
<!-- 3.配置aop -->
<aop:config>
<!-- 切入点:拦截哪些bean的方法 这里是拦截后缀名为Service的方法 -->
<aop:pointcut expression="bean(*Service)" id="myPointcut" />
<!-- 切面:要对哪些方法进行怎样的增强 aop:aspect:aspectctj的方式 ref:配置通知 -->
<aop:aspect ref="myAspectAdvice">
<!-- 第一个前置通知:在访问目标对象方法之前,先执行通知的方法 method:advice类中的方法名, pointcut-ref="myPointcut":注入切入点
目的是让通知关联切入点 -->
<aop:before method="firstbefore" pointcut-ref="myPointcut" />
<aop:after-returning method="firstafter" pointcut-ref="myPointcut"/>
<aop:after method="firstround" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
【扩展优化】
1.将切入点放入aspectj标签里写,同时配置多个通知方法
//将切入点放入aspectj标签里写,同时配置多个通知方法
<!-- 3.配置aop -->
<aop:config>
<!-- 切面:要对哪些方法进行怎样的增强 aop:aspect:aspectctj的方式 ref:配置通知 -->
<aop:aspect ref="myAspectAdvice">
<!-- 切入点:拦截哪些bean的方法 这里是拦截后缀名为Service的方法 -->
<aop:pointcut expression="bean(*Service)"
id="myPointcut" />
<!-- 第一个前置通知:在访问目标对象方法之前,先执行通知的方法 method:advice类中的方法名, pointcut-ref="myPointcut":注入切入点
目的是让通知关联切入点 -->
<aop:before method="firstbefore" pointcut-ref="myPointcut" />
<aop:before method="firstbefore1" pointcut-ref="myPointcut" />
<aop:after-returning method="firstafter"
pointcut-ref="myPointcut" />
<aop:after method="firstround" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
2.配置多个通知方法
public void firstbefore() {
System.out.println("第一个前置通知执行了111。。。");
}
public void firstbefore1() {
System.out.println("第二个前置通知执行了222。。。");
}
4.分析各种通知应用
before前置通知
//没有接口的类
package com.feng.advice;
public class ProductService {
public void save() {
System.out.println("ProductService--save");
}
public Integer find() {
System.out.println("ProductService--find");
return 99;
}
}
package com.feng.advice;
//表示代理的目标接口
public interface ICustomerService {
//保存
public void save();
//查询
public Integer find();
}
package com.feng.advice;
//实现类
public class CustomerServiceImpl implements ICustomerService{
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("CustomerServiceImpl--save");
//异常通知:故意制作异常
//int d=1/0;
}
@Override
public Integer find() {
System.out.println("CustomerServiceImpl--find");
return 88;
// TODO Auto-generated method stub
}
}
//aspect的advice通知增强类,无需实现任何接口
package com.feng.advice;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import javax.management.RuntimeErrorException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
//前置通知:运行方法之前增强
//应用:权限控制(权限不足,抛出异常)、记录方法调用信息日志
//参数:org.aspect.lang.JoinPoint
//参数:连接点对象(方法的包装对象:方法,参数,目标对象)
public void before(JoinPoint joinpoint) {
//分析:抛出异常拦截的
//当前用户登录
//测试前置通知时,loginname=“chen”,测试其他通知时,改为“admin”
String loginname="chen";
System.out.println("方法名称:"+joinpoint.getSignature().getName());
System.out.println("目标对象:"+joinpoint.getTarget().getClass().getName());
System.out.println("代理对象:"+joinpoint.getThis().getClass().getName());
//判断当前用户有没有执行方法权限
if(joinpoint.getSignature().getName().equals("find")) {
if(!loginname.equals("admin")) {
//只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
throw new RuntimeException("您没有权限执行方法:"+joinpoint.getSignature().getName()+",类型为"+joinpoint.getTarget().getClass().getName());
}
}
}
//应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信
//后置通知:会在目标方法执行之后调用通知方法增强
//参数1:连接点对象(方法的包装对象:方法,参数,目标对象)
//参数2:目标方法执行后的返回值,类型是object
public void afterReturning(JoinPoint joinPoint,Object returnVal) {
//下发短信:调用运行商的接口
System.out.println("后置通知-当前下发短信的方法---尊敬的用户,您调用的方法返回余额为:"+returnVal);
//同时可以在下发后,记录日志
System.out.println("日志记录--操作类型:"+joinPoint.getTarget().getClass().getSimpleName()+",操作方法:"+joinPoint.getSignature().getName());
}
//应用场景:日志、缓存、权限、性能监控、事物管理
//环绕通知:在目标对象方法执行前后,可以增强
//参数:可以执行的连接点对象ProcessdingJoinPoint(方法),特点是调用processd()方法可以随时随地执行目标对象的方法(相当于目标对象的方法执行了)
//必须抛出一个Throwable
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//目标:事物的控制
//开启事物
System.out.println("----开启事物----");
//执行了目标对象的方法
Object resultObject=proceedingJoinPoint.proceed();
//结束事物
System.out.println("---提交了事物---");
return resultObject;//目标对象执行的结果
}
//作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
//只有目标对象方法出现异常,通知才会执行
//参数1:静态连接点(方法对象)
//参数2:目标方法抛出的异常
public void afterThrowing(JoinPoint joinPoint,Throwable ex) {
//一旦发生异常,发送邮件或者短信给管理员
System.out.println("管理员您好!,"+joinPoint.getTarget().getClass().getName()+"的方法:"+joinPoint.getSignature().getName()+"发生了异常,异常为:"+ex.getMessage());
}
//应用场景:释放资源(关闭文件、关闭数据库连接、网络连接、释放内存对象)
//最终通知:不管是否有异常都会执行
public void after(JoinPoint joinPoint) {
//释放数据库的连接
System.out.println("数据库的connection被释放了。。。执行的方式是:"+joinPoint.getSignature().getName());
}
}
//测试类
package com.feng.advice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//Springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test() {
//基于接口
customerService.save();
customerService.find();
//基于类
productService.save();
productService.find();
}
}
//applicationCOntex.xml
<bean id="customerService"
class="com.feng.advice.CustomerServiceImpl"></bean>
<bean id="productService" class="com.feng.advice.ProductService"></bean>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="com.feng.advice.MyAspect"></bean>
<!-- 3.配置aop -->
<aop:config>
<!-- 切面:要对哪些方法进行怎样的增强 aop:aspect:aspectctj的方式 ref:配置通知 -->
<aop:aspect ref="myAspectAdvice">
<!-- 切入点:拦截哪些bean的方法 这里是拦截后缀名为Service的方法 -->
<aop:pointcut expression="bean(*Service)"
id="myPointcut" />
<!-- 前置通知:在访问目标对象方法之前,先执行通知的方法 method:advice类中的方法名, pointcut-ref="myPointcut":注入切入点
目的是让通知关联切入点 -->
<aop:before method="before" pointcut-ref="myPointcut" />
<!-- 后置通知:returning:配置方法中的参数名字,与通知方法的第二个参数的名字必须对应。 在运行的时候,spring会自动将返回值传人该参数中 -->
<aop:after-returning method="afterReturning"
returning="returnVal" pointcut-ref="myPointcut" />
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="myPointcut" />
<!-- 抛出通知throwing:通知中的方法的第二个参数,异常类型的参数的名字,在运行的时候,spring会自动将异常传入参数中 -->
<aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="myPointcut" />
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
测试前置通知
测试后置通知、环绕通知、最终通知
异常通知:
五种通知小结
只要掌握around(环绕通知)通知类型,就可以实现其他四种通知效果
因为你可以在环绕通知的方法中编写如下代码:
try {
//前置通知
Object result=ProceedingJoinPoint.proceed();
//后置通知
} catch (Exception e) {
//抛出通知
}finally {
//最终通知
}