一、动态代理原理
1.1 简介
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
总结JDK代理只能对接口进行代理,Cglib则是对实现类进行代理
如何强制使用CGLIB实现AOP?
- (1)添加CGLIB库,SPRING_HOME/cglib/*.jar
- (2)在spring配置文件中加入
JDK动态代理和CGLIB字节码生成的区别?
- (1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
- (2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
1.2 性能比较
1) 从 jdk6 到 jdk7、jdk8 ,动态代理的性能得到了显著的提升,而 cglib 的表现并未跟上,甚至可能会略微下降。
2) 传言的 cglib 比 jdk动态代理高出 10 倍的情况也许是出现在更低版本的 jdk 上吧。
3)在jdk7 下,情况发生了逆转!在运行次数较少(1,000,000)的情况下,jdk动态代理比 cglib 快了差不多30%;而当调用次数增加之后(50,000,000), 动态代理比 cglib 快了接近1倍。
二、代码实现
- 接口类
package cn.yxq.service;
public interface PersonService {
public void save(String name);
public void update(String name, Integer personid);
public String getPersonName(Integer personid);
}
- 实现类
package cn.yxq.service.impl;
import cn.yxq.service.PersonService;
public class PersonServiceBean implements PersonService{
private String user = null;
public String getUser() {
return user;
}
public PersonServiceBean(){}
public PersonServiceBean(String user){
this.user = user;
}
public String getPersonName(Integer personid) {
System.out.println("我是getPersonName()方法");
return "xxx";
}
public void save(String name) {
System.out.println("我是save()方法");
}
public void update(String name, Integer personid) {
System.out.println("我是update()方法");
}
}
- JDK动态代理类
/**
* CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法。
* @author yxq
*
*/
public class JDKProxyFactory implements InvocationHandler{
/**代理的目标对象*/
private Object targetObject;
public Object createProxyIntance(Object targetObject){
this.targetObject = targetObject;
/**
* 第一个参数设置代码使用的类装载器,一般采用跟目标类相同的类装载器
* 第二个参数设置代理类实现的接口
* 第三个参数设置回调对象,当代理对象的方法被调用时,会委派给该参数指定的对象的invoke方法
*/
return Proxy.newProxyInstance(
this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {//环绕通知
PersonServiceBean bean = (PersonServiceBean) this.targetObject;
// System.out.println("proxy:"+JSONObject.toJSONString(proxy));
// System.out.println("method:"+JSONObject.toJSONString(method));
// System.out.println("arg:");
// for(Object o:args){
// System.out.println(JSONObject.toJSONString(o));
// }
Object result = null;
if(bean.getUser()!=null){
// System.out.println("..... advice()-->前置通知");
//..... advice()-->前置通知
try {
/**把方法调委派给目标对象*/
result = method.invoke(targetObject, args);
// System.out.println("afteradvice() -->后置通知");
// afteradvice() -->后置通知
} catch (RuntimeException e) {
//exceptionadvice()--> 例外通知
// System.out.println("exceptionadvice()--> 例外通知");
}finally{
// System.out.println("finallyadvice(); -->最终通知");
//finallyadvice(); -->最终通知
}
}
return result;
}
}
- 测试类
package junit.test;
import org.junit.BeforeClass;
import org.junit.Test;
import cn.yxq.aop.CGlibProxyFactory;
import cn.yxq.aop.JDKProxyFactory;
import cn.yxq.service.PersonService;
import cn.yxq.service.impl.PersonServiceBean;
public class AOPTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@Test public void TestJDKProxy(){
System.out.println("JDKProxyFactory--begin");
JDKProxyFactory factory = new JDKProxyFactory();
PersonService service = (PersonService) factory.createProxyIntance(new PersonServiceBean("xxx"));
service.save("888");
}
@Test public void TestCGlibProxy(){
System.out.println("CGlibProxyFactory--begin");
CGlibProxyFactory factory = new CGlibProxyFactory();
PersonServiceBean service = (PersonServiceBean) factory.createProxyIntance(new PersonServiceBean("xxx"));
service.save("999");
}
}
- 运行结果:
JDKProxyFactory--begin
我是save()方法
CGlibProxyFactory--begin
我是save()方法
JDK代理是不需要以来第三方的库,只要要JDK环境就可以进行代理,它有几个要求
实现InvocationHandler
使用Proxy.newProxyInstance产生代理对象
被代理的对象必须要实现接口CGLib 必须依赖于CGLib的类库
但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理
在Hibernate中的拦截器其实现考虑到不需要其他接口的条件Hibernate中的相关代理采用的是CGLib来执行。
三、AOP
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)
Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Target(目标对象):代理的目标对象
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入.
Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
3.1 使用Spring进行面向切面(AOP)编程
要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间:
<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-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
</beans>
Spring提供了两种切面声明方式,实际工作中我们可以选用其中一种:
- 基于XML配置方式声明切面。
- 基于注解方式声明切面。
3.2 基于注解方式声明切面
@Aspect
public class LogPrint {
@Pointcut("execution(* cn.itcast.service..*.*(..))")
private void anyMethod() {}//声明一个切入点
@Before("anyMethod() && args(userName)")//定义前置通知
public void doAccessCheck(String userName) {
}
@AfterReturning(pointcut="anyMethod()",returning="revalue")//定义后置通知
public void doReturnCheck(String revalue) {
}
@AfterThrowing(pointcut="anyMethod()", throwing="ex")//定义例外通知
public void doExceptionAction(Exception ex) {
}
@After("anyMethod()")//定义最终通知
public void doReleaseAction() {
}
@Around("anyMethod()")//环绕通知
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
}
- 接口
package cn.yxq.service;
public interface PersonService {
public void save(String name);
public void update(String name, Integer id);
public String getPersonName(Integer id);
}
- 实现类
package cn.yxq.service.impl;
import cn.yxq.service.PersonService;
public class PersonServiceBean implements PersonService {
public String getPersonName(Integer id) {
System.out.println("我是getPersonName()方法");
return "xxx";
}
public void save(String name) {
//throw new RuntimeException("我爱例外");
System.out.println("我是save()方法");
}
public void update(String name, Integer id) {
System.out.println("我是update()方法");
}
}
3.3 基于基于XML配置方式声明切面
- 切面
package cn.yxq.service;
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;
/**
* 切面
*
*/
@Aspect
public class MyInterceptor {
@Pointcut("execution (* cn.yxq.service.impl.PersonServiceBean.*(..))")
private void anyMethod() {
}// 声明一个切入点
@Before("anyMethod() && args(name)")
public void doAccessCheck(String name) {
System.out.println("前置通知:" + name);
}
@AfterReturning(pointcut = "anyMethod()", returning = "result")
public void doAfterReturning(String result) {
System.out.println("后置通知:" + result);
}
@After("anyMethod()")
public void doAfter() {
System.out.println("最终通知");
}
@AfterThrowing(pointcut = "anyMethod()", throwing = "e")
public void doAfterThrowing(Exception e) {
System.out.println("例外通知:" + e);
}
@Around("anyMethod()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// if(){//判断用户是否在权限
System.out.println("进入方法");
Object result = pjp.proceed();
System.out.println("退出方法");
// }
return result;
}
}
- XML配置方式声明切面
<aop:aspectj-autoproxy/>
<bean id="personService" class="cn.yxq.service.impl.PersonServiceBean"></bean>
<bean id="aspetbean" class="cn.yxq.service.MyInterceptor"/>
<aop:config>
<aop:aspect id="asp" ref="aspetbean">
<aop:pointcut id="mycut" expression="execution(* cn.yxq.service..*.*(..))"/>
<aop:before pointcut-ref="mycut" method="doAccessCheck"/>
<aop:after-returning pointcut-ref="mycut" method="doAfterReturning"/>
<aop:after-throwing pointcut-ref="mycut" method="doAfterThrowing"/>
<aop:after pointcut-ref="mycut" method="doAfter"/>
<aop:around pointcut-ref="mycut" method="doBasicProfiling"/>
</aop:aspect>
</aop:config>
3.4 Springboot-Aop
- 网关web层,统一参数校验aop
@Aspect
@Component
public class ReqValidAop {
private static final Logger logger = LoggerFactory.getLogger(ReqValidAop.class);
@Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.device.*.*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
public void deviceController() {
}
@Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.user..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
public void userController() {
}
@Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.house..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
public void houseController() {
}
@Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.flow..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
public void flowController() {
}
@Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.parking..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
public void parkingController() {
}
@Pointcut(value = "execution(public * com.doordu.soa.service.admin.controller.third..*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
public void thirdController() {
}
@Around(value = "deviceController() || userController()|| houseController() || flowController() || parkingController() || thirdController()")
public Object doValid(ProceedingJoinPoint joinPoint) throws Throwable {
for (Object args : joinPoint.getArgs()) {
String errMsg = DataValidService.checkData(args);
if (StringUtils.isNotBlank(errMsg)) {
return ((BaseController) joinPoint.getThis()).checkParamError(errMsg);
}
if (args instanceof PageParam) {
PageParam pageParam = (PageParam) args;
if (Integer.valueOf(pageParam.getPage()) <= 0 || Integer.valueOf(pageParam.getLimit()) <= 0) {
return ((BaseController) joinPoint.getThis()).checkParamError("分页参数校验失败,正确格式为正整数");
}
}
}
return joinPoint.proceed(joinPoint.getArgs());
}
}