- 引入
之前谈到了三层架构,以及如何使用spring通过ioc和注入解耦,今天我们就来谈一谈一个新的技术aop,通过它来实现架构的高内聚。(注意:本篇文章讲的都是底层原理,不需要跟代码)
上图就是通过前面的技术,优化出来的三层架构系统图,后面的案列会给出代码,通过这个图片可以看出还是存在一些问题的,当老板说需要新增校验权限,开启事务,记录日志,提交事务,回滚事务等操作的时候,就目前上图来说,只能写在service层;这样就会导致一个问题,原本只负责业务处理的service层,需要处理事务的代码,比如insert方法需要添加相关代码,querry也需要,这时候可能又有人说可以写工具类,话是这么说没有错,但还是在service层添加了和业务处理无关的相关代码,为了保证系统的高内聚,提出了新的技术AOP。
- AOP解释
所谓AOP就是面向切面编程,实现AOP的方法是一种成为代理类的设计模式,这个切面听起来很抽象,这里我给大家画个图就一目了然。
从上图可以看出,web层调用的方法不再是service,而是代理类的方法,通过代理类再来调用service中的方法,这样就可以不用修改service中的方法,从而实现高内聚。接下里给大家阐述一下三种实现AOP的代理模式方式:
- 静态代理模式:
静态代理模式简单来说,就是自己写一个类,让这个类来实现service中的接口,并在该类中声明一个service中的对象(假设为对象a),然后通过注解注入。之后覆写接口中的方法,在进行了一系列操作后,再通过a来调用service中处理业务的方法。其逻辑就和上图一样。
这样的设计模式虽然简单易懂,但是有个最大的问题就是会有很多冗余的代码,每一个操作如插入,查询,都要写一个方法,并且方法中的操作还都类似。
- 动态代理模式:
java为了解决上述的问题,开发了一个专门用来生产代理者的工具类,并且可以使用里面的回调函数来实现上述的事务处理等操作。下面就是获取代理者对象的核心声明代码,这里来解释一下他的参数:
1.ClassLoader loader是一个类加载器通常传入的是被代理对象的类加载器,获取方法为——被代理对象.getClass().getClassLoader(),通过反射来获取。
2.Class>[] interfaces,传入一个和被代理对象实现的相同接口,获取方法——被代理对象.getClass().getInterfaces(),
3.InvocationHandler h,这是一个回调函数,当使用代理者调用方法时,会进入回调函数,该函数一般使用匿名内部类来实现
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
下面为用工厂模式来实现动态代理的代码:
@Component
public class UserServiceImplJavaProxyFactory {
@Autowired
@Qualifier("userServiceImpl")
private UserService userService = null;//被代理者
@Bean("userService")//工厂类的注解,通过该id注入到web层中的对象
public UserService getProxy(){
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),//类加载器
userService.getClass().getInterfaces(),//实现的接口
new InvocationHandler() {//回调函数
@Override//invoke函数说明,proxy是调用method的对象,method是当前执行的方法,arg是当前方法的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
System.out.println("记录日志..");
System.out.println("控制权限..");
System.out.println("开启事务..");
Object retObj = method.invoke(userService,args);//当前执行的方法调用invoke,第一个参数表示是去调用哪个对象的method,第二个表示参数,
System.out.println("提交事务..");
return retObj;
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
);
return proxy;//返回代理者
}
}
这种方式也不是没有问题,问题就在这里
明眼人已经发现,这样做的话就只能调用接口中声明的方法,而对service类中其他的方法无法调用,针对这种情况,就有了下面第三种方法。
- 第三方包cglib实现的动态代理模式:
CGLIB实现动态代理的原理是,生成的动态代理是被代理者的子类,所以代理者具有和父类即被代理者 相同的方法,从而实现代理,这种方式基于继承,不再受制于接口。
ServiceUser su = new ServiceUser();
//增强器
Enhancer enhancer = new Enhancer();
//设定接口 -- 此方法要求生成的动态代理额外实现指定接口们 ,单cglib动态代理不是靠接口实现的,所以可以不设置
enhancer.setInterfaces(su.getClass().getInterfaces());
//设定父类 -- 此处要传入被代理者的类,cglib是通过集成被代理者的类来持有和被代理者相同的方法的,此方法必须设置
enhancer.setSuperclass(su.getClass());
//设定回调函数 -- 为增强器设定回调函数,之后通过增强器生成的代理对象调用任何方法都会走到此回调函数中,实现调用真正被代理对象的方法的效果
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
-----------------------------
}
});
//创建代理对象
Service service=(serviceUser)enhancer.create();
原理图和文章第一张图片相同这里就不再贴图了,直接上代码:
Dao层:
public interface Dao {
public void insert();
public void querry();
}
//////////////////////////////
@Repository("mysql")
public class MySql implements Dao {
@Override
public void insert() {
System.out.println("Mysql----insert");
}
@Override
public void querry() {
System.out.println("Mysql----querry");
}
}
service层:
public interface Service {
public void regist();
public void login();
}
////////////////////////////
@org.springframework.stereotype.Service("US")
public class UserService implements Service{
@Autowired
@Qualifier("mysql")
Dao dao=null;
@Override
public void regist() {
dao.insert();
}
@Override
public void login() {
dao.querry();
}
}
web层:
@Controller("UW")
public class UserWeb {
@Autowired
@Qualifier("US")
Service service=null;
public void regist(){
service.regist();
}
public void login(){
service.login();
}
}
xml配置文件:
<context:component-scan base-package="cn.tdu"/>
<context:annotation-config></context:annotation-config>
测试:
@Test
public void test01(){
ApplicationContext context=new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserWeb us=(UserWeb) context.getBean("UW");
us.login();
us.regist();
((ClassPathXmlApplicationContext)context).close();
}