代理模式实际上就是对于不能直接访问的对象提供给一个代理对象,通过代理对象可以访问该对象的功能,这样做的有点就每个对象的职责清晰,具有高扩展性更智能化,但是由于访问的对象当中增加了代理对象,有些类型的代理模式会造成请求的处理变慢,实现代理模式需要额外的工作会导致更加复杂;代理实现的技术方案:1、基于接口的动态代理(JDK动态代理,JDK官方的Proxy类),要求被代理的类最少实在这里插入代码片
现一个接口;2、基于子类的动态代理(第三方的CGLib)要求被代理的类不能用final修饰的类(最终类,为什么不能用final修饰,看源码可以了解到底层实际是重新实现了被代理类的子类,final修饰的类不能被继承)
JDK动态代理
1、被代理的对象必须实现一个接口;2、用JDK代理,被代理的过程需要实现InvocationHandler接口;3、代理的过程在invoke中实现;4、创建代理对象用Proxy.newProxyInstance实现;
实例:比如房东,中介和租客,那么中介可以代理房东实现收房租的功能;
public interface fangDongService {
void pay(String name);
}
public class fangDongServiceImpl implements fangDongService {
@Override
public void pay(String name) {
System.out.println(name+"来交租!");
}
}
创建代理处理过程对象
public class woaiwojia implements InvocationHandler {
//被代理的对象
private Object instance;
public woaiwojia(Object instance) {
this.instance = instance;
}
//执行调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy -->woaiwojia 创建的代理
// method-->pay方法 代理类执行的方法
//args pay方法的参数
args[0]="我爱我家带"+args[0];
Object invoke = method.invoke(instance, args);
return invoke;
}
}
public static void main(String[] args) {
//指定被创建代理的对象
fangDongService fangDongService = new fangDongServiceImpl();
//创建代理
fangDongService service=(fangDongService)Proxy.newProxyInstance(
//获取被代理对象的类加载器
fangDongServiceImpl.class.getClassLoader(),
//被创建代理的对象实现的所有接口
fangDongServiceImpl.class.getInterfaces(),
new woaiwojia(fangDongService));//代理
service.pay("张三");
}
CGLib动态代理
1、代理过程可以实现MethodInterceptor(Callback)接口中的invoke来实现;2、通过Enhancer来创建代理对象
cglib代理方式与jdk代理及其相似,可以理解为在jdk的代理上进行了封装,将上述的我爱我家改成链家代理;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class lianjia implements MethodInterceptor {
//被代理的对象
private Object instance;
public lianjia(Object instance) {
this.instance = instance;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//proxy -->woaiwojia 创建的代理
// method-->pay方法 代理类执行的方法
//args pay方法的参数
objects[0]="链家带"+objects[0];
Object invoke = method.invoke(instance, objects);
return invoke;
}
}
import org.springframework.cglib.proxy.Enhancer;
public class test2 {
public static void main(String[] args) {
//给房东生产代理,被代理的对象
fangDongService fangDongService = new fangDongServiceImpl();
//将需要代理的对象给代理过程对象
lianjia lianjia = new lianjia(fangDongService);
fangDongService proxy = (fangDongService)Enhancer.create(fangDongServiceImpl.class, lianjia);
proxy.pay("王五");
}
}
用cglib代理的话是否实现接口都可以完成
AOP动态代理
基于SpringAOP可以实现非常强大的功能,例如声明式事务、基于AOP的日志管理、基于AOP的权限管理等功能, 利用AOP可以将重复的代码抽取,重复利用,节省开发时间,提升开发效率。Spring的AOP其实底层就是基于动态 代理而来,并且支持JDK动态代理和CGLib动态代理,动态代理的集中体现在 DefaultAopProxyFactory 类中
如果我们在spring的配置文件中不配置 <aop:config proxy-target-class=“true”> ,此时默认使用的将是JDK动态 代理,如果配置了,则会使用CGLib动态代理
Spring中的aop动态代理实际上用的是jdk代理和cglib代理,如果实现接口则用jdk代理,如果没有实现接口则用cglib代理
AOP中jdk代理实现如下
cglib代理实现如下
SpingAop的简单运用
实现一个简单的AOP 比如对某一个方法进行增加日志的操作 ,比如下面这个add操作前后操作后增加一些日志
public interface UserService {
void add();
}
public class UserServiceImpl implements UserService{
//public class UserServiceImpl {
//@Override
public void add(){
System.out.println("增加用户!");
}
}
在调用add方法前和方法后进行一个记录日志的操作,在调用add前先调用before方法,调用add方法后调用after方法
//增强(切面)类
public class Log {
public void before(JoinPoint jp){
System.out.println("执行对象:"+jp.getSignature().getName());
System.out.println("前置通知记录!");
}
public void after(){
System.out.println("后置操作记录");
}
}
配置以下的xml
<!--userService-->
<bean id="userService" class="com.****.UserServiceImpl"/>
<!--注册增强(切面)类-->
<bean id="log" class="com.******.Log"/>
<!--AOP配置-->
<aop:config proxy-target-class="false"> <!-- -->
<!--配置切面(切入点和通知的结合)-->
<aop:aspect ref="log">
<!--前置通知-->
<aop:before method="before" pointcut="execution(* com.***.service.*.*(..))"></aop:before>
<aop:after method="after" pointcut="execution(* com.***.aop.service.*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
在spring 源码中,
public static void main(String[] args) {
//先加载容器,然后从容器中获取对象调用add方法
ApplicationContext act = new ClassPathXmlApplicationContext("spring-aop.xml");
UserServiceImpl userService = (UserServiceImpl) act.getBean("userService");
userService.add();
}
代理模式的理解
代理实际是一种思想想,jdk与cglib只是实现代理的一种技术手段,比如:代理模式-分布式文件代理服务,代理模式的应用场景除了代码级别,还可以将代理模式迁移到应用以及架构级别,如下图文件上传代理服务,针对一些图片小文件,我们可以直接把文件存储到FastDFS服务,针对大文件,例如视频文件,我们可以把它存储到第三方OSS。用户通过文件上传代理服务可以间接访问OSS和本地FastDFS,这种分布式海量文件管理解决方案,这里不仅在代码层面充分运用了代理模式,在架构层面也充分运用了代理模式。下面基于springboot简单的实现一下实现流程如下:
先做一个fileuplaod接口,然后阿里云上传和fastDfs上传都实现这个接口的upload方法,创建一个代理类FileUploadProxy,在代理类中根据根据上传文件的类型调用不同的上传方式,(为了更好的扩展)将上传方式和文件类型存放到以map的形式存放到yml文件中,key作为文件上传方式,Value为文件类型是一个list集合,将AliyunOSS和FastDfs注入到spring容器中,获取到yml文件中的map并且遍历,根据实际上传的文件类型来决定上传方式,然后通过代理调用上传
抽象出来的上传接口
public interface FileUpload {
String upload(byte[] buffers , String extName);
}
阿里云上传实现该接口
@Component(value = "aliyunOSSFileUpload")
public class AliyunOSSFileUpload implements FileUpload{
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKey}")
private String accessKey;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.oss.key}")
private String key;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Value("${aliyun.oss.backurl}")
private String backurl;
@Override
public String upload(byte[] buffers,String extName) {
String realName = UUID.randomUUID().toString()+"."+extName ;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessKeySecret);
// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key+realName, new ByteArrayInputStream(buffers));
// 上传字符串。
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(FileUtil.getContentType("."+extName));
putObjectRequest.setMetadata(objectMetadata);
ossClient.putObject(putObjectRequest);
// 关闭OSSClient。
ossClient.shutdown();
return backurl+realName;
}
}
FastDfs实现接口
@Component(value = "fastdfsFileUpoad")
public class FastdfsFileUpoad implements FileUpload{
@Value("${fastdfs.url}")
private String url;
/***
* 文件上传
* @param buffers:文件字节数组
* @param extName:后缀名
* @return
*/
@Override
public String upload(byte[] buffers, String extName) {
/***
* 文件上传后的返回值
* uploadResults[0]:文件上传所存储的组名,例如:group1
* uploadResults[1]:文件存储路径,例如:M00/00/00/wKjThF0DBzaAP23MAAXz2mMp9oM26.jpeg
*/
String[] uploadResults = null;
try {
//获取StorageClient对象
StorageClient storageClient = getStorageClient();
//执行文件上传
uploadResults = storageClient.upload_file(buffers, extName, null);
return url+uploadResults[0]+"/"+uploadResults[1];
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/***
* 初始化tracker信息
*/
static {
try {
//获取tracker的配置文件fdfs_client.conf的位置
String filePath = new ClassPathResource("fdfs_client.conf").getPath();
//加载tracker配置信息
ClientGlobal.init(filePath);
} catch (Exception e) {
e.printStackTrace();
}
}
public static StorageClient getStorageClient() throws Exception{
//创建TrackerClient对象
TrackerClient trackerClient = new TrackerClient();
//通过TrackerClient获取TrackerServer对象
TrackerServer trackerServer = trackerClient.getConnection();
//通过TrackerServer创建StorageClient
StorageClient storageClient = new StorageClient(trackerServer,null);
return storageClient;
}
代理类实现方式如下
@Component
@ConfigurationProperties(prefix = "upload") //获取yml文件中的map
public class FileUploadProxy implements ApplicationContextAware{
//注入application.yml中配置实例对应处理的文件类型
private Map<String,List<String>> filemap;
//注入Spring的容器对象ApplicationContext
private ApplicationContext act;
/***
* 上传方法
* 接收文件对象:MultipartFile
*/
public String upload(MultipartFile file) throws Exception{
//buffers:文件字节数组
byte[] buffers = file.getBytes();
//extName:后缀名 1.jpg->jpg
String fileName = file.getOriginalFilename();
String extName = StringUtils.getFilenameExtension(fileName);
//循环filemap映射关系对象
for (Map.Entry<String, List<String>> entry : filemap.entrySet()) {
//获取指定的value mp4,avi | png,jpg
List<String> suffixList = entry.getValue();
//匹配当前用户上传的文件扩展名是否匹配
for (String suffix : suffixList) {
if(extName.equalsIgnoreCase(suffix)){
//获取指定key aliyunOSSFileUpload | fastdfsFileUpload
//一旦匹配执行文件上传
String key = entry.getKey();
return act.getBean(key,FileUpload.class).upload(buffers,extName);
}
}
}
return "";
}
//注入映射配置
public void setFilemap(Map<String, List<String>> filemap) {
this.filemap = filemap;
}
//注入容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
act = applicationContext;
}
}
yml文件
@RestController
@RequestMapping(value = "/file")
public class FileController {
@Autowired
private FileUploadProxy fileUploadProxy;
/***
* 文件上传
* @param file
* @return
* @throws IOException
*/
@PostMapping(value = "/upload")
public String upload(MultipartFile file) throws Exception {
return fileUploadProxy.upload(file);
}
}