(JavaEE)面向切面
最近接触了下Spring的aop面向切面编程,感觉挺好用的。但是在这之前徘徊了很长时间。
先不提用法,百度一大堆谁都知道怎么用!
为什么要有这些概念?
且不提别的方面,当你加载其它第三方jar包的时候控制台打印的数据是不是都是日志,以前也曾小看了这些日志,其实这些日志作用非常大可以帮助我们检测代码的运行过程,所以在项目开发中日志是必不可少的,但是假如你在每一个方法中都前后写日志的话那是不是要写好多冗余的代码?所以我们将冗余的代码抽取出来,然后动态的向我们要执行的目标方法中织入即可。话说回来也不仅仅是打印打印日志而已,你甚至可以在里边做参数验证、权限验证等.....。
这张图在我们平时写代码的时候我们都懂,如果就是在方法执行的各个期间做出对应的操作,就是我们要达到的目的。
我们将这些需要的内容抽取出来,所以我们要做的这些事情有一个名词就叫做横切关注点。当我们将这些横切关注点抽取出来以后就形成了一个切面,切面对方法进行织入的前提条件就是得有连接点。如日志切面、权限验证切面等 ...... ,通过这些切面我们去动态的向目标方法进行织入。所以我们要执行的操作就会被在指定的时刻被执行。试想这样的话我们的目标方法就可以专注于专业的逻辑(代码结构会变得更加清晰)。
其实Spring中的aop底层也是基于动态代理的。
来先来看下动态代理实现面向切面编程的具体实例!
WorkInterface接口
public interface WorkInterface {
// 做工作的抽象方法
public abstract String doWork(String workName);
}
DynamicProxy动态代理类
public class DynamicProxy {
private static String INTERFACENAME = null;
// 返回代理类对象
public static Object backProxy(Class<?> clazz) {
DynamicProxy.INTERFACENAME = clazz.getName();
System.out.println(INTERFACENAME);
// 获取接口的名称
return new Invoker().getInstance(clazz);
}
}
//代理生成类
class Invoker {
// 生成代理类实例
public Object getInstance(Class<?> clazz) {
methodInvokeHandler invocationHandler = new methodInvokeHandler();
Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] {clazz}, invocationHandler);
return (Object) proxy;
}
}
class methodInvokeHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置通知:
System.out.println("前置通知 ...");
String result = null;
try {
System.out.println(args[0]);
result = args[0].toString();
System.out.println("返回通知 ..." + result);
// System.out.println(4/0);
}catch(Exception e) {
System.out.println("异常通知:" + e.getMessage());
}
// 后置通知
System.out.println("后置通知 ...");
return result;
}
}
测试类
TestService
public class TestService {
@Test
public void test() {
// 获取代理类实例
WorkInterface proxy = (WorkInterface) DynamicProxy.backProxy(WorkInterface.class);
// 执行方法
proxy.doWork("看书 ...");
}
}
打印结果:
如果想看异常通知:
就将上面代理类的那个注释打开就会看到打印 ...
以上便是通过,动态代理,动态的为借接口提供实现,并插入我们想要的行为。
下面通过SpringAop来实现切面编程会更加的优雅。
搭建Spring工程就不说了,主要说要想实现SpringAop面向切面编程的步骤
-1).在applicationContext.xml配置文件中开启扫描组件,并启用切面(AspectJ)注解驱动
<!-- 配置扫描组件 -->
<context:component-scan base-package="com.YHStudio.springAop" />
<bean id="work" class="com.YHStudio.springAop.dao.WorkImpl" />
<bean id="workService" class="com.YHStudio.springAop.service.WorkService" />
<!-- 启用切面注解 -->
<aop:aspectj-autoproxy />
-2).别忘了导入Springaop的命名空间、相关的jar包
aopalliance-1.0.jar
aspectjweaver-1.8.7.jar
spring-aop-4.2.4.RELEASE.jar
spring-aspects-4.2.4.RELEASE.jar
-3).我们虽然开启了Springaop注解驱动但是还差一步,就是我们必须得先新建一个切面类然后将其放入IOC容器中再使用@Aspect注解标注才可以
LogAspect切面
@Aspect
@Component(value="logAspect")
public class LogAspect {
// 定义切点表达式
@Pointcut(value="execution(* com.YHStudio.springAop.dao.WorkInterface.doWork(..))")
public void pointCut() {}
@Before(value="pointCut()")
public void workBeforeNote(JoinPoint jp) {
System.out.println("前置通知 ... 参数:" + Arrays.asList(jp.getArgs()));
/**
* 前置通知:
* 在方法执行前执行的, 可以取到方法的参数,可以对参数进行验证/拦截/转发页面但是我并不认为
* Apring aop在拦截/转发页面这块比SpringMVC 做得更好。
* 不过还是可以进行用户的权限判断的。
* 并且对于前置通知、后置通知、返回通知、异常通知最大的作用感觉莫过于打印日志了。
* 环绕通知的用处就大得多了,可以改变目标方法的返回值,可以决定目标方法是否能够执行,什么时候执行。
*/
}
@After(value="pointCut()")
public void workAfterNote(JoinPoint jp) {
System.out.println("后置通知 ... 参数:" + Arrays.asList(jp.getArgs()));
}
@AfterReturning(value="pointCut()", returning="result")
public void workReturnNote(JoinPoint jp, Object result) {
System.out.println("返回通知 ... 参数:" + Arrays.asList(jp.getArgs()) + "返回值:" + result);
}
@AfterThrowing(value="pointCut()", throwing="e")
public void workThrowsNote(JoinPoint jp, Exception e) {
System.out.println("异常通知 ... 参数:" + Arrays.asList(jp.getArgs()) + "异常:" + e);
}
@Around(value="pointCut()")
public Object workAround(ProceedingJoinPoint pjp) {
Object obj = null;
try {
obj = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("环绕通知 ..." + "返回值:" + obj);
return obj;
}
/*@Before(value="execution(* com.YHStudio.springAop.service.WorkService.testService())")
public void testServiceBeforeNote(JoinPoint jp) {
System.out.println("前置通知 ... 参数:" + Arrays.asList(jp.getArgs()));
}*/
@AfterThrowing(value="execution(* com.YHStudio.springAop.service.WorkService.testService())", throwing="e")
public void testServiceBeforeNote(JoinPoint jp, Exception e) {
System.out.println("前置通知 ... 异常:" + e);
}
}
以上我们通过切点表达式来指定我们切面应用的位置。
* com.YHStudio.springAop.dao.WorkInterface.doWork(..)
这句的意思就指向了接口中的方法 任意修饰符 com.YHStudio.springAop.dao.WorkInterface 包下的任意形参的doWork()方法。如果想切入该接口下面的所有方法仅仅需要将doWork方法改为 '*'星号即可!
并通过@Before、@Aftter、@AfterReturning、@AfterThrowing、@Around五个注解来对目标方法执行的5个时刻进行行为上的织入。其中@AfterRerurning、@Around注解标注的方法必须有返回值,返回值就是执行在@Around中执行的目标方法的返回值,@Around(环绕通知)是最强大的通知。有权决定方法是否执行,什么时候执行。
-4).WorkInterface接口
public interface WorkInterface {
// 工作的抽象方法
public abstract Boolean doWork(String content);
}
-5).WorkService -> Service类
public class WorkService {
public void checkWork() {
// 获取ctx对象
GetCtx<WorkInterface> ctx = new GetCtx<WorkInterface>();
WorkInterface workImpl = ctx.getInstance("work");
// 执行方法
Boolean bol = workImpl.doWork("敲代码");
// 获取并打印返回值
System.out.println(bol);
}
public void testService() {
System.out.println(4/0);
System.out.println("testService ...");
}
}
-7).WorkImpl接口的实现类
public class WorkImpl implements WorkInterface {
@Override
public Boolean doWork(String content) {
System.out.println("正在:" + content);
return true;
}
}
-8).Spring获取bean的抽象类
public class GetCtx<T> {
@SuppressWarnings("unchecked")
public T getInstance(String beanName) {
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
return (T)ctx.getBean(beanName);
}
}
-9).测试类
public class TestMain {
@Test
public void test() {
GetCtx<WorkService> ctx = new GetCtx<WorkService>();
WorkService service = ctx.getInstance("workService");
service.checkWork();
service.testService();
}
}
结果测试 -> 被实现类实现的接口的方法与普通方法都可以被织入,对其进行切面。
打印结果如下
所以相比较起来Springaop的切面编程更加的优雅,我们也可以对我们想要切面的方法进行切面编程了!
以上便是这一期的内容,学习中如有不对的地方还请指出!