一 java代理
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。通过代理,可以实现对目标对象的间接访问,即通过代理对象访问目标对象
代理模式中涉及到两方:
委托方、代理方
目标对象、代理对象
1 静态代理(掌握)
代理类和目标类实现相同的接口,在代理类中维护目标类的对象,以此实现对目标对象方法的调用。
优点:可以实现不修改目标对象代码的情况下,对目标对象的功能进行扩展。
缺点:代理对象与目标对象一样的接口,不易维护,一旦接口增加方法,则目标对象和代理类都需要维护
例如:老板通知开会,秘书传达指示
老板目标对象
秘书代理对象
动态代理
2 jdk代理
通过jdk代理,可以动态的在内容中构建代理对象(在程序运行时运用反射机制动态创建)
使用动态代理,要求目标对象必须实现了接口
1)定义接口
2)目标对象类实现接口
3)代理对象类实现InvocationHandler接口
public class XXX implements InvocationHandler{
//目标对象
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
String name = method.getName();
if(name.equals("xianglong18")){
//调用目标对象方法
Object ret = method.invoke(this.target, args);
System.out.println("增加的内容");
return ret;
}else{
return method.invoke(this.target, args);
}
}
//设置目标对象,返回代理对象
public Object bind(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), //类加载器
target.getClass().getInterfaces(), //对象接口
this);
}
}
3 cglib代理
针对没有实现接口的目标对象进行代理
原理:生成目标对象的子类,子类中覆盖父类的方法
需要支持cglib的jar文件
本例中使用spring中提供的相关类
//实现方法拦截器接口
public class BookProxy implements MethodInterceptor{
//目标对象
private Object target;
public Object bind(Object target){
this.target = target;
//增强类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(this.target.getClass());
//设置回调
en.setCallback(this);
//创建代理对象
return en.create();
}
@Override
public Object intercept(Object proxy, Method arg1, Object[] arg2, MethodProxy methodProxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println("调用方法前");
System.out.println("Method:" + arg1.getName());
System.out.println("Method:" + methodProxy.getSuperName());
//调用目标的方法(父类的方法)
Object ret= methodProxy.invokeSuper(proxy, arg2);
System.out.println("调用方法后");
return ret;
}
}
spring在运行期创建代理,有两种代理方式:
若目标对象实现了若干接口,spring就会使用JDK动态代理。
若目标对象没有实现任何接口,spring就使用CGLIB库生成目标对象的子类。
二 AOP
1 AOP概述
Aspect Oriented Program面向切面的编程,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
AOP是OOP(Object Oriented Programming,面向对象编程)的补充和完善。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
面向切面的编程将关注点的代码与业务逻辑分离
2 实现原理
AOP技术是建立在Java语言的反射机制与动态代理(jdk代理、cglib代理)机制之上的。
业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。
调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。
- 连接点(Joinpoint):是程序执行的某一个特定位置,是一个虚拟概念。如类中某一个方法调用前/调用后,方法抛出异常后等等,一个类或一段程序代码拥有一些具有边界性质的特定点
- 关注点(Advice通知、增强):织入目标类连接点上特定的代码
- 切面(aspect):关注点形成的类,切面类
- 切入点(pointcut切点):需要执行的目标方法
- 织入:将增强添加到目标类具体连接点上的过程
2 手动实现aop
3 xml配置实现aop(spring给咱们提供的)
- 需要导入jar包:
spring-aop-5.1.5.RELEASE.jar
aopalliance.jar
aspectjrt.jar aspectj-1.8.2\lib
aspectjweaver.jar aspectj-1.8.2\lib
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
- 引入aop的名称空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
3)创建切面类对象和aop配置
<!-- aop的配置 -->
<aop:config>
<!-- 切入点,对哪些方法织入切面方法 -->
<!--
execution([修饰符] 返回值类型 方法名(参数) [异常模式]) []表示可选部分
第一个* 任意返回类型
com.rr.aop 包
第二个* 包下面的任意类
*(..) 任意名称和参数的方法
-->
<aop:pointcut expression="execution(* com.rr.aop.*.*(..))" id="pc"/>
<!-- 切面类,通过ref指定对应的切面类的bean -->
<aop:aspect ref="logAOP">
<!-- 前置通知 ,执行目标方法前执行的方法 -->
<aop:before method="before" pointcut-ref="pc"/>
<!-- 后置通知 ,不管目标方法正常执行还是异常,都会调用 -->
<aop:after method="end" pointcut-ref="pc"/>
<!-- 正常返回通知 -->
<aop:after-returning method="afterReturn" pointcut-ref="pc"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pc"/>
<!-- 异常返回通知
throwing属性的值要和异常通知使用的方法中的异常变量的参数名 保持一致
-->
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
切入点表达式用法:
参考帮助文档11.2.3 Declaring a pointcut 切点的表达式
Some examples of common pointcut expressions are given below.
- the execution of any public method:
execution(public * *(..))
- the execution of any method with a name beginning with "set":
execution(* set*(..))
- the execution of any method defined by the AccountService interface:
execution(* com.xyz.service.AccountService.*(..))
- the execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))
- the execution of any method defined in the service package or a sub-package:
execution(* com.xyz.service..*.*(..))
- any join point (method execution only in Spring AOP) within the service package:
within(com.xyz.service.*)
- any join point (method execution only in Spring AOP) within the service package or a sub-package:
within(com.xyz.service..*)
- any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:
this(com.xyz.service.AccountService)
五类通知类型:
before前置通知[Before advice]:在切点前面执行,前置通知不会影响切点的执行,除非此处抛出异常。
after-returning正常返回通知[After returning advice]:在切点正常执行完成后执行,如果切点抛出异常,则不会执行。
after-throwing异常返回通知[After throwing advice]:在切点抛出异常后执行。
afrter返回通知[After (finally) advice]:在切点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
around环绕通知[Around advice]:环绕通知围绕在切点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
54 注解方式实现aop
@Aspect 表示切面类
@Order 执行顺序,数字越小,越先执行
@PointCut 切点
@Before 前置通知
...
@Component
@Aspect //指定当前类为切面类
public class LogAop {
// 指定切入点表单式: 拦截哪些方法; 即为哪些类生成代理对象
@Pointcut("execution(* com.rr.xmlaop.MyDao.*(..))")
public void pointcut(){
}
//前置通知 执行目标方法前执行
//JoinPoint 连接点对象,可以不写,通过该对象,可以获取目标方法相关的信息
@Before("pointcut()")
public void begin(JoinPoint jp) {
System.out.println("begin:");
System.out.println(jp.getSignature().getName());
}
//后置通知,执行目标方法后执行,不论是否异常
@After("pointcut()")
// public void end(){
public void end(JoinPoint jp) throws Exception, SecurityException {
}
//正常返回后通知,调用目标方法结束后执行,在代理返回前执行,异常不执行
@AfterReturning("pointcut()")
public void afterReturn(){
System.out.println("after return");
}
//环绕方法,必须使用ProceedingJoinPoint参数
@Around("pointcut()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}
// 异常返回时执行的方法
// 异常返回通知
@AfterThrowing(pointcut="pointcut()", throwing="e")
public void afterThrowing(Exception e){
System.out.println("after throw :" + e.getMessage());
}
}
<!-- aop的扫描 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>