@Service
public class RoleServiceImpl implements RoleService {
public Role get(Integer id) {
System.out.println("查询Role");
return new Role();
}
public void add(Role role) {
System.out.println("添加Role");
}
public void delete(Integer id) {
// 日志
System.out.println("删除Role");
}
public void update(Role role) {
System.out.println("修改Role");
}
}
此代码非常简单,就是基础的三层CRUD的代码实现,此时如果需要添加日志功能应该怎么做呢,只能在每个方法中添加日志输出,同时如果需要修改的话会变得非常麻烦。
按照上述方式抽象之后,代码确实简单很多,但是大家应该已经发现在输出的信息中并不包含具体的方法名称,我们更多的是想要在程序运行过程中动态的获取方法的名称及参数、结果等相关信息,此时可以通过使用代理的方式来进行实现。
弊端:需要为每一个被代理的类创建一个“代理类”,虽然这种方式可以实现,但是成本太高
jdk动态代理 :必须保证被代理的类实现了接口,
cglib动态代理 :不需要接口
package com.example.inter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 帮助Calculator生成代理对象的类
*/
public class CalculatorProxy {
/**
*
* 为传入的参数对象创建一个动态代理对象
* @param calculator 被代理对象
* @return
*/
public static Calculator getProxy(final Calculator calculator){
//被代理对象的类加载器
ClassLoader loader = calculator.getClass().getClassLoader();
//被代理对象的接口
Class>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,执行被代理对象的目标方法
InvocationHandler h = new InvocationHandler() {
/**
* 执行目标方法
* @param proxy 代理对象,给jdk使用,任何时候都不要操作此对象
* @param method 当前将要执行的目标对象的方法
* @param args 这个方法调用时外界传入的参数值
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//利用反射执行目标方法,目标方法执行后的返回值
// System.out.println("这是动态代理执行的方法");
Object result = null;
try {
System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(args));
result = method.invoke(calculator, args);
System.out.println(method.getName()+"方法执行完成,结果是:"+ result);
} catch (Exception e) {
System.out.println(method.getName()+"方法出现异常:"+ e.getMessage());
} finally {
System.out.println(method.getName()+"方法执行结束了......");
}
//将结果返回回去
return result;
}
};
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
return (Calculator) proxy;
}
}·
我们可以看到这种方式更加灵活,而且不需要在业务方法中添加额外的代码,这才是常用的方式。如果想追求完美的,还可以使用下述的日志工具类来完善。
package com.example.util;
import java.lang.reflect.Method;
import java.util.Arrays;
public class LogUtil {
public static void start(Method method, Object ... objects){
System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));
}
public static void stop(Method method,Object ... objects){
System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));
}
public static void logException(Method method,Exception e){
System.out.println(method.getName()+"方法出现异常:"+ e.getMessage());
}
public static void end(Method method){
System.out.println(method.getName()+"方法执行结束了......");
}
}
package com.example.inter;
import com.example.util.LogUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 帮助Calculator生成代理对象的类
*/
public class CalculatorProxy {
/**
*
* 为传入的参数对象创建一个动态代理对象
* @param calculator 被代理对象
* @return
*/
public static Calculator getProxy(final Calculator calculator){
//被代理对象的类加载器
ClassLoader loader = calculator.getClass().getClassLoader();
//被代理对象的接口
Class>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,执行被代理对象的目标方法
InvocationHandler h = new InvocationHandler() {
/**
* 执行目标方法
* @param proxy 代理对象,给jdk使用,任何时候都不要操作此对象
* @param method 当前将要执行的目标对象的方法
* @param args 这个方法调用时外界传入的参数值
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//利用反射执行目标方法,目标方法执行后的返回值
// System.out.println("这是动态代理执行的方法");
Object result = null;
try {
LogUtil.start(method,args);
result = method.invoke(calculator, args);
LogUtil.stop(method,args);
} catch (Exception e) {
LogUtil.logException(method,e);
} finally {
LogUtil.end(method);
}
//将结果返回回去
return result;
}
};
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
return (Calculator) proxy;
}
}
看到上述代码之后可能感觉已经非常完美了,但是要说明的是,这种动态代理的实现方式调用的是jdk的基本实现,如果需要代理的目标对象没有实现任何接口,那么是无法为他创建代理对象的,这也是致命的缺陷。而在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。
通过动态代理的方式实现日志功能的,但是比较麻烦,现在我们将要使用spring aop的功能实现此需求,其实通俗点说的话,就是把LogUtil的工具类换成另外一种实现方式。
org.aspectj
aspectjweaver
1.9.5
org.springframework
spring-aspects
5.2.3.RELEASE
将目标类和切面类加入到IOC容器中,在对应的类上添加组件注解
给LogUtil添加@Component注解
给MyCalculator添加@Service注解
添加自动扫描的配置
设置程序中的切面类
在LogUtil.java中添加@Aspect注解
设置切面类中的方法是什么时候在哪里执行
在增强模块的类上面标记
声明为切面
将切面交给spring去管理
@Aspect
@Component
package com.example.util;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogUtil {
/*
设置下面方法在什么时候运行
@Before:在目标方法之前运行:前置通知
@After:在目标方法之后运行:后置通知
@AfterReturning:在目标方法正常返回之后:返回通知
@AfterThrowing:在目标方法抛出异常后开始运行:异常通知
@Around:环绕:环绕通知
当编写完注解之后还需要设置在哪些方法上执行,使用表达式
execution(访问修饰符 返回值类型 方法全称)
*/
// 前置通知
@Before("execution(* com.example.service..*.*(..))")
public static void before(){
/* System.out.println(method.getName()+"方法运行前,参数是"+
(args==null?"": Arrays.asList(args).toString()));*/
System.out.println("方法前");
}
// 后置通知
@After("execution(* com.example.service..*.*(..))")
public static void after(){
/* System.out.println(method.getName() +"方法运行后,参数是"+
(args==null?"": Arrays.asList(args).toString()));*/
System.out.println("方法后");
}
// 后置异常通知
@AfterThrowing("execution(* com.example.service..*.*(..))")
public static void afterException(){
// System.out.println("方法报错了:"+ex.getMessage());
System.out.println("方法异常");
}
// 后置返回通知
@AfterReturning("execution(* com.example..*.*(..))")
public static void afterEnd(){
//System.out.println("方法结束,返回值是:"+returnValue);
System.out.println("方法返回");
}
}
开启基于注解的aop的功能
import com.example.inter.Calculator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
Calculator bean = context.getBean(Calculator.class);
bean.add(1,1);
}
}
spring AOP的动态代理方式是jdk自带的方式,容器中保存的组件是代理对象com.sun.proxy.$Proxy对象
package com.example.inter;
import org.springframework.stereotype.Service;
@Service
public class MyCalculator {
public int add(int i, int j) {
int result = i + j;
return result;
}
public int sub(int i, int j) {
int result = i - j;
return result;
}
public int mult(int i, int j) {
int result = i * j;
return result;
}
public int div(int i, int j) {
int result = i / j;
return result;
}
}
public class MyTest {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
MyCalculator bean = context.getBean(MyCalculator.class);
bean.add(1,1);
System.out.println(bean);
System.out.println(bean.getClass());
}
}
可以通过cglib的方式来创建代理对象,此时不需要实现任何接口,代理对象是
class com.example.inter.MyCalculator$$EnhancerBySpringCGLIB$$1f93b605类型
综上所述:在spring容器中,如果有接口,那么会使用jdk自带的动态代理,如果没有接口,那么会使用cglib的动态代理。