参考Tiffany_弥弥的博客,网址:https://www.jianshu.com/p/5b9a0d77f95f
参考吴胜贤的博客,网址:https://blog.csdn.net/qq_32454347/article/details/88847340
参考:https://blog.csdn.net/ysl19910806/article/details/91898875
https://blog.csdn.net/u010201575/article/details/104041677
package com.xx.xxx.utils;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodLog {
String remark() default ""; // 自定义的操作描述属性
String operType() default "0"; // 自定义的操作类型
}
package com.xx.xxx.utils;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.servlet.http.HttpServletRequest;
import com.xx.xxx.dao.ManageDao;
import com.xx.xxx.entity.Syslog;
import com.xx.xxx.entity.UsersT;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Component
@Aspect
public class LogService {
@Autowired
private ManageDao manageDao; // 保存日志表DAO
public LogService() {
System.out.println("Aop");
}
/**
* 自定义注解装饰
* @throws Throwable
*/
@Pointcut("@annotation(com.xx.xxx.utils.MethodLog)")
public void methodCachePointcut() {}
/**
* 方法执行的前后调用
* @param point
* @return
* @throws Throwable
*/
// 此注解为:调用前和后都执行,如果只调用前或后,用@Before或@After
@Around("methodCachePointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
// 获取日期,格式为:2020-01-01 23:23:23 星期四
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");
Calendar ca = Calendar.getInstance();
String operDate = df.format(ca.getTime());
String ip = getIpAddr(request);
ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
// 获取存在缓存中的用户Bean
UsersT user = (UsersT) SecurityUtils.getSubject().getSession().getAttribute("userVO");
String loginName;
// 初次登录没有用户Bean,则获取用户登录账号作为账号名称,如果业务中必须要名称就拿登录账号去库里查一遍
if (user != null) {
loginName = user.getUserName();
} else {
loginName = request.getParameter("userAccount");
}
// 获取自定义注解描述
String monthRemark = getMthodRemark(point);
// 获取方法名
String monthName = point.getSignature().getName();
// 获取类包
String packages = point.getThis().getClass().getName();
if (packages.indexOf("$$EnhancerBySpringCGLIB$$") > -1) {
try {
// 去掉CGLIB动态生成的类
packages = packages.substring(0, packages.indexOf("$$"));
} catch (Exception ex) {
ex.printStackTrace();
}
}
Object[] method_param = null;
Object object;
try {
method_param = point.getArgs(); //获取方法参数
//String param=(String) point.proceed(point.getArgs());
// 保存日志到数据库日志表
Syslog sysLog = new Syslog();
sysLog.setIpAddress(ip);
sysLog.setLoginName(loginName);
sysLog.setMethodName(packages + "." + monthName);
sysLog.setMethodRemark(monthRemark);
if(StringUtils.isNotBlank(monthRemark)){
sysLog.setOperation(monthRemark);
}
//sysLog.setOperationDate(operDate);
// 保存日志表(调用Mybatis和DAO不做细解了)
manageDao.saveOptionLog(sysLog);
// 请求对象属性及值
object = point.proceed();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return object;
}
/**
* 方法运行出现异常时调用(暂时用不到)
* @param ex
*/
public void afterThrowing(Exception ex) {
System.out.println("afterThrowing");
System.out.println(ex);
}
/**
* 获取自定义注解描述
* @param joinPoint
* @return
* @throws Exception
*/
public static String getMthodRemark(ProceedingJoinPoint joinPoint)
throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
String methode = "";
for (Method m : method) {
if (m.getName().equals(methodName)) {
Class[] tmpCs = m.getParameterTypes();
if (tmpCs.length == arguments.length) {
MethodLog methodCache = m.getAnnotation(MethodLog.class);
if (methodCache != null) {
methode = methodCache.remark();
}
break;
}
}
}
return methode;
}
/**
* 获取请求IP
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
if (request.getHeader("x-forwarded-for") == null) {
return request.getRemoteAddr();
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
4、配置启动对@AspectJ注解的支持及监听类,在Spring的配置文件中,添加如下配置即可
<!-- aop实现类 -->
<bean id="logService" class="com.xx.xxx.utils.LogService"></bean>
<!-- 启动对@AspectJ注解的支持 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
5、业务Controller引用,在@RequestMapping下添加自定义注解杰即可
@RequestMapping(value="/addUser",method=RequestMethod.POST)
@MethodLog(remark = "添加用户")
public void addUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
具体的业务
}
一、实例
切面:LogAspects.java
package com.spring.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LogAspects {
//抽取公共的切入点表达式
//1、本类引用
//2、其他的切面引用
@Pointcut("execution(public int com.spring.aop.MathCalculator.*(..))")
private void pointCut(){};
//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
//JoinPoint一定要出现在参数列表的第一位
@Before(value = "pointCut()")
public void logStart(JoinPoint joinpoint) {
System.out.println("logStart>>>>"+joinpoint.getSignature().getName()+">>>>"+Arrays.toString(joinpoint.getArgs()));
}
@After(value ="com.spring.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinpoint) {
System.out.println("logEnd>>>>>"+joinpoint.getSignature().getName()+">>>>"+Arrays.toString(joinpoint.getArgs()));
}
@AfterReturning(value ="execution(public int com.spring.aop.MathCalculator.*(..))",returning="object")
public void logReturn(Object object) {
System.out.println("logReturn>>>>"+object);
}
@AfterThrowing(value = "execution(public int com.spring.aop.MathCalculator.*(..))",throwing = "object")
public void logException(Exception object) {
System.out.println("logException>>>>"+object);
}
}
业务类:MathCalculator.java
package com.spring.aop;
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator >> div");
return i / j;
}
}
注入bean和代理MainConfigOfAop.java
package com.spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.spring.aop.LogAspects;
import com.spring.aop.MathCalculator;
/**
* AOP:【动态代理】
* 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式。
*
* 1、导入aop模块:Spring AOP:(spring-aspects)
* 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候讲日志进行打印(方法之前、方法运行结束、方法出现异常等)
* 3、定义一个日志切面类(LOgAspects);切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行对应的切面方法;
* 通知方法:
* 前置通知(@Before):logStart:在目标方法div()运行之前运行
* 后置通知(@After):logEnd:在目标方法div()运行结束之后运行
* 返回通知(@AfterReturning):logReturn:在目标方法div()正常返回之后运行
* 异常通知(@AfterThrowing):logException:在目标方法div()出现异常之后运行
* 环绕通知:动态代理,手动推进目标方法运行(joinPoint.procced())
* 4、给切面类的目标方法标注何时何地运行(通知注解)
* 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
* 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
* 7※给配置类中加@EnableAspectJAutoProxy 开启基于注解的AOP模式
* 在Spring中很多的@EnableXXX都是表示要开启XXX功能
*
* 主要三步:
* 1、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个类是切面类(@Aspect)
* 2、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
* 3、开启基于注解的AOP模式;@EnableAspectJAutoProxy
*
*/
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAop {
//业务逻辑类加入到容器中
@Bean
public MathCalculator mathCalculator() {
System.out.println("mathCalculator bean");
return new MathCalculator();
}
//切面类加入到容器中
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
测试:IOCTestAOP.java
package com.spring.test;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.spring.aop.MathCalculator;
import com.spring.config.MainConfigOfAop;
public class IOCTestAOP {
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAop.class);
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
//mathCalculator.div(10, 1);
mathCalculator.div(10, 0);
applicationContext.close();
}
}
输出:
mathCalculator bean
logStart>>>>div>>>>[10, 0]
MathCalculator >> div
logEnd>>>>>div>>>>[10, 0]
logException>>>>java.lang.ArithmeticException: / by zero
二、实例
如:execution(* com.my.spring.bean..(…)) 指定com.my.spring.bean包下所有类的所有方法作为切点
AspectJ切点表达式的指示器不只有execution:
arg() :限制连接点匹配参数为指定类型的执行方法
execution() :用于匹配是连接点的执行方法
this() :限制连接点匹配 AOP 代理的 bean 引用为指定类型的类
target :限制连接点匹配目标对象为指定类型的类
within() :限制连接点匹配指定的类型
实例:
基础接口BaseInterface
public interface BaseInterface {
/**
* 新增歌曲
**/
Integer addSong(String author, String songTitle);
/**
* 删除歌曲
**/
Integer delSong(String author, String songTitle);
}
实现类:BaseBean
@Component
public class BaseBean implements BaseInterface{
private String author;
private String songTitle;
private Integer count=0;
public Integer addSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("新增了一首歌:"+author+"-"+songTitle);
count++;
return count;
}
public Integer delSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("删除了一首歌:"+author+"-"+songTitle);
count--;
return count;
}
}
切面类:切面类BaseBeanAspect
package aspectj;
import org.apache.log4j.Logger;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BaseBeanAspect {
private Logger logger = Logger.getLogger(BaseBean.class);
/**
* 方法执行前的通知
*/
@Before("execution(* aspectj.*.*(..))")
public void beforeInvoke(){
logger.debug("方法执行前");
}
/**
* 方法执行后的通知
*/
@After("execution(* aspectj.*.*(..))")
public void afterInvoke(){
logger.debug("方法执行后");
}
/**
* 方法执行返回后的通知
*/
@AfterReturning("execution(* aspectj.*.*(..))")
public void afterReturning(){
logger.debug("==================方法执行完成");
}
/**
* 方法抛出异常的通知
*/
@AfterThrowing("execution(* aspectj.*.*(..))")
public void afterThrowing(){
logger.debug("==================方法执行报错");
}
}
创建自动化装配的配置类
@Configuration
@EnableAspectJAutoProxy//开启自动代理开关,启用切面
@ComponentScan(basePackages = {"aspectj"})
public class ComponentConfig {
}
创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class,classes = {ComponentConfig.class})
public class AppTest {
@Autowired
private BaseInterface baseInterface;
@Test
public void testBean(){
baseInterface.addSong("myBean","mySong");
baseInterface.delSong("myBean","mySong");
}
}
输出:
[DEBUG] 2019-03-27 11:55:28,075 method:aspectj.BaseBeanAspect.beforeInvoke(BaseBeanAspect.java:22)
方法执行前
新增了一首歌:myBean-mySong
[DEBUG] 2019-03-27 11:55:28,075 method:aspectj.BaseBeanAspect.afterInvoke(BaseBeanAspect.java:30)
方法执行后
[DEBUG] 2019-03-27 11:55:28,076 method:aspectj.BaseBeanAspect.afterReturning(BaseBeanAspect.java:38)
==================方法执行完成
[DEBUG] 2019-03-27 11:55:28,076 method:aspectj.BaseBeanAspect.beforeInvoke(BaseBeanAspect.java:22)
方法执行前
删除了一首歌:myBean-mySong
[DEBUG] 2019-03-27 11:55:28,076 method:aspectj.BaseBeanAspect.afterInvoke(BaseBeanAspect.java:30)
方法执行后
[DEBUG] 2019-03-27 11:55:28,077 method:aspectj.BaseBeanAspect.afterReturning(BaseBeanAspect.java:38)
==================方法执行完成
[DEBUG] 2019-03-27 11:55:28,077 method:org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener.beforeOrAfterTestMethod(AbstractDirtiesContextTestExecutionListener.java:106)
AOP的几种方式:
一.经典的基于代理的AOP
二.@AspectJ注解驱动的切面
三.纯POJO切面(纯粹通过<aop:fonfig>标签配置)
四.注入式AspectJ切面
一、经典的基于代理的AOP
定义接口helloworld
public interface HelloWorld {
void printHelloWorld();
void doPrint();
}
实现类HelloWorldImpl1.java
public class HelloWorldImpl1 implements HelloWorld {
public void printHelloWorld() {
System.out.println("------11111------按下HelloWorld1.printHelloWorld()-----11111111-------");
}
public void doPrint() {
System.out.println("------1111111------打印HelloWorldImpl1-----1111111------");
return ;
}
}
HelloWorldImpl2.java
public class HelloWorldImpl2 implements HelloWorld {
public void printHelloWorld() {
System.out.println("------222222------按下HelloWorld2.printHelloWorld()------2222222------");
}
public void doPrint() {
System.out.println("-------22222-----打印HelloWorldImpl2------22222-----");
return ;
}
}
TimeHandler类。因为一个是切入点前执行、一个是切入点之后执行,所以实现对应接口。
横切关注点,这里是打印时间:
public class TimeHandler implements MethodBeforeAdvice, AfterReturningAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("代理----前----CurrentTime = " + System.currentTimeMillis());
}
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("代理----后----CurrentTime = " + System.currentTimeMillis());
}
}
spring配置:application.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<!-- 定义被代理者 -->
<bean id="h1" class="com.lym.aopTest.HelloWorldImpl1"></bean>
<bean id="h2" class="com.lym.aopTest.HelloWorldImpl2"></bean>
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="timeHandler" class="com.lym.aopTest.TimeHandler"></bean>
<!-- 定义切入点位置,这里定义到了doPrint方法上 -->
<bean id="timePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*doPrint"></property>
</bean>
<!-- 使切入点与通知相关联,完成切面配置 -->
<bean id="timeHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="timeHandler"></property>
<property name="pointcut" ref="timePointcut"></property>
</bean>
<!-- 设置代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的对象,有打印时间能力 -->
<property name="target" ref="h1"></property>
<!-- 使用切面 -->
<property name="interceptorNames" value="timeHandlerAdvisor"></property>
<!-- 代理接口,hw接口 -->
<property name="proxyInterfaces" value="com.lym.aopTest.HelloWorld"></property>
</bean>
<!-- 设置代理 -->
<bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的对象,有打印时间能力 -->
<property name="target" ref="h2"></property>
<!-- 使用切面 -->
<property name="interceptorNames" value="timeHandlerAdvisor"></property>
<!-- 代理接口,hw接口 -->
<property name="proxyInterfaces" value="com.lym.aopTest.HelloWorld"></property>
</bean>
<!--<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>-->
</beans>
Test.java
public class Test {
public static void main(String[] args){
//@SuppressWarnings("resource")
//如果是web项目,则使用以下代码加载配置文件,如果是一般的Java项目,则使用注释的方式
ApplicationContext appCtx = new ClassPathXmlApplicationContext("conf/application.xml");
//ApplicationContext appCtx = new FileSystemXmlApplicationContext("conf/application.xml");
HelloWorld hw1 = (HelloWorld) appCtx.getBean("proxy");
HelloWorld hw2 = (HelloWorld) appCtx.getBean("proxy2");
hw1.printHelloWorld();
System.out.println();
hw1.doPrint();
System.out.println();
hw2.printHelloWorld();
System.out.println();
hw2.doPrint();
}
}
输出:
-----1111---按下HelloWorld1.printHelloWorld()--1111
代理---前--CurrentTime=1113454
----111--打印HelloWorldImpl1---111--
代理---后---CurrentTime=111111
-----222---按下HelloWorld1.printHelloWorld()--2222
代理---前--CurrentTime=1113454
----222--打印HelloWorldImpl1---22--
代理---后---CurrentTime=111111