Java学习笔记03 ── Spring( AOP )

动态代理

所谓动态代理就是使用一个代理将原本对象包装起来,通过动态代理对象来完成实际对象要完成的功能。使用动态代理也是要实现实际对象要实现的功能,只不过可以在实现基本功能的基础上可以实现一些其他功能。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();
		
	}
}


今天的内容到此结束,谢谢大家的观看,如有错误请指正,谢谢!CSDN记录成长!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值