053天
行百里者半九十,想要在一个行业里成为顶尖人才,一定满足一万小时定律,要想学好JAVA,需要持之以恒不断地努力,每天都要勤思考+善于询问+解决问题!
知识温故而知新>>>>>>
今天学到的知识>>>>>>
Spring整合MyBatis开发步骤,并描述其配置详情
Spring+MyBatis+Servlet(SpringMVC)整合步骤
一、Spring整合MyBatis:
总体思路:DataSource--->SqlSessionFacotyBean---->DAO(Mapper)---->Service
1.新建web项目并添加spring和mybatis依赖的jar文件(不要忘记添加spring整合mybatis的jar文件:mybatis-spring-x.x.x.jar)
2.新建实体类(Admin)
public class Admin implements Serializable {
private int id;
private String uname;
private String pwd;
//省略的getter和setter方法
}
3.新建Mapper接口(AdminMapper)和映射文件(AdminMapper.xml)
a.AdminMapper接口
public interface AdminMapper {
//查询单个用户操作
public Admin selectOne(String uname,String pwd);
}
b.AdminMapper映射文件
4.新建Service接口和实现类
a.AdminService接口
public interface AdminService {
public Admin login(String uname,String pwd);
}
b.AdminServiceImpl类,实现了AdminService接口
public class AdminServiceImpl implements AdminService {
AdminMapper adminMapper;
@Override
public Admin login(String uname, String pwd) {
Admin admin = adminMapper.selectOne(uname,pwd);
return admin;
}
//setter方法:setter注入
public void setAdminMapper(AdminMapper adminMapper) {
this.adminMapper = adminMapper;
}
}
5.在src下(classpath:类路径)新建spring的配置文件:applicationContext.xml
a.配置数据源DataSource,指定连接的数据库
b.配置SqlSessionFactoryBean,并注入数据源,用于生产SqlSession
c.配置扫描Mapper接口,作用是根据Mapper接口和映射文件生成实现类(类似与DAO的实现类)
d.配置Service的实现类,并注入Mapper对象
6.编写测试类
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
AdminService adminService = ac.getBean("adminService", AdminService.class);
Admin admin = adminService.login("zhangsan","123");
System.out.println(admin);
}
}
二、Spring整合Servlet
1.在web.xml中配置初始化的参数app,用于指定spring配置文件的名称
app
applicationContext.xml
2.编写jsp页面login.jsp和success.jsp
a.login.jsp
用户登陆
b.success.jsp
用户登陆成功!
3.编写Servlet,读取spring配置文件获取service对象
@WebServlet("/AdminLogin")
public class AdminLogin extends HttpServlet{
AdminService adminService;
//Servlet的初始化方法:初始化Spring容器,并获取service对象
@Override
public void init() throws ServletException {
//读取全局参数app对应的参数值:applicationContext.xml
String path = this.getServletContext().getInitParameter("app");
ApplicationContext app=new ClassPathXmlApplicationContext(path);
//获得业务层创建好的对象
adminService = app.getBean("adminService",AdminService.class);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//[1]接受页面数据
String uname = req.getParameter("uname");
String pwd = req.getParameter("pwd");
//[2]数据处理
Admin admin = adminService.login(uname, pwd);
//[3]做出响应
if(admin!=null){
resp.sendRedirect(req.getContextPath()+"/success.jsp");
}else {
req.setAttribute("msg","登陆失败");
req.getRequestDispatcher("/login.jsp").forward(req,resp);
}
}
}
AOP的相关概念
1.连接点(Joinpoint)
程序执行的某个特定位置:如类开始初始化之前、类初始化之后、类某个方法调用前、调用后等;一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就成为“连接点”,Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后以及方法调用前后的这些程序执行点织入增强。比如:黑客攻击系统需要找到突破口,没有突破口就没有办法攻击,从某种程度上来说,AOP就是一个黑客,连接点就是AOP向目标类攻击的候选点。连接点有两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位;如在Test.foo()方法执行前的连接点,执行点为Test.foo,方位为该方法执行前的位置。Spring使用切点对执行点进行定位,而方位则在增强类型中定义。
2.切点(Pointcut)
每个程序类都拥有许多连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。但在为数众多的连接点中,如何定位到某个连接点上呢?AOP通过切点定位特定连接点。通过数据库查询的概念来理解切点和连接点:连接点相当于数据库表中的记录,而切点相当于查询条件。连接点和切点不是一一对应的关系,一个切点可以匹配多个连接点。
在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点;其实确切的说应该是执行点而非连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。
3.增强(Advice)
增强是织入到目标类连接点上的一段程序代码(好比AOP以黑客的身份往业务类中装入木马),增强还拥有一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点了,所以Spring提供的增强接口都是带方位名的:BefortAdvice、AfterReturningAdvice、ThrowsAdvice等。(有些将Advice翻译为通知,但通知就是把某个消息传达给被通知者,并没有为被通知者做任何事情,而Spring的Advice必须嵌入到某个类的连接点上,并完成了一段附加的应用逻辑;)
4.目标对象(Target)
增强逻辑的织入目标类,如果没有AOP,目标业务类需要自己实现所有逻辑,在AOP的帮助下,目标类只实现那些非横切逻辑的程序逻辑,而其他监测代码则可以使用AOP动态织入到特定的连接点上。
5.引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法,这样即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态的为该业务类添加接口的实现逻辑,让这个业务类成为这个接口的实现类。
6.织入(Weaving)
织入是将增强添加到目标类具体连接点上的过程,AOP就像一台织布机,将目标类、增强或者引介编织到一起,AOP有三种织入的方式:
a.编译期间织入,这要求使用特殊的java编译器;
b.类装载期织入,这要求使用特殊的类装载器;
c.动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入。
7.代理(Proxy)
一个类被AOP织入增强后,就产生出了一个结果类,它是融合了原类和增强逻辑的代理类。
8.切面(Aspect)
切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
总结:AOP的工作重点就是如何将增强应用于目标对象的连接点上,这里首先包括两个工作:第一,如何通过切点和增强定位到连接点;第二,如何在增强中编写切面的代码。
代理的概念
代理模式是常用的java设计模式,他的特征是代理类与委托类(或目标类)有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成,无需手动编写代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。
代理原理:代理对象内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
JDK动态代理
a. JDK动态代理是面向接口的,必须提供一个委托类和代理类都要实现的接口,只有接口中的方法才能够被代理。
b. JDK动态代理的实现主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口。**
**InvocationHandler接口:**
来看看java的API帮助文档是怎么样描述InvocationHandler接口的:
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
说明:每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。同时在invoke的方法里 我们可以对被代理对象的方法调用做增强处理(如添加事务、日志、权限验证等操作)。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
参数说明:
Object proxy:指被代理的对象。
Method method:要调用的方法。(指代的是我们所要调用代理对象的某个方法的Method对象)
Object[] args:方法调用时所需要的参数。(指代的是调用真实对象某个方法时接受的参数)
可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。
**Proxy类: **
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:
参数说明:
ClassLoader loader:类加载器
Class>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler接口的子类实例
**JDK动态代理示例:**
定义一个业务接口IUserService,如下:
package com.spring.aop; public interface IUserService { //添加用户
public void addUser(); //删除用户
public void deleteUser();
}
一个简单的实现类UserServiceImpl,如下:
package com.spring.aop; public class UserServiceImpl implements IUserService{ public void addUser(){
System.out.println("新增了一个用户!");
} public void deleteUser(){
System.out.println("删除了一个用户!");
}
}
现在我们要实现的是,在addUser和deleteUser之前和之后分别动态植入处理。
JDK动态代理主要用到java.lang.reflect包中的两个类:Proxy和InvocationHandler。
InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑和业务逻辑编织在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
如下,我们创建一个InvocationHandler实例DynamicProxy:**(当执行动态代理对象里的目标方法时,实际上会替换成调用DynamicProxy的invoke方法)**
public class DynamicProxy implements InvocationHandler{ //被代理对象(就是要给这个目标类创建代理对象)
private Object target; //传递代理目标的实例,因为代理处理器需要,也可以用set等方法。
public DynamicProxy(Object target){ this.target=target;
} /** * 覆盖java.lang.reflect.InvocationHandler的方法invoke()进行织入(增强)的操作。
* 这个方法是给代理对象调用的,留心的是内部的method调用的对象是目标对象,可别写错。
* 参数说明:
* proxy是生成的代理对象,method是代理的方法,args是方法接收的参数 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ //目标方法之前执行
System.out.println("do sth Before..."); //通过反射机制来调用目标类方法
Object result = method.invoke(target, args); //目标方法之后执行
System.out.println("do sth After...\n"); return result;
}
}
下面是测试:
//用java.lang.reflect.Proxy.newProxyInstance()方法创建动态实例来调用代理实例的方法
import java.lang.reflect.Proxy; public class DynamicTest { public static void main(String[] args){ //希望被代理的目标业务类
IUserService target = new UserServiceImpl(); //将目标类和横切类编织在一起
DynamicProxy handler= new DynamicProxy(target); //创建代理实例,它可以看作是要代理的目标业务类的加多了横切代码(方法)的一个子类 //创建代理实例(使用Proxy类和自定义的调用处理逻辑(handler)来生成一个代理对象)
IUserService proxy = (IUserService)Proxy.newProxyInstance(
target.getClass().getClassLoader(),//目标类的类加载器
target.getClass().getInterfaces(), //目标类的接口
handler); //横切类
proxy.addUser();
proxy.deleteUser();
}
}
说明:上面的代码完成业务类代码和横切代码的编制工作,并生成了代理实例,newProxyInstance方法的第一个参数为类加载器,第二个参数为目标类所实现的一组接口,第三个参数是整合了业务逻辑和横切逻辑的编织器对象。
每一个动态代理实例的调用都要通过InvocationHandler接口的handler(调用处理器)来调用,动态代理不做任何执行操作,只是在创建动态代理时,把要实现的接口和handler关联,动态代理要帮助被代理执行的任务,要转交给handler来执行。其实就是调用invoke方法。(可以看到执行代理实例的addUser()和deleteUser()方法时执行的是DynamicProxy的invoke()方法。)
运行结果:
![image](https://upload-images.jianshu.io/upload_images/6009888-367c89071e63c9d0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
基本流程:用Proxy类创建目标类的动态代理,创建时需要指定一个自己实现InvocationHandler接口的回调类的对象,这个回调类中有一个invoke()用于拦截对目标类各个方法的调用。创建好代理后就可以直接在代理上调用目标对象的各个方法。
实现动态代理步骤:
A. 创建一个实现接口InvocationHandler的类,他必须实现invoke方法。
B.创建被代理的类以及接口。
C.通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class>[]interfaces, InvocationHandler handler)创建一个代理。
D.通过代理调用方法。
**使用JDK动态代理有一个很大的限制,就是它要求目标类必须实现了对应方法的接口,它只能为接口创建代理实例。**我们在上文测试类中的Proxy的newProxyInstance方法中可以看到,该方法第二个参数便是目标类的接口。如果该类没有实现接口,这就要靠cglib动态代理了。
CLIMB动态代理
CGLib采用非常底层的字节码技术,可以为一个类创建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势植入横切逻辑。
字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然用CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖。
a.使用CGLIB动态代理不要求必须有接口,生成的代理对象是目标对象的子类对象,**所以需要代理的方法不能是private或者final或者static的。**
b.使用CGLIB动态代理需要有对cglib的jar包依赖(导入asm.jar和cglib-nodep-2.1_3.jar)
CGLibProxy与JDKProxy的代理机制基本类似,只是其动态代理的代理对象并非某个接口的实现,而是针对目标类扩展的子类。换句话说JDKProxy返回动态代理类,是目标类所实现接口的另一个实现版本,它实现了对目标类的代理(如同UserDAOProxy与UserDAOImp的关系),而CGLibProxy返回的动态代理类,则是目标代理类的一个子类(代理类扩展了UserDaoImpl类)
**cglib 代理特点:**
CGLIB 是针对类来实现代理,它的原理是对指定的目标类生成一个子类,并覆盖其中方法。因为采用的是继承,**所以不能对 finall 类进行继承**。
我们使用CGLIB实现上面的例子:
代理的最终操作类:
public class CglibProxy implements MethodInterceptor{ //增强器,动态代码生成器
Enhancer enhancer = new Enhancer(); /** * 创建代理对象
* @param clazz
* @return 返回代理对象 */
public Object getProxy(Class clazz){ //设置父类,也就是被代理的类(目标类)
enhancer.setSuperclass(clazz); //设置回调(在调用父类方法时,回调this.intercept())
enhancer.setCallback(this); //通过字节码技术动态创建子类实例(动态扩展了UserServiceImpl类)
return enhancer.create();
} /** * 拦截方法:在代理实例上拦截并处理目标方法的调用,返回结果
* obj:目标对象代理的实例;
* method:目标对象调用父类方法的method实例;
* args:调用父类方法传递参数;
* proxy:代理的方法去调用目标方法 */
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy) throws Throwable{
System.out.println("--------测试intercept方法的四个参数的含义-----------");
System.out.println("obj:"+obj.getClass());
System.out.println("method:"+method.getName());
System.out.println("proxy:"+proxy.getSuperName()); if(args!=null&&args.length>0){ for(Object value : args){
System.out.println("args:"+value);
}
} //目标方法之前执行
System.out.println("do sth Before..."); //目标方法调用 //通过代理类实例调用父类的方法,即是目标业务类方法的调用
Object result = proxy.invokeSuper(obj, args); //目标方法之后执行
System.out.println("do sth After...\n"); return result;
}
}
测试类:
package com.spring.aop;
public class CglibProxyTest { public static void main(String[] args){
CglibProxy proxy=new CglibProxy(); //通过java.lang.reflect.Proxy的getProxy()动态生成目标业务类的子类,即是代理类,再由此得到代理实例 //通过动态生成子类的方式创建代理类
IUserService target=(IUserService)proxy.getProxy(UserServiceImpl.class);
target.addUser();
target.deleteUser();
}
}
基本流程:需要自己写代理类,它实现MethodInterceptor接口,有一个intercept()回调方法用于拦截对目标方法的调用,里面使用methodProxy来调用目标方法。创建代理对象要用Enhance类,用它设置好代理的目标类、由intercept()回调的代理类实例、最后用create()创建并返回代理实例。
输出:
![image.png](https://upload-images.jianshu.io/upload_images/6009888-d49fb1e659e75364.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们看到达到了同样的效果。它的原理是生成一个父类enhancer.setSuperclass(clazz)的子类enhancer.create(),然后对父类的方法进行拦截enhancer.setCallback(this). 对父类的方法进行覆盖,所以父类方法不能是final的。
**总结:**
(1).通过输出可以看出,最终调用的是com.spring.aop.UserServiceImpl的子类(也是代理类)com.spring.aop.UserServiceImpl$$EnhancerByCGLIB$$43831205的方法。
(2). private,final和static修饰的方法不能被代理。
**注意:**
(1).CGLIB是通过实现目标类的子类来实现代理,不需要定义接口。
(2).生成代理对象使用最多的是通过Enhancer和继承了Callback接口的MethodInterceptor接口来生成代理对象,设置callback对象的作用是当调用代理对象方法的时候会交给callback对象的来处理。
(3).创建子类对象是通过使用Enhancer类的对象,通过设置enhancer.setSuperClass(Class class)和enhancer.setCallback(Callback callback)来创建代理对象。
解释MethodInterceptor接口的intercept方法:
参数说明:Object var1代表的是子类代理对象,Method var2代表的是要调用的方法反射对象,第三个参数是传递给调用方法的参数,前三个参数和JDK的InvocationHandler接口的invoke方法中参数含义是一样的,第四个参数MethodProxy对象是cglib生成的用来代替method对象的,使用此对象会比jdk的method对象的效率要高。
如果使用method对象来调用目标对象的方法: method.invoke(var1, var3),则会陷入无限递归循环中, 因为此时的目标对象是目标类的子代理类对象。
MethodProxy类提供了两个invoke方法:
注意此时应该使用invokeSuper()方法,顾名思义调用的是父类的方法,若使用invoke方法,则需要提供一个目标类对象,但我们只有目标类子类代理对象,所以会陷入无限递归循环中。
CGLIB所创建的动态代理对象的性能比JDK所创建的动态代理对象的性能高很多,但创建动态代理对象时比JDK创建动态代理对象要花费更长的时间。