一、控制反转IOC
1. 概念介绍
控制反转分开来开,控制指的是创建对象、对象的属性赋值、对象之间的关系管理等等;而反转指的由容器代替开发人员管理对象、创建对象、给属性赋值。
2. IOC的实现:依赖注入DI
Spring 容器是一个超级工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用依赖注入 DI 的方式来管理 Bean 之间的依赖关系。spring底层创建对象,使用的是反射机制。
2.1 基于XML方式
首先创建bean,有三种方式:构造函数、静态工厂、实例工厂
<!--
通过构造函数创建
id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依赖关系也是通过 id 关联的。
class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。
-->
<bean id="SomeService" class="com.gmy.SomeServiceImpl"></bean>
<!--
通过静态工厂创建,静态工厂类的所有的方法必须是static
class 静态工厂全限定类名
factory-method 静态方法名
public static UserService createService(){
return new UserServiceImpl();
}
-->
<bean id="MyBeanFactory" class="com.gmy.MyBeanFactory" factory-method="createService"></bean>
<!--
通过工厂实例创建,需要两个bean,一个是工厂,一个是目标类
factory-bean 工厂实例bean
factory-method 普通方法
public UserService createService(){
return new UserServiceImpl();
}
-->
<bean id="myBeanFactoryNew" class="com.gmy.MyBeanFactory"></bean>
<bean id="SomeServiceNew" factory-bean="myBeanFactoryNew" factory-method="createService"></bean>
接下来是依赖注入,分为set方法注入和有参构造函数注入
- set方法注入:通过属性的set方法注入,如果没有setr方法则报错;另外需要该类有无参构造函数
set方法注入 :spring调用类的set方法, 在set方法中完成属性赋值
使用<property>标签
1)简单类型的set注入
<bean id="xxx" class="yyy">
<property name="属性名字" value="此属性的值"/>
<property....>
</bean>
2) 引用类型的set注入
<bean id="xxx" class="yyy">
<property name="属性名称" ref="bean的id(对象的名称)" />
</bean>
- 构造函数注入
构造方法注入:spring调用类的有参数构造方法,在创建对象的同时,在构造方法中给属性赋值。
使用<constructor-arg>标签
标签属性:
name:表示构造方法的形参名
index:表示构造方法的参数的位置,参数从左往右位置是 0 , 1 ,2的顺序
value:构造方法的形参类型是简单类型的,使用value
ref:构造方法的形参类型是引用类型的,使用ref
- 引用类型自动注入
引用类型的自动注入:有两种,分为byName, byType
1.byName(按名称注入):java类中引用类型的属性名和spring容器中<bean>的id名称一样,且数据类型是一致的
<bean id="xxx" class="yyy" autowire="byName">
简单类型属性赋值
</bean>
2.byType(按类型注入):java类中引用类型的数据类型和spring容器中<bean>的class属性是同源关系的
同源的意思:
a.java类中引用类型的数据类型和bean的class的值是一样的。
b.java类中引用类型的数据类型和bean的class的值父子类关系的。
c.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
语法:
<bean id="xxx" class="yyy" autowire="byType">
简单类型属性赋值
</bean>
注意:在byType中,在xml配置文件中声明bean只能有一个符合条件的,存在多个是错误的
2.2 基于注解方式
首先创建bean
@Component: 创建对象的, 等同于<bean>的功能
属性:value 就是对象的名称,也就是bean的id值,值是唯一的,创建的对象在整个spring容器中唯一
位置:在类的上面
@Component(value = "myStudent")
public class Student{}
等同于
<bean id="myStudent" class="com.gmy.Student" />
当然不止该标签可以创建bean,
还有@Repository,@Service,@Controller分别对应dao层、service层、controller层
接下来是依赖注入
- 简单类型:使用@Value注解,放于属性上面即可
- 引用类型:使用@Autowired、@Resource注解
@Autowired:默认使用的是byType自动注入,用在属性定义的上面,无需set方法
@Qualifier(value="bean_id"):在属性上额外加上该标签,表示使用指定名称的bean完成赋值
@Resource:默认是byName,如果byName赋值失败,再使用byType
二、面向切面编程AOP
1. 概念介绍
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术,常用功能为日志记录,性能统计,安全控制,事务处理,异常处理(内容来自百度百科),通过上面我们可以了解到,AOP主要是将一些公共方法从业务方法中剥离出来,做成统一的代码块。
几个名词:
- 切面aspect:指定就是切面类,切面类会管理着切点、通知
- 通知advice:需要增强到业务方法中的公共代码,在需要增加的业务方法不同位置执行(前置通知、后置通知、异常通知、环绕通知)
- 切点pointcut:决定哪些方法需要增强,哪些不需要增强,结合切点表达式实现
- 连接点join point:被增强的业务方法
- 目标对象:增强的对象
2. 原理
AOP底层实现采用的是动态代理,分为两种情况,对于有接口的采用JDK动态代理,对于没有接口的采用CGLIB动态代理。JDK动态代理只提供接口的代理,不支持类的代理
JDK运行时为目标类生成一个动态代理类$proxy*.class,该代理类实现了目标类接口,并且实现接口所有的方法增强代码,调用时,先增强,然后通过反射来调用目标方法;CGLIB代理底层是通过ASM在运行时动态的生成一个目标类的子类,通过继承的方式做的动态代理,因此若某个类被标记为final,那么他是无法使用CGLIB做动态代理
创建UserService接口实现类UserServiceImpl的代理对象,增强类的方法
使用Proxy类的newProxyInstance方法动态代理,三个参数:类加载器、增强方法所在的类,这个类实现的接口
增强方法所在的类要实现这个接口Invocationhandler,创建代理对象,写增强方法
public class JDKProxy {
public static void main(String[] args) {
//创建接口实现类代理对象
Class[] interfaces = {UserService.class};
UserServiceImpl userServiceImpl = new UserServiceImpl();
UserService userService = (UserService) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserServiceProxy(userServiceImpl));
int sum = userService.add(4,6);
System.out.println(sum);
}
}
//创建代理对象
class UserServiceProxy implements InvocationHandler{
/**
* 创建谁的代理对象,要把谁传递进来
* 有参函数构造传递
*/
private Object obj;
public UserServiceProxy(Object obj){
this.obj = obj;
}
//增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前,可以打印参数等
System.out.println("方法之前执行"+method.getName()+"传递的参数"+ Arrays.toString(args));
//被增强方法执行
Object res = method.invoke(obj,args);
//方法之后,可以记录结果、处理事务等等,此处随便打印一下
System.out.println("方法之后执行"+obj);
return res;
}
}
public interface UserService {
public int add(int a,int b);
public String update(String id);
}
public class UserServiceImpl implements UserService {
@Override
public int add(int a, int b) {
System.out.println("方法执行了");
return a+b;
}
@Override
public String update(String id) {
return id;
}
}
结果:
方法之前执行add传递的参数[4, 6]
方法执行了
方法之后执行com.zy.bysj.AOP.UserServiceImpl@36aa7bc2
10
3. AOP的实用场景
日志收集、全局异常处理
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gmy.utils.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 定义拦截类:指定切入点、切面,并做拦截处理
*/
@Component//指明这是一个组件,被spring接管,注册bean
@Aspect//指明这是一个切面
@Order(1)//当存在多个切面时,该注解指定执行的优先级:参数越小,优先级越高
public class LogTrackAspect {
private static final Logger log = LoggerFactory.getLogger(LogTrackAspect.class);
/**
* 定义切点方法:
* 四个常用的表达式:@execution(),@annotation(),@within(),@target()
*
* @execution(* com.gmy.controller..*.*(..)))范围指定方法
* 第一个*号的位置,表示返回值类型,*代表所有类型
* 第二个*号的位置,表示类名,*表示所有类
* *(..):这个星号表示所有的方法,后面括号里表示方法的参数,两个句点表示任何参数
*
* @within(org.apache.dubbo.config.annotation.Service)匹配指定类、注解标注类下的所有方法
*/
/**
*定义一个切点,所有@LogTrack注解修饰的会被织入advice
*/
@Pointcut(value = "@annotation(com.zy.bysj.AOP.LogTrack)")
public void access(){
}
/**
* 定义一个切点,拦截com.gmy.controller.AopController包下的所有方法
*/
@Pointcut("execution(* com.gmy.controller.AopController..*.*(..))")
public void pointCut(){
}
//进入切点,先经过的第一站
//@Before 注解指定的方法在切面切入目标方法之前执行,可以做一些 Log 处理,也可以做一些信息的统计,比如获取用户的请求 URL 以及用户的 IP 地址等等
@Before("pointCut()")
// @Before("access()")
public void doBefore(JoinPoint joinPoint){
log.info("-aop日志记录启动-doBefore方法进入"+new Date());
//获取签名
Signature signature = joinPoint.getSignature();
//获取切入的包名
String declaringTypeName = signature.getDeclaringTypeName();
//获取即将执行的方法名
String funcName = signature.getName();
log.info("即将执行的方法为:{},属于{}包",funcName,declaringTypeName);
//也可以记录一些信息,比如获取请求的URL和IP
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String URL = request.getRequestURI();
String IP = request.getRemoteAddr();
log.info("请求的url为:{},ip地址为{}",URL,IP);
}
/**
* 环绕增强,在before前就会触发
* @param pjp
* @param logTrack
* @return
* @throws Throwable
* 两个特性:
* @Around注解可以自由选择增强动作与目标方法的执行顺序,也就是可以在增强动作前后,甚至过程中执行目标方法,
* 这个特性的实现在于,调用ProceedingJoinPoint参数的proceed()方法才会执行目标方法
*
* @Around可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值
* 当定义一个Around增强处理方法时,该方法的第一个形参必须是ProceedingJoinPoint类型,调用其proceed方法
* 才会执行目标方法
* 调用proceed方法时,还可以传入一个Object[]对象,该数组中的值将被传入目标方法作为实参,这是around增强
* 处理方法可以改变目标方法参数值的关键
* 注意:功能虽然强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturning
* 就能解决的问题,就没有必要使用Around了。如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用
* Around。尤其是需要使用增强处理阻止目标的执行,或需要改变目标方法的返回值,则只能使用Around增强处理了。
*/
@Around("access()")
public Object around(ProceedingJoinPoint pjp,LogTrack logTrack) throws Throwable {
System.out.println("-aop 日志环绕阶段-"+new Date());
//根据ServletRequestAttributes 获取前端请求方法名、参数、路径等信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURI();
String ip = IpUtils.getIpAddr(request);
/**
* 下面三个参数对应的自定义注解的三个变量,获取到的是在controller层定义的值
*/
String logTrackValue = logTrack.value();
int num = logTrack.num();
String opertion = logTrack.opertion();
Object[] pipArray = pjp.getArgs();
//多参,不是MAP/JsonObject方式
if(pipArray.length>1){
List<Object> argList = new ArrayList<>();
for(Object arg:pjp.getArgs()){//这里arg对应的是参数的值
//request/response无法使用toJSON
if(arg instanceof HttpServletRequest){//instanceof关键字,严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,是则返回true
argList.add("request");
}else if(arg instanceof HttpServletResponse){
argList.add("response");
}else{
argList.add(JSON.toJSON(arg));
}
}
//接口签名
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
//参数名数组
String[] parameterNames = ((MethodSignature) signature).getParameterNames();
System.out.println("参数名数组:"+new ArrayList(Arrays.asList(parameterNames)));
System.out.println("参数是:"+argList.toString());
System.out.println("模块名称:"+logTrackValue);
System.out.println("操作编号:"+num);
System.out.println("操作名称:"+opertion);
System.out.println("url:"+url);
System.out.println("ip:"+ip);
return pjp.proceed();
}else{
Object param = pipArray[0];
System.out.println("logTrackValue:"+logTrackValue);
System.out.println("url:"+url);
System.out.println("ip:"+ip);
//这里打印的是传入的参数
System.out.println("param:"+param.toString());
/**
* 这里尝试修改参数,目标方法里打印的会是修改的参数
* 如果传入的Object[ ]数组长度与目标方法所需要的参数个数不相等,或者Object[ ]数组元素与目标方法所需参数的类型不匹配,程序就会出现异常
*/
JSONObject object = new JSONObject();
object.put("name","zhangsan");
object.put("age",10);
pipArray[0] = object;
return pjp.proceed(pipArray);
}
}
//return pjp.proceed(); 这个是从切点的环绕增强里面脱离出来,接下来会进入before阶段 ,然后回到接口,再回来after阶段。
//所以扩展业务逻辑处理的话,可以放在return pjp.proceed();这行代码之前,例如判断用户密码是否正确;判断用户权限等等
//进来切点,最后经过的一站,也是方法正常结束后
//@After 注解和 @Before 注解相对应,指定的方法在切面切入目标方法之后执行,也可以做一些完成某方法之后的 Log 处理。
@After("pointCut()")
// @After("access()")
public void after(JoinPoint joinPoint){
log.info("-aop-拦截开始-"+new Date());
Signature signature = joinPoint.getSignature();
String method = signature.getName();
log.info("方法{}已经执行完成",method);
}
/**
* 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
* @param joinPoint
* @param object
*/
@AfterReturning(pointcut = "pointCut()",returning = "object")
public void doAfterReturning(JoinPoint joinPoint,Object object){
Signature signature = joinPoint.getSignature();
String method = signature.getName();
log.info("方法{}执行完成,返回参数为{}",method,object);
//实际项目中可以根据业务做具体的返回值增强
log.info("对返回的参数进行业务上的增强:{}",object+"增强版");
}
/**
* 当被切方法执行过程中抛出异常时,会进入 @AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。
* 要注意的是 throwing 属性的值必须要和参数一致,否则会报错。该方法中的第二个入参即为抛出的异常。
* @param joinPoint
* @param ex
*/
@AfterThrowing(pointcut = "access()",throwing = "ex")
public void doAfterThrowing(JoinPoint joinPoint,Throwable ex){
Signature signature = joinPoint.getSignature();
String method = signature.getName();
//处理异常的逻辑
log.info("执行方法{}出错,异常为{}",method,ex);
}
}