目录
动态代理
所谓动态代理就是使用一个代理将原本对象包装起来,通过动态代理对象来完成实际对象要完成的功能。使用动态代理也是要实现实际对象要实现的功能,只不过可以在实现基本功能的基础上可以实现一些其他功能。Spring中的AOP代理,可以是JDK动态代理,也可以是CHLIB动态代理。
JDK动态代理
JDK动态代理是通过java.long.reflect.Proxy类来实现的,使用JDK动态代理要求被代理类必须要实现接口,下面我们看一下使用步骤。
1.创建User接口,有addUser、deleteUser连个方法
public interface User {
public void addUser(int i);
public void deletdUser(int i);
}
2.创建User的实现类UserImpl,实现两个方法,简单的进行输出模拟。
public class UserImpl implements User {
@Override
public void addUser(int i) {
System.out.println("添加用户");
}
@Override
public void deletdUser(int i) {
System.out.println("删除用户");
}
}
3.创建代理类MyProxy,创建createProxy方法用来获取代理类。使用Proxy的newProxyInstance方法来创建代理类对象,需要三个参数,分别是类加载器、被代理类对象所实现的接口、InvocationHandler类实例。
public class MyProxy{
private Object obj;
public Object createProxy(Object obj) {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), obj.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("检查权限");
Object result = method.invoke(obj, args);
System.out.println("记录日志,方法名:"+method.getName()+",参数:"+Arrays.toString(args));
return result;
}
});
}
}
4.创建Test类进行测试,这里注意获取的代理类对象要强转成接口类的,不能墙砖成实现类的对象。
public class Test {
public static void main(String[] args) {
MyProxy myProxy = new MyProxy();
User user = (User)myProxy.createProxy(new UserImpl());
user.addUser(10);
user.deletdUser(20);
}
}
执行结果:
CGLIB动态代理
cglib实现代理时被代理类可以没有接口,但是要有父类。首先创建一个动态对象Enhancer,这事cglib的核心,然后调用setSuperclass来确定目标对象;然后调用setCallback来添加回调函数,最后将返回值返回。
public class CGLIBProxy implements MethodInterceptor{
public Object createProxy(Object object) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("前置处理");
Object resule = arg3.invoke(arg0, arg2);
System.out.println("后置处理");
return resule;
}
}
AOP术语
横切关注点
从方法中抽取出来的某些功能、某些非核心业务,像第一个例子的添加权限和记录日志。
切面
封装横切关注点的类,一个关注点为切面中的一个方法
通知
切面要执行的方法,可以理解为切面类中的方法
目标
被通知对象,可以简单的理解为被代理的对象
代理
向目标对象应用通知之后创建的代理对象,可以简单的理解为代理对象
连接点
就是spring允许你使用通知的地方,一般在调用前、调用后、try中、catch中、fianlly中
切入点
切面与程序流程的交叉点,经过某些规则筛选过后的连接点。
织入
将切面代码插入到目标对象上,生成代理对象的过程。
通知类型
基于注解的小demo
1.创建xml文件,指定扫描的包和自动创建代理
<context:component-scan base-package="com.glq.spring.aop.annoction"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.创建与第一个例子相同的User和UserImpl类,并且在UserImpl类上加上@Compoent注解,标明是Spring的组件
3.创建代理类MyLogger,使用注解@Component来标识是Spring类的组件,然后使用@Aspect注解来表明是一个切面
4.在类中定义一个before方法,使用注解@Before来表明是一个前置通知,然后在方法中简单的输出一下“前置”两个字。@Before注解中可以定义value的内容,这里是指定作用的包,可以使用通配符。
@Component
@Aspect
public class MyLogger {
@Before(value = "execution(* com.glq.spring.aop.annoction.*.*(..))")
public void before() {
System.out.println("前置");
}
}
5.创建Test类进行测试,这里要说明的是获取bean的时候输入类型要是接口类的类型
ApplicationContext ac = new ClassPathXmlApplicationContext("annoction.xml");
User bean = ac.getBean("userImpl",User.class);
bean.addUser(10);
下面的几种通知方法在此demo的基础上进行学习。
前置通知
@Before注解标明该方法是一个前置通知,作用于执行语句的最前面。在方法中可以加入JoinPoint joinPoint参数,通过该参数可以获取方法名、参数列表等信息。
@Before(value = "execution(* com.glq.spring.aop.annoction.*.*(..))")
public void before(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println("before-参数:"+Arrays.toString(args)+",方法名:"+name);
}
执行结果:
后置通知
后置通知的位置想当于在try的最后一行,如果前面出了异常该后置是不会执行的。
在addUser方法中添加一个int类型的返回值,默认返回10,便于我们测试使用
@AfterReturnint注解用来标明是一个后置通知,注解中可以添加returning参数来标明返回值,然后在方法中也加入一个标识返回值的变量,这里要注意的是形参名称和注解中写的要一样
@AfterReturning(value = "execution(* com.glq.spring.aop.annoction.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("afterReturning-方法名:"+name+",返回值:"+result);
}
异常通知
异常通知的位置相当于在catch中,只有出异常的时候才会执行。
可通过throwing设置接收方法返回的异常信息,可以在参数列表中课通过具体的异常类型,来对指定的异常信息进行操作
@AfterThrowing(value = "execution(* com.glq.spring.aop.annoction.*.*(..))",throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("有异常,异常类型为:"+e);
}
最终通知
相当于在catch的位置,有没有异常都会执行
@After(value = "execution(* com.glq.spring.aop.annoction.*.*(..))")
public void after() {
System.out.println("最终通知");
}
环绕通知
环绕通知包含了前面几个通知,一般不使用,因为使用环绕通知和直接写代理模式差不多。
@Around(value = "execution(* com.glq.spring.aop.annoction.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint){
try {
System.out.println("around的前置通知");
Object obj = joinPoint.proceed();//执行方法
System.out.println("around的最终通知");
return obj;
} catch (Exception e) {
System.out.println("around的异常通知");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
System.out.println("ardurn的后置通知");
}
return -1;
}
定义公共的切入点表达式
每个方法上面都需要写长长的 execution(* com.glq.spring.aop.annoction..(…)) ,比较麻烦,我们可以定义一个公共的切入点表达式,然后在其他的方法上直接用这个就可以了。
1.定义一个公共的,需要随便创建一个方法,然后使用@Pointcut注解来定义
@Pointcut(value = "execution(* com.glq.spring.aop.annoction.*.*(..))")
public void test() {
}
2.在其他的通知注解里面直接使用test(),运行结果不变
@Before(value = "test()")
public void before(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println("before-参数:"+Arrays.toString(args)+",方法名:"+name);
}
切面的优先级
如果有多个切面,可以通过@Order注解来定义其优先级,只需要在Order注解中写入数字即可,数字越小,优先级越高。注意:@Order是写在类上面的。
xml方式配置切面
xml与注解方式基本相似,这里只用前置通知做例子
1.首先将类上加上相应的组件注解。
2…然后创建xml文件,扫描所有的组件
3.创建一个切面类,写好相应的方法
4.在xm文件中进行配置,也可以先定义好一个切入点,然后直接引用就可以。
<aop:config>
<aop:aspect ref="myLogger">
<aop:pointcut expression="execution(* com.glq.spring.aop.xml.*.*(..))" id="cut"/>
<aop:before method="before" pointcut-ref="cut"/>
<!-- <aop:before method="before" pointcut="execution(* com.glq.spring.aop.xml.*.*(..))"/> -->
</aop:aspect>
</aop:config>
5.创建测试类进行测试。这里要说明的是如果被代理类没有接口并且导入了CGLIB的包的话会自动从JDK代理切换到CGLIB代理。
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("xml.xml");
User bean = ac.getBean("userImpl",User.class);
int addUser = bean.addUser(10);
System.out.println(addUser);
BeanTest bean2 = ac.getBean("beanTest",BeanTest.class);
bean2.test();
}
}