这篇文章主要介绍Spring Aop的相关知识,在最后介绍了Java的代理模式设计思想。文章中所有代码仅供参考,详情了解请参考官网文档
Spring-AOP
1、AOP?
Aspect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来说,Aop 关注的不再是程序代码中某个类,某些方法,而 aop 考虑的更多的是一种面到面的切入 即层与层之间的一种切入,所以称之为切面。联想大家吃的汉堡(中间夹肉)。那么 aop 是怎么做到拦截整个面的功能呢?考虑中级学到的 servlet urlpattern /* 的配置 ,实际上也是 aop 的实现。Spring AOP 是 Spring 的核心模块之一,Spring AOP 通过消除代码纠缠和交叉问题,在我们的项目中支持面向切面的模块化。将 Spring AOP 库添加到我们的项目中,你将会开始体验到使用它的一些组件所带来的好处。
官方资料文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop
2、SpringAop使用场景
- 记录日志
- 监控方法运行时间 (监控性能)
- 权限控制
- 缓存优化
- 第一次调用查询数据库,将查询结果放入内存对象
- 第二次调用, 直接从内存对象返回,不需要查询数据库
- 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
3、使用Aop的好处
- 降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)
- 提高了代码的复用性。
- 提高系统的扩展性。
4、SpringAop的基本概念
- Joinpoint(连接点)
- 被拦截到的每个点,spring 中指被拦截到的每一个方法,一个连接点即代表一个方法的执行。
- Pointcut(切入点)
- 对连接点进行拦截的定义(匹配规则定义 规定拦截哪些方法,对哪些方法进行处理),spring 这块有专门的表达式语言定义。
- Advice(通知/增强)
- 拦截到每一个连接点即(每一个方法)后所要做的操作
- 前置通知 (前置增强) --before() 执行方法前通知
- 返回通知(返回增强)–afterReturn 方法正常结束返回后的通知
- 异常抛出通知(异常抛出增强)–afetrThrow()
- 最终通知—after 无论方法是否发生异常,均会执行该通知
- .环绕通知—around 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行
- 正常情况执行顺序:
- 异常情况执行顺序:
- Aspect(切面)
- 切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。
- Target(目标对象)
- 被代理的目标对象
- Weave(织入)
- 将切面应用到目标对象并生成代理对象的这个过程即为织入。
- Introduction(引入)
- 在不修改原有应用程序代码的情况下,在程序运行期为类动态添加方法或者字段的过程称为引入
Spring AOP类结构图
最核心的处理是InvocationHandler接口中的invoke()方法
5、SpringAop日志实现方式
Aop配置有两种方式,分别是注解方式和XML方式。
第一种是注解直接加在增强方法上,第二种是先定义切点,在增强,这两种方式都可以实现springaop的操作。
第一种注解方式
Ⅰ jar包坐标引入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
Ⅱ beans.xml配置
配置spring文件,开启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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启AOP注解驱动-->
<aop:aspectj-autoproxy />
</beans>
Ⅲ 编写Aop实现类:
package com.shsxt.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@Component // 代表此类交给Spring管理
@Aspect // 标记此类是一个切面
public class LogAspect {
// 定义一个切入点:就是定义连接点(被AOP拦截的方法)的规则
@Pointcut("execution(* com.shsxt.service..*.*(..))")
public void pointcut(){}
// 定义前置增强 -- 比较常见
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
// 获取目标对象
Object target = joinPoint.getTarget();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取执行方法
Method method = signature.getMethod();
System.out.println("方法名称:" + method.getName());
// 获取参数
Parameter[] parameters = method.getParameters();
System.out.println("前置增强。。。。");
}
// 定义后置返回增强
@AfterReturning("pointcut()")
public void afterReturning() {
System.out.println("后置返回增强。。。");
}
// 定义异常增强
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("异常增强。。。");
}
// 定义最终(后置)增强
@After("pointcut()")
public void after() {
System.out.println("最终增强。。。");
}
// 环绕增强 --- 比较常见
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取目标对象
Object target = joinPoint.getTarget();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取执行方法
Method method = signature.getMethod();
System.out.println("方法名称:" + method.getName());
// 获取参数
Parameter[] parameters = method.getParameters();
// 执行连接点方法
Object result = joinPoint.proceed(); // 获取连接点返回值
return result;
}
}
Ⅳ 实现业务Demo:
用户登录操作的业务增强
UserDao:
数据访问层
package com.shsxt.dao;
import com.shsxt.model.User;
import com.shsxt.service.UserService;
import org.springframework.stereotype.Repository;
@Repository // 标记这个dao类交给Spring管理
public class UserDao {
public void save(User user) {
System.out.println("Save User..." + user.toString());
}
}
UserService:
业务逻辑层
package com.shsxt.service;
import com.shsxt.dao.UserDao;
import com.shsxt.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service // 标记这个Service类交给Spring管理
public class UserService {
@Resource
private UserDao userDao;
/**
* 编写一个添加用户的方法
*/
public void addUser(User user) {
userDao.save(user);
}
public String updateUser() {
System.out.println("更新数据。。。。。。。。。。。。。。。");
return "更新成功!";
}
public String deleteUser() {
System.out.println("删除数据。。。。。。。。。。。。。。。");
int i = 1/0;
return "删除成功!";
}
}
UserController:
控制层
package com.shsxt.controller;
import com.shsxt.model.User;
import com.shsxt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
@Controller // 标记这个controller交给Spring管理
public class UserController {
@Autowired
private UserService userService;
public void show(User user) {
System.out.println("调用控制层方法。。");
userService.addUser(user);
}
public void error() {
System.out.println("调用抛出异常方法。。");
userService.deleteUser();
}
}
User:
JavaBean
package com.shsxt.model;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class User {
private Integer id;
private String userName;
private String password;
private List<String> noddles;
private Set<String> cookies;
private Map<String, String> views;
private Properties sxts;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
public List<String> getNoddles() {
return noddles;
}
public void setNoddles(List<String> noddles) {
this.noddles = noddles;
}
public Set<String> getCookies() {
return cookies;
}
public void setCookies(Set<String> cookies) {
this.cookies = cookies;
}
public Map<String, String> getViews() {
return views;
}
public void setViews(Map<String, String> views) {
this.views = views;
}
public Properties getSxts() {
return sxts;
}
public void setSxts(Properties sxts) {
this.sxts = sxts;
}
}
LogAspectTest:
实现测试类
package com.shsxt;
import com.shsxt.model.User;
import com.shsxt.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class LogAspectTest {
@Test
public void testBefore() {
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
UserService userService = ac.getBean(UserService.class);
User user = new User();
user.setUserName("abc");
user.setPassword("111");
user.setId(1);
userService.addUser(user);
}
/**
* 测试返回增强
*/
@Test
public void testAfterReturning() {
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
UserService userService = ac.getBean(UserService.class);
userService.updateUser();
}
/**
* 测试异常增强
*/
@Test
public void testThrowReturning() {
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
UserService userService = ac.getBean(UserService.class);
userService.deleteUser();
}
/**
* 测试环绕增强
*/
@Test
public void testAround() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicajavation.xml");
UserService userService = ac.getBean(UserService.class);
String result = userService.updateUser();
System.out.println(result);
}
}
第二种XML方式:
Ⅰjar包坐标引入
配置项目pom.xml:demo代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shsxt</groupId>
<artifactId>spring-aop</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring-aop</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.2.1.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--引入Spring test测试框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!--aspectj语法的jar包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
</dependencies>
<build>
</build>
</project>
Ⅱ beans.xml配置
<!-- aop 相关配置 -->
<aop:config>
<!-- aop 切面配置 -->
<aop:aspect ref="logCut">
<!-- 定义 aop 切入点 -->
<aop:pointcut expression="execution (* com.shsxt.service..*.*(..))"
id="cut"/>
<!-- 配置前置通知 指定前置通知方法名 并引用切入点定义 -->
<aop:before method="before" pointcut-ref="cut"/>
<!-- 配置返回通知 指定返回通知方法名 并引用切入点定义 -->
<aop:after-returning method="afterReturning" pointcut-ref="cut"/>
<!-- 配置异常通知 指定异常通知方法名 并引用切入点定义 -->
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-
ref="cut"/>
<!-- 配置最终通知 指定最终通知方法名 并引用切入点定义 -->
<aop:after method="after" pointcut-ref="cut"/>
<!-- 配置环绕通知 指定环绕通知方法名 并引用切入点定义 -->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
Ⅲ 编写Aop实现类
package com.shsxt.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class PermissionAspect {
// 定义前置增强 -- 比较常见
public void before(JoinPoint joinPoint) {
// 获取目标对象
Object target = joinPoint.getTarget();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取执行方法
Method method = signature.getMethod();
System.out.println("方法名称:" + method.getName());
// 获取参数
Parameter[] parameters = method.getParameters();
System.out.println("前置增强。。。。");
}
// 定义后置返回增强
public void afterReturning() {
System.out.println("后置返回增强。。。");
}
// 定义异常增强
public void afterThrowing() {
System.out.println("异常增强。。。");
}
// 定义最终(后置)增强
public void after() {
System.out.println("最终增强。。。");
}
// 环绕增强 --- 比较常见
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取目标对象
Object target = joinPoint.getTarget();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取执行方法
Method method = signature.getMethod();
System.out.println("方法名称:" + method.getName());
// 获取参数
Parameter[] parameters = method.getParameters();
// 执行连接点方法
Object result = joinPoint.proceed(); // 获取连接点返回值
return result;
}
}
Ⅳ 业务实现代码与上述第一种一致
测试类:
package com.shsxt;
import com.shsxt.controller.HelloController;
import com.shsxt.controller.UserController;
import com.shsxt.model.User;
import com.shsxt.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) // 单元测试程序入口
@ContextConfiguration({"classpath:application.xml"}) // 加载上下文xml配置文件
public class PermissionAspectTest {
@Autowired
private UserController userController;
@Test
public void testBefore() {
helloController.say();
}
/**
* 测试返回增强
*/
@Test
public void testAfterReturning() {
}
/**
* 测试异常增强
*/
@Test
public void testThrowReturning() {
userController.error();
}
/**
* 测试环绕增强
*/javajava
@Test
public void testAround() {
}
}
6、SpringAOP实现机制
横向抽取机制,那么什么是横向抽取机制呢?所谓的横向抽取机制就是使用动态的代理的方式(cglib代理和jdk代理)来实现对象的代理,实际上我们操作的是假对象。既然有横向抽取机制,那么有没有纵向代理模式呢 ?答案是有的。那么什么是纵向抽取呢?纵向抽取就是把公共的方法写在父类里,所有的类都继承父类,这样就是能调用父类的方法。例如,你购物付款是一个子类的功能,你可能还会取款,这也是一个功能,而在他们结束之后,银行都会发送一个信息给你,这又是一个功能,这个银行给你发送信息是个公共的方法,所以这个发信息的功能就是属于父类的。子类继承父类并调用父类的方法就是纵向抽取。
横向抽取机制(AOP思想):
7、SpringAop的执行流程
-
解析xml;
-
实例化所有的bean;
-
解析aop:config;
- 解析aop:aspect,得到aspect引用的对象;txManager
- 解析aop:aspect里面的每一个切面;
- 得到该aspect对应的pointcut-ref;
- 得到pointcut-ref对应的pointcut的表达式;
- 使用表达式中用于匹配类型的表达式;
- 使用该表达式去和spring里面配置的所有的bean的类型进行匹配;
- 如果匹配不上,不管;
- 如果匹配上了,该对象作为spring动态代理的目标对象;
- 如果该对象实现了接口,使用JDK的动态代理包装;
- 如果该对象没有实现接口,使用cglib包装;
- 得到配置的拦截时机+逻辑提供类(txManager)的对应方法(从method解析)+pointcut表达式中方法的匹配模式创建一个拦截器
- 在把该拦截器使用对应的动态代理机制代理成代理对象;
- 替换spring容器中的对应bean的实例;
8、SpringAop实现原理
首先AOP思想的实现一般都是基于代理模式 ,具体分为静态代理和动态代理。在下面第九模块会详细介绍代理模式。而SpringAOP主要是在CGLIB、JDK动态代理之间进行切换。
在SpringAOP动态代理指的是,AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。为了启用Spring对@AspectJ方面配置的支持,并保证Spring容器中的目标Bean被一个或多个方面自动增强,但必须在Spring配置文件中添加如下配置<aop:aspectj-aotuproxy/>。当启动了@AspectJ支持后,在Spring容器中配置一个带@Aspect注释的Bean,Spring将会自动识别该Bean,并将该Bean作为方面Bean处理。
JDK动态代理:
通过反射来接收被代理的类,但是被代理的类必须实现接口,核心是InvocationHandler和Proxy类
代码实现:
第一步:编写一个代理生成处理类,实现InvocationHandler接口
public class JdkHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
第二步:编写一个目标对象(真实角色)的引用,是一个Object类型
public class JdkHandler implements InvocationHandler {
// 真实角色的引用
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
第三步:编写生成代理对象的方法 —重点
public class JdkHandler implements InvocationHandler {
// 真实角色的引用
private Object target;
/**
* 获取代理对象 -- 重点,通过反射机制获取代理对象
* @param target 告诉代理生成器要生成什么样的代理对象
* @return
*/
public Object getProxy(Object target) {
// 传入类加载器、当前对象的所有接口方法、InvocationHandler对象
this.target = target;
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
第四步:重写invoke方法,设置增强,同时执行代理方法 –难点
/**
* 基于JDK的代理
*/
public class JdkHandler implements InvocationHandler {
// 真实角色的引用
private Object target;
/**
* 获取代理对象 -- 重点,通过反射机制获取代理对象
* @param target 告诉代理生成器要生成什么样的代理对象
* @return
*/
public Object getProxy(Object target) {
// 传入类加载器、当前对象的所有接口方法、InvocationHandler对象
this.target = target;
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强方法
before();
// 执行真实角色的方法
Object result = method.invoke(target, args);
after();
return result;
}
private void before() {
System.out.println("结婚现场紧张布置中。。。");
}
private void after() {
System.out.println("恭喜你进入人生第二阶段。。。");
}
}
第五步:测试
public class JdkDynamicProxyTest {
@Test
public void testJdkProxy() {
// 真实角色
Marry marry = new You();
JdkHandler jdkHandler = new JdkHandler();
// 生成一个代理对象
Marry marryCompany = (Marry)jdkHandler.getProxy(marry);
// 执行接口方法, 会去执行Handler的invoke()方法
marryCompany.toMarry();
}
@Test
public void testHouseRentProxy() {
// 真实角色
RentHouse rentHouse = new Customer();
JdkHandler jdkHandler = new JdkHandler();
// 生成一个代理对象, 会去执行Handler的invoke()方法
RentHouse lianjia = (RentHouse)jdkHandler.getProxy(rentHouse);
// 执行接口方法
lianjia.rent();
}
}
CGLIB动态代理:
cglib动态代理的类一般是没有实现接口的类,cglib是一个代码生成的类库,可以在运行时动态生成某个类的子类,所以,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
代码实现:
第一步 编写动态cglib代理类
- 实现MethodInterceptor接口
- 编写一个真实角色的引用 编写构建代理对象的方法
- 通过Enhancer类进行创建,不需要相关的接口
- 重写拦截请求代理的方法,设置增强
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DynamicCglibProxyHandler implements MethodInterceptor {
// 真是角色的引用
private Object target;
/**
* 创建代理对象
* @param target 真实角色对象
* @return
*/
public Object buildProxy(Object target) {
this.target = target;
// 创建代理对象
Enhancer enhancer = new Enhancer();
// 通过enhancer对象创建代理对象
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
Object proxy = enhancer.create();
return proxy;
}
/**
* 拦截执行代理的方法
* @param o
* @param method
* @param objects 执行代理方法的参数
* @param methodProxy 执行代理的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
before();
Object result = methodProxy.invoke(target, objects);// 其中执行的是真实角色的方法索引应该传入target对象
after();
return result;
}
private void before() {
System.out.println("方法执行前。。。。");
}
private void after() {
System.out.println("方法执行后。。。。");
}
}
第二步 编写一个StudyJava的类
public class StudyJava {
public String study(String name) {
System.out.println("好喜欢学JAVA...");
return name + ":好喜欢学JAVA";
}
}
第三步 编写测试类
package com.shsxt;
import com.shsxt.marry.Marry;
import com.shsxt.marry.You;
import com.shsxt.rent.Customer;
import com.shsxt.rent.RentHouse;
import com.shsxt.study.StudyJava;
import org.junit.Test;
public class TestDynamicCGlibProxy {
@Test
public void testDynamicProxy() {
// 创建一个生成器
DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler();
// 创建一个真实角色
Marry marry = new You();
// 创建代理对象
Marry proxy = (Marry) handler.buildProxy(marry);
// 执行Marry的toMarry方法
proxy.toMarry();
}
@Test
public void testRentHouse() {
// 创建生成器
DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler();
// 创建租房真实角色
RentHouse rentHouse = new Customer();
// 生成一个代理对象
RentHouse lianjia = (RentHouse)handler.buildProxy(rentHouse);
// 执行代理租房方法
String result = lianjia.rent("lili");
System.out.println("最终结果:" + result);
}
@Test
public void testStudyJava() {
// 创建生成器
DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler();
// 创建租房真实角色
StudyJava studyJava = new StudyJava();
// 生成一个代理对象
StudyJava banzhang = (StudyJava)handler.buildProxy(studyJava);
// 执行代理租房方法
String result = banzhang.study("丽丽");
System.out.println("最终结果:" + result);
}
}
Spring AOP使用方式实现代理的逻辑在org.springframework.aop.framework.DefaultAopProxyFactory中实现
源代码如下供参考:
package org.springframework.aop.framework;
import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//判断如果是接口,或者是被代理的类,则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
//否则用cglib动态代理,(没有实现接口的类)
return new ObjenesisCglibAopProxy(config);
}
else {
//默认使用jdk动态代理
return new JdkDynamicAopProxy(config);
}
}
/**
* 确定提供的{@link AdvisedSupport}是否仅有
* 指定了{@link org.springframework.aop.SpringProxy}接口
*(或者根本没有指定的代理接口)。
*/
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
小结:
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
9、代理设计模式
代理模式在 java 开发中是一种比较常见的设计模式。设计目的旨在为服务类与客户类之间插入其他功能,插入的功能对于调用者是透明的,起到伪装控制的作用。如租房的例子 房客、中介、房东。对应于代理模式中即:客户类 代理类 委托类(被代理类)。
代理模式设计原则
-
代理类 与委托类具有相似的行为(共同)
-
代理类增强委托类的行为
代理模式实现三要素
- 接口定义
- 目标对象 与代理对象必须实现统一接口
- 代理对象持有目标对象的引用 增强目标对象行为
代理模式分类
静态代理和动态代理
静态代理 :
为某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
静态代理对于代理的角色是固定的,如 dao 层 20 个 dao 类,如果要对方法的访问权限进行代理,此时需要创建 20 个静态代理角色,引起类爆炸,无法满足生产上的需要,于是就催生了动态代理的思想。
动态代理:
相比于静态代理,动态代理在创建代理对象上更加的灵活,它会根据需要通过反射机制在程序运行期动态的为目标对象创建代理对象,代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。提高开发效率,并且可以批量化创建代理,提高代码复用率。
动态代理实现方式
JDK 动态代理
被代理目标对象必须实现某一或某一组接口 实现方式 通过回调创建代理对象。
对于 jdk 动态代理实现方式比较复杂,回调方式实现 底层原理参考:http://rejoy.iteye.com/blog/1627405
代码实现:
package com.shsxt.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* jdk 动态代理
*
*/
public class JdkHandler implements InvocationHandler{
// 目标类
private Object target;
public JdkHandler(Object target) {
this.target = target;
}
/**
* 程序运行期动态创建代理角色
* @return
*/
public Object getProxy(){
/**
* 获取代理对象
* 1.类加载器
* 2.目标类 实现的接口 class
* 3.当前类
* @return
*/
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
public void before(){
System.out.println("婚礼现场紧张布置中......");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before();//增强真实角色行为
Object result= method.invoke(target, args);// 执行真实角色方法
after();//增强真实角色行为 java
return result;
}
public void after(){
System.out.println("恭喜您成功进入人生第二阶段.....");
}
}
CGLLIB 动态代理
code generator library ,操作字节码,被代理目标对象可以不必实现接口,继承的方式实现
与 jdk 提供的代理区别:Proxy->委托类必须有接口,制作过程比较快,执行慢;cglib->委托类可以没有接口,继承的思维来实现相似性,制作代理过程比较慢,执行快。主要解决没有接口类的代理实现。
代码实现:
package com.shsxt.test;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibInterceptor implements MethodInterceptor {
private Object target;
public CglibInterceptor(Object target) {
this.target = target;
}
// 运行期动态创建代理类
public Object getProxy(){
Enhancer enhancer=new Enhancer();
//设置父类 class
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public void before(){
System.out.println("婚礼现场紧张布置中......");
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
before();//增强真实角色行为
Object result= arg3.invoke(target, arg2);
after();//增强真实角色行为
return result; java
}
public void after(){
System.out.println("恭喜您成功进入人生第二阶段.....");
} }
cglib->委托类可以没有接口,继承的思维来实现相似性,制作代理过程比较慢,执行快。主要解决没有接口类的代理实现。
代码实现:
package com.shsxt.test;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibInterceptor implements MethodInterceptor {
private Object target;
public CglibInterceptor(Object target) {
this.target = target;
}
// 运行期动态创建代理类
public Object getProxy(){
Enhancer enhancer=new Enhancer();
//设置父类 class
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public void before(){
System.out.println("婚礼现场紧张布置中......");
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
before();//增强真实角色行为
Object result= arg3.invoke(target, arg2);
after();//增强真实角色行为
return result; java
}
public void after(){
System.out.println("恭喜您成功进入人生第二阶段.....");
} }