一、Spring-AOP的作用
在实际开发中,我们往往会遇到对已有功能做扩展的情况,我们需要在不对源码进行修改的情况下扩展功能,在没有spring-aop的情况下我们应该这么做:
情景1:我们现在要对AServiceImpl(实现了AService接口)中的login方法进行扩展,这种情形我们可以使用代理模式来做,过程如下:创建一个BServiceImpl类并实现AService接口,并重现doLogin方法。
class BServiceImpl implements AService{
private AService trueObj;
BServiceImpl(AService service){
trueObj = service;
}
@override
public void doLogin(){
sout("扩展前");
trueObj.doLogin();
sout("扩展后");
}
}
而AOP的出现就可以省略BServiceImpl的创建,我们指定一个类的方法为切点,然后我们需要对这个切点进行扩展,扩展又分为扩展前、扩展后、环绕、和异常等部分,我们可以对其进行声明然后指向切点。
id为before的bean是前置通知,在真实方法(doLogin)执行前执行,id为after的bean是后置通知在真实方法执行后执行
代码如下
<bean id="before" class="com.codeXie.MyBefore"></bean>
<bean id="after" class="com.codeXie.MyBefore"></bean>
<aop:config>
<aop:pointcut id="lg" expression="execution(* com.codeXie.service.Impl.AServiceImpl.doLogin(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="lg"></aop:advisor>
<aop:advisor advice-ref="after" pointcut-ref="lg"></aop:advisor>
</aop:config>
二、spring-aop的专业概念
真实对象:要进行功能扩展的对象,相当于AServicImpl对象
代理对象:完成功能扩展的对象,相当于BServiceImpl对象
在spring-aop中,代理对象是动态创建的
切点:要进行功能扩展的方法,相当于doLogin方法
前置通知方法:在切点执行之前就执行的扩展方法
后置通知方法:在切点执行后才执行的扩展方法
切面:有前置通知+切点+后置通知形成的横向切面
织入:形成切面的过程
AOP:面相切面编程
三、spring-aop的SchemaBase方法详解
一、 前置通知
使用:
- 声明一个普通Java类,实现BeforeAdvice接口。
- 在Spring配置文件中配置前置通知的bean对象
- 配置组装
方法:
- 方法名:before
- 调用者:代理对象中的扩展方法调用
- 方法体:声明切点之前执行的扩展代码
参数:
- Method method:切点的方法对象
- Object[] objects:代理方法接收的实参的数组
- Object o:真实对象
public class LoginBefore implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("start login....");
}
}
二、后置通知
使用:
- 声明一个普通Java类,实现AfterReturningAdvice接口。
- 在Spring配置文件中配置后置通知的bean对象
- 配置组装
方法:
- 方法名:after
- 调用者:代理对象中的扩展方法调用
- 方法体:声明切点之后执行的扩展代码
参数:
- Object o:真实方法的返回值
- Method method:切点的方法对象
- Object[] objects:代理方法接收的实参的数组
- Object o:真实对象
public class LoginAfter implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("Login end");
System.out.println("Login User:"+o);
}
}
三、环绕通知
使用:
- 声明一个普通Java类,实现MethodInterceptor接口。
- 在Spring配置文件中配置环绕通知的bean对象
- 配置组装
方法:
- 方法名:invoke
- 调用者:代理对象中的扩展方法调用
- 方法体:声明扩展代码同时根据需求是否放行。
参数:
- MethodInvocation
public class LoginAround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object[] arguments = methodInvocation.getArguments();
String user = (String) arguments[0];
String pwd = (String) arguments[1];
if(user==null || user == "" || pwd==null || pwd == ""){
System.out.println("username and pwd can't be null");
return null;
}
Object emp = methodInvocation.proceed();
if(emp != null){
System.out.println("login success");
}
return emp;
}
}
四、异常通知
使用:
- 声明一个普通Java类,实现ThrowAdvice接口。
- 在Spring配置文件中配置异常通知的bean对象
- 配置组装
方法:
- 方法名:afterThrowing
- 调用者:代理对象中的扩展方法的catch中调用
- 方法体:根据异常信息处理异常。
参数:Exception ex
参数的作用:获取异常信息
public class MyThrow implements ThrowsAdvice {
public void afterThrowing(Exception e){
System.out.println("我是异常通知:"+e.getMessage());
}
}
四、spring-aop的SchemaBase方式
步骤一:导入jar包
步骤二:在src下创建advice包,用来装载通知类
//前置通知需要实现接口MethodBeforeAdvice
public class LoginBefore implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
Logger logger = Logger.getLogger(LoginBefore.class);
logger.debug(objects[0]+"发起登陆");
}
}
//后置通知需要实现接口AfterReturningAdvice
public class LoginAfter implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
if(o!=null){
Logger logger = Logger.getLogger(LoginAfter.class);
logger.debug(objects[0]+"登陆成功");
}
}
}
步骤三:在applicationcontext.xml文件中配置资源的bean对象以及声明组装规则
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--配置dataSource的bean-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/bank?serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="j3391111"></property>
</bean>
<!--配置工厂的bean-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Mapper扫描bean-->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="factory"></property>
<property name="basePackage" value="com.codeXie.mapper"></property>
</bean>
<!--AccountServiceImpl的bean -->
<bean id="ac" class="com.codeXie.service.Impl.AccountServiceImpl">
<property name="mapper" ref="accountMapper"></property>
</bean>
<bean id="lc" class="com.codeXie.service.Impl.LoginServiceImpl">
<property name="mapper" ref="userMapper"></property>
</bean>
<bean id="loginBefore" class="com.codeXie.advice.LoginBefore"></bean>
<bean id="loginAfter" class="com.codeXie.advice.LoginAfter"></bean>
<aop:config>
<aop:pointcut id="lg" expression="execution(* com.codeXie.service.Impl.LoginServiceImpl.doLogin(..))"/>
<aop:advisor advice-ref="loginBefore" pointcut-ref="lg"></aop:advisor>
<aop:advisor advice-ref="loginAfter" pointcut-ref="lg"></aop:advisor>
</aop:config>
</beans>
spring-aop可以通过切点中execution中的路径自动匹配之前的bean,并根据实现的接口动态创建代理对象
步骤四:从spring容器获取代理对象并完成功能开发
@WebServlet("/UserServlet")
public class UserServlet extends BaseServlet{
private LoginService service;
@Override
public void init() throws ServletException {
ApplicationContext ac= WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
service= (LoginService) ac.getBean("lc");//通过获取切点动态获得代理对象
}
public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String uname = req.getParameter("uname");
String pwd = req.getParameter("pwd");
User user = service.doLogin(uname, pwd);
req.getSession().setAttribute("user",user);
resp.sendRedirect(this.getServletContext().getContextPath()+"/main.jsp");
resp.getWriter().print((user!=null) + "");
}
}
五、spring-aop的AspectJ方式
(一)XML配置方式
1. 创建通知类
public class MyAdvice {
//前置通知方法
@Before("execution(* com.bjsxt.pojo.Student.testStu(..))")
public void before(){
System.out.println("我是前置通知");
}
//后置通知方法
public void after(){
System.out.println("我是后置通知");
}
//环绕通知方法
public Object myRound(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕--前");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("环绕--后");
return proceed;
}
//异常通知方法
public void myThrow(){
System.out.println("我是异常通知");
}
}
2. 找到真实类
public class Student implements StudentInterface{
@Override
public String testStu() {
//int i=5/0;
System.out.println("我是Student "+"的学生方法,我被执行了,,,,:");
return "我是真实对象";
}
}
3. 配置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: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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="stu" class="com.bjsxt.pojo.Student"></bean>
<!--2.配置扩展bean-->
<bean id="advice" class="com.bjsxt.advice.MyAdvice"></bean>
<!--3.声明AOP-->
<aop:config>
//传入通知类
<aop:aspect ref="advice">-
<!–声明切点–>
<aop:pointcut id="mp" expression="execution(* com.bjsxt.pojo.Student.testStu())"/>
<!–声明前置通知方法–>-->
<aop:before method="before" pointcut-ref="mp"></aop:before>
<!–声明后置通知方法–>
<aop:after method="after" pointcut-ref="mp"></aop:after>
<!–声明环绕通知–>
<aop:around method="myRound" pointcut-ref="mp"></aop:around>
<!–声明异常通知–>
<aop:after-throwing method="myThrow" pointcut-ref="mp"></aop:after-throwing>
</aop:aspect>
</aop:config>
(二) 注解方式配置
1. xml配置
//扫描注解的范围,提高程序效率
<context:component-scan base-package="com.bjsxt.*"></context:component-scan>
//自动创建代理类,默认为jdk方式
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
2. 真实类的配置(相当于创建真实类的bean和切点)
@Component
public class Student implements StudentInterface{
//切点的创建不是必要的,但最好还是声明一下
@Pointcut("execution(* com.bjsxt.pojo.Student.testStu(..))")
@Override
public String testStu(String name) {
//int i=5/0;
// System.out.println("我是Student "+"的学生方法,我被执行了,,,,:");
System.out.println("我是Student "+name+"的学生方法,我被执行了,,,,:");
return "我是真实对象";
}
}
3. 通知类的配置
@Component //创建它的bean
@Aspect //声明它为通知类
public class MyAdvice {
//前置通知方法
@Before("execution(* com.bjsxt.pojo.Student.testStu(..))")
public void before(){
System.out.println("我是前置通知");
}
//后置通知方法
@After("execution(* com.bjsxt.pojo.Student.testStu(..))")
public void after(){
System.out.println("我是后置通知");
}
//环绕通知方法
@Around("execution(* com.bjsxt.pojo.Student.testStu(..))")
public Object myRound(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕--前");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("环绕--后");
return proceed;
}
//异常通知方法
@AfterThrowing("execution(* com.bjsxt.pojo.Student.testStu())")
public void myThrow(){
System.out.println("我是异常通知");
}
}
共同点
无论是哪种方式的aop,都需要创建通知类和真实类的bean,需要一个给定的切点并且通知方法需要被声明是哪种类型的通知方法
差别
对于有参数的切点使用SchemaBase方式
对于无参数的切点或者不需要参数的通知方法使用aspectJ方式