学而时习之,不亦说乎!

                             --《论语》

看这一篇之前最好先看前面关于AOP的两篇。

http://www.cnblogs.com/zby9527/p/6945756.html (JDK代理和CGLIB代理)

http://www.cnblogs.com/zby9527/p/6946952.html (Spring的AOP)

AspectJ:

1.AspectJ是一个基于Java语言的AOP框架。

2.Spring2.0以后新增了对AspectJ切点表达式支持。

3.@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面新版本Spring框架,建议使用AspectJ方式来开发

AspectJ最强大的地方在于他的切入点表达式:

语法:execution(修饰符  返回值  包.类.方法名(参数) throws异常)

  修饰符,一般省略

    public 公共方法

    * 任意

  返回值,不能省略

    void 返回没有值

    String 返回值字符串

    * 任意

  包

    com.zby.service  固定包

    com.zby.oa.*.service oa包下面子包 (例如:com.zby.oa.flow.service)

    com.zby.oa..   oa包下面的所有子包(含自己)

    com.zby.oa.*.service.. oa包下面任意子包,固定目录service,service目录任意包

  类

    UserServiceImpl 指定类

    *Impl 以Impl结尾

    User* 以User开头

    * 任意

  方法名,不能省略

    addUser 固定方法

    add* 以add开头

    *Do 以Do结尾

    * 任意

  (参数)

    () 无参

    (int) 一个整型

    (int ,int) 两个

    (..) 参数任意

  throws ,可省略,一般不写。

当然,execution也是可以变得,但是一般用这个就够了,更详细的表达式用法,当然是查看专业文档。

AspectJ和aopalliance通知的区别:

AOP联盟的通知类型具有特性接口,必须实现,从而确定方法名称,而AspectJ的通知类型只定义了类型名称和方法格式,这意味着,我们的切面不需要实现任何方法!!!。

 AspectJ通知:

  before:前置通知(应用:各种校验)

    在方法执行前执行,如果通知抛出异常,阻止方法运行

  afterReturning:后置通知(应用:常规数据处理)

    方法正常返回后执行,如果方法中抛出异常,通知无法执行,必须在方法执行后才执行,所以可以获得方法的返回值。

  around:环绕通知(应用:十分强大,可以做任何事情)

    方法执行前后分别执行,可以阻止方法的执行,必须手动执行目标方法

  afterThrowing:抛出异常通知(应用:包装异常信息)

    方法抛出异常后执行,如果方法没有抛出异常,无法执行

  after:最终通知(应用:清理现场)

    方法执行完毕后执行,无论方法中是否出现异常

当然,最重要也最常用的还是环绕通知,因为环绕通知必须手动执行目标方法,所以,可以代替其他几个通知。

使用XML配置Spring整合AspectJ的AOP:

1)项目整体结构如下:

2)创建maven项目,pom.xml如下:

<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.zby</groupId>
<artifactId>aop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
</dependencies>
</project>

3)创建目标类UserService:

package com.zby.service;

public class UserService {

	public void saveUser(String username, String password) {
System.out.println("save user[username=" + username + ",password=" + password + "]");
} }

4)创建切面类:

package com.zby.interceptor;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect { public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知 : " + joinPoint.getSignature().getName());
} public void myAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
} public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知执行方法前");
// 手动执行目标方法
Object obj = joinPoint.proceed(); System.out.println("环绕通知执行方法后");
return obj;
} public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常通知 : " + e.getMessage());
} public void myAfter(JoinPoint joinPoint) {
System.out.println("最终通知");
}
}

切面类没有实现接口,但是有几种方法参数,这些不是必须的。这些传入的对象是什么?当然是我们在切面点需要的信息!用脑壳想,在给一个方法进行增强的时候,前置方法,或者后置方法,或者环绕方法,有可能需要得到原方法的哪些信息,这里面都有。

5)编写配置文件applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 目标类 -->
<bean id="userService" class="com.zby.service.UserService"></bean> <!-- 切面类 -->
<bean id="myInterceptor" class="com.zby.interceptor.MyAspect"></bean> <aop:config>
<aop:aspect ref="myInterceptor">
<aop:pointcut expression="execution(* com.zby.service.UserService.*(..))"
id="myPointcut" />
<!--环绕通知
<aop:around method="" pointcut-ref=""/>
通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ }
返回值类型:Object 方法名:任意
参数:org.aspectj.lang.ProceedingJoinPoint
抛出异常
执行目标方法:Object obj = joinPoint.proceed();
例如: <aop:around method="myAround" pointcut-ref="myPointCut"/> -->
<aop:around method="myAround" pointcut-ref="myPointcut" />
<!-- 最终通知 --> <aop:after method="myAfter" pointcut-ref="myPointcut" />
<!--后置通知 ,目标方法后执行,获得返回值
<aop:after-returning method="" pointcut-ref="" returning=""/>
returning 通知方法第二个参数的名称 通知方法格式:
public void myAfterReturning(JoinPoint joinPoint,Object ret){}
参数1:连接点描述
参数2:类型Object,参数名 returning="ret" 配置的 例如:
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" /> -->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointcut" returning="ret" />
<!--抛出异常 <aop:after-throwing method="" pointcut-ref="" throwing=""/>
throwing :通知方法的第二个参数名称
通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ }
参数1:连接点描述对象
参数2:获得异常信息,类型Throwable ,参数名由throwing="e" 配置 例如: <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> -->
<aop:after-throwing method="myAfterThrowing"
pointcut-ref="myPointcut" throwing="e" />
<!--前置通知
<aop:before method="" pointcut="" pointcut-ref=""/>
method : 通知,及方法名
pointcut :切入点表达式,此表达式只能当前通知使用。
pointcut-ref : 切入点引用,可以与其他通知共享切入点。
通知方法格式:public void myBefore(JoinPoint joinPoint){}
参数1:org.aspectj.lang.JoinPoint 用于描述连接点(目标方法),获得目标方法名等
例如: <aop:before method="myBefore" pointcut-ref="myPointCut"/> -->
<aop:before method="myBefore" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
</beans>

  

6)编写测试类:

package com.zby.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.zby.service.UserService; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class AOPTest { @Autowired
private UserService userService; @Test
public void testProxy() {
System.out.println("After Proxy......");
userService.saveUser("zby", "1234567890");
}
}

7)控制台打印结果:

六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
六月 09, 2017 2:07:56 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6e983d8d, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@4cf12cb4, org.springframework.test.context.support.DirtiesContextTestExecutionListener@6dae04e2]
六月 09, 2017 2:07:56 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
六月 09, 2017 2:07:56 下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext@24024ad8: startup date [Fri Jun 09 14:07:56 CST 2017]; root of context hierarchy
After Proxy......
环绕通知执行方法前
前置通知 : saveUser
save user[username=zby,password=1234567890]
后置通知 : saveUser , -->null
最终通知
环绕通知执行方法后

这个DEMO就是一个大杂烩,其实使用时使用一个环绕通知就够了。再环绕通知里面必须手动执行方法,因此我们用try-catch把方法执行包裹起来,然后在执行前和执行后写增强代码即可。

使用注解配置Spring整合AspectJ的AOP:

1)上面的一二步骤不变。

2)编写目标类UserService:

package com.zby.service;

import org.springframework.stereotype.Service;

@Service
public class UserService { public void saveUser(String username, String password) {
System.out.println("save user[username=" + username + ",password=" + password + "]");
} }

3)编写切面类,使用注解:

package com.zby.interceptor;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; @Component
@Aspect
public class MyAspect {
// 多个方法需要使用这个切入点表达式,定义为一个公用的
@Pointcut("execution(* com.zby.service..*(..))")
public void myPointCut() { } // 这里注解里面的值为上面的方法名
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知 : " + joinPoint.getSignature().getName());
} // 当你只有一个方法,或者只在这儿用,可以直接写切入点表达式
@AfterReturning(value = "execution(* com.zby.service..*(..))", returning = "ret")
public void myAfterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
} //
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知执行方法前");
// 手动执行目标方法
Object obj = joinPoint.proceed(); System.out.println("环绕通知执行方法后");
return obj;
} @AfterThrowing(value = "myPointCut()", throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常通知 : " + e.getMessage());
} @After("myPointCut()")
public void myAfter(JoinPoint joinPoint) {
System.out.println("最终通知");
}
}

4)编写配置文件applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
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-4.3.xsd"> <context:component-scan base-package="com.zby"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

5)编写测试类:

package com.zby.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.zby.service.UserService; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class AOPTest { @Autowired
private UserService userService; @Test
public void testProxy() {
System.out.println("After Proxy......");
userService.saveUser("zby", "1234567890");
}
}

  6)控制台打印结果:

六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
六月 09, 2017 2:29:21 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6dae04e2, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@3bc2c9af, org.springframework.test.context.support.DirtiesContextTestExecutionListener@71471ecf]
六月 09, 2017 2:29:21 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
六月 09, 2017 2:29:21 下午 org.springframework.context.support.GenericApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.GenericApplicationContext@69f31d: startup date [Fri Jun 09 14:29:21 CST 2017]; root of context hierarchy
After Proxy......
环绕通知执行方法前
前置通知 : saveUser
save user[username=zby,password=1234567890]
环绕通知执行方法后
最终通知
后置通知 : saveUser , -->null

总结:对比起来,可以看出来使用最后一种方式开发AOP很方便,这也是我们最常用的,至于spring原生的AOP,大多在一些框架里面看到。使用整合AspectJ的方式,最主要的是要注意切面表达式的书写和方法参数传入,以及怎么使用这些参数。

Spring整合AspectJ的AOP的更多相关文章

  1. 在Spring整合aspectj实现aop的两种方式

    -----------------------------基于XML配置方案目标对象接口1 public interface IUserService { public void add(); pub ...

  2. (转)Spring使用AspectJ进行AOP的开发:注解方式

    http://blog.csdn.net/yerenyuan_pku/article/details/69790950 Spring使用AspectJ进行AOP的开发:注解方式 之前我已讲过Sprin ...

  3. Spring 基于 AspectJ 的 AOP 开发

    Spring 基于 AspectJ 的 AOP 开发 在 Spring 的 aop 代理方式中, AspectJ 才是主流. 1. AspectJ 简介 AspectJ 是一个基于 java 语言的 ...

  4. Spring整合JDBC以及AOP管理事务

    本节内容: Spring整合JDBC Spring中的AOP管理事务 一.Spring整合JDBC Spring框架永远是一个容器,Spring整合JDBC其实就是Spring提供了一个对象,这个对象 ...

  5. JAVAEE——spring03:spring整合JDBC和aop事务

    一.spring整合JDBC 1.spring提供了很多模板整合Dao技术 2.spring中提供了一个可以操作数据库的对象.对象封装了jdbc技术. JDBCTemplate => JDBC模 ...

  6. Spring使用AspectJ开发AOP:基于XML

    基于XML的声明式 基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面.切入点及声明通知,而所有的切面和通知都必须定义在 <aop:config> 元素中. 下面通过案例 ...

  7. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring使用AspectJ开发AOP基于XML和基于Annotation

    AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言.Spring 2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 Aspe ...

  8. [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.

    前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...

  9. Spring基于AspectJ的AOP的开发——注解

    源码:https://gitee.com/kszsa/dchart 一, AspectJ的概述: AspectJ是一个面向切面的框架,它扩展了Java语言.AspectJ定义了AOP语法所以它有一个专 ...

随机推荐

  1. csv、txt读写及模式介绍

    1读写模式 r以读方式打开文件,可读取文件信息 w已写方式打开文件,可向文件写入信息.如文件存在,则清空,再写入 a以追加模式打开文件,打开文件可指针移至末尾,文件不存在则创建 r+以读写方式打开文件 ...

  2. HDU 4803 Poor Warehouse Keeper(贪心)

    题目链接 题意 :屏幕可以显示两个值,一个是数量x,一个是总价y.有两种操作,一种是加一次总价,变成x,1+y:一种是加一个数量,这要的话总价也会相应加上一个的价钱,变成x+1,y+y/x.总价显示的 ...

  3. 「BZOJ 1001」狼抓兔子

    题目链接 luogu bzoj \(Solution\) 这个貌似没有什么好讲的吧,直接按照这个给的图建图就好了啊,没有什么脑子,但是几点要注意的: 建双向边啊. 要这么写,中间还要写一个\(whil ...

  4. 多线程《七》信号量,Event,定时器

    一 信号量 信号量也是一把锁,可以指定信号量为5,对比互斥锁同一时间只能有一个任务抢到锁去执行,信号量同一时间可以有5个任务拿到锁去执行,如果说互斥锁是合租房屋的人去抢一个厕所,那么信号量就相当于一群 ...

  5. 03process对象的其他方法属性

    一 Process对象的join方法 在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况 情况一:在主进程的任务与子进程的任务彼此独立的情况下, ...

  6. day08.1-Linux软件包管理

    Linux系统中的两种软件包:tar,保存内容为源码,编译后再安装:rpm,保存内容为编译后的机器码,直接安装.其中,rpm软件包由5部分构成,分别为: 第1部分是name,表示这个rpm软件包的名称 ...

  7. vi—终端中的编辑器

    *:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !impor ...

  8. JDBC_事务概念_ACID特点_隔离级别_提交commit_回滚rollback

    事务的概念 一组要么同时执行成功,要么同时执行失败的SQL语句,是数据库操作的一个执行单元! 事务开始于: 连接到数据库上,并执行一条DML语句(insert,update或delete),前一个事务 ...

  9. 二、为什么要选用pytest以及 pytest与unittest比较

    为什么要选择pytest,我看中的如下: 写case,不需要像unittest那样,创建测试类,继承unittest.TestCase pytest中的fixture(类似于setUp.tearDow ...

  10. 前端CSS的基本素养

    前端开发的三驾马车——html.css.js,先谈谈CSS CSS 前期:解决布局.特效.兼容问题 中级:网站风格的制定.色调.模块.布局方式.交互方式.逻辑设计等 高级:模块命名.类的命名.文件的组 ...